10

我的目标是使用CreateFile(然后发出一些WriteFiles 和ReadFiles)打开通过 USB 连接的打印机。

如果打印机是 LPT 打印机,我只会做CreateFile("LPT1:", ...). 但是对于 USB 打印机,必须传递一个特殊的设备路径CreateFile才能打开该打印机。

正如我能够找到的那样,此设备路径是通过SetupDiGetClassDevs-> SetupDiEnumDeviceInterfaces-> SetupDiGetDeviceInterfaceDetail-> DevicePathmember检索的,如下所示:

\\?\usb#vid_0a5f&pid_0027#46a072900549#{28d78fad-5a12-11d1-ae5b-0000f803a8c2}

一切都很好,但我输入的是人类可读的打印机名称,如Devices and Printers. 这些SetupDi*函数似乎没有使用它,它们只在设备实例 IDs上运行。所以现在的问题是如何从将传递给的打印机名称中获取设备实例 IDOpenPrinter

不难看出上面的 GUID 部分是GUID_DEVINTERFACE_USBPRINT, 并且\\?\usb是固定的,所以我真正感兴趣的唯一一点是vid_0a5f&pid_0027#46a072900549#. 这个路径我可以很容易地在打印机属性对话框中手动查找:

转到设备和打印机
右键单击​​打印机
属性
切换到硬件选项卡
选择打印设备,例如 ZDesigner LP2844-Z
属性
切换到详细信息选项卡
从下拉列表中选择“父级”。

但我不知道如何以编程方式执行此操作,提供的唯一内容是在“设备和打印机”面板中看到的打印机名称。


PS 1:我对使用打开打印机OpenPrinter然后使用WritePrinter/不感兴趣ReadPrinter。这已经完成,工作正常,但现在目标不同了。

PS 2:我可以用一种更简单的方法将可读的打印机名称转换为可以传递给CreateFile.

PS 3:这个问题,我已经发布了答案,与我最终想要做的事情非常相关。

PS 4:反过来也可以:如果可以从SP_DEVINFO_DATA结构中获得可读的名称,那也将是答案,尽管不太方便。

4

5 回答 5

4

下面是我终于能够想出的。

请确认这SYSTEM\CurrentControlSet\Control\Print\Printers\{0}\PNPData是受支持的路径,并且不只是在当前实现中恰好存在,可能会在未来发生变化。

结构对齐有一点问题,为此我发布了一个单独的问题

public static class UsbPrinterResolver
{

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    private struct SP_DEVINFO_DATA
    {
        public uint cbSize;
        public Guid ClassGuid;
        public uint DevInst;
        public IntPtr Reserved;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    private struct SP_DEVICE_INTERFACE_DATA
    {
        public uint cbSize;
        public Guid InterfaceClassGuid;
        public uint Flags;
        public IntPtr Reserved;
    }


    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 1)]
    private struct SP_DEVICE_INTERFACE_DETAIL_DATA  // Only used for Marshal.SizeOf. NOT!
    {
        public uint cbSize;
        public char DevicePath;
    }


    [DllImport("cfgmgr32.dll", CharSet = CharSet.Auto, SetLastError = false, ExactSpelling = true)]
    private static extern uint CM_Get_Parent(out uint pdnDevInst, uint dnDevInst, uint ulFlags);

    [DllImport("cfgmgr32.dll", CharSet = CharSet.Auto, SetLastError = false)]
    private static extern uint CM_Get_Device_ID(uint dnDevInst, string Buffer, uint BufferLen, uint ulFlags);

    [DllImport("cfgmgr32.dll", CharSet = CharSet.Auto, SetLastError = false, ExactSpelling = true)]
    private static extern uint CM_Get_Device_ID_Size(out uint pulLen, uint dnDevInst, uint ulFlags);

    [DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr SetupDiGetClassDevs([In(), MarshalAs(UnmanagedType.LPStruct)] System.Guid ClassGuid, string Enumerator, IntPtr hwndParent, uint Flags);

    [DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern int SetupDiEnumDeviceInfo(IntPtr DeviceInfoSet, uint MemberIndex, ref SP_DEVINFO_DATA DeviceInfoData);

    [DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern int SetupDiEnumDeviceInterfaces(IntPtr DeviceInfoSet, [In()] ref SP_DEVINFO_DATA DeviceInfoData, [In(), MarshalAs(UnmanagedType.LPStruct)] System.Guid InterfaceClassGuid, uint MemberIndex, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData);

    [DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern int SetupDiGetDeviceInterfaceDetail(IntPtr DeviceInfoSet, [In()] ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData, IntPtr DeviceInterfaceDetailData, uint DeviceInterfaceDetailDataSize, out uint RequiredSize, IntPtr DeviceInfoData);

    [DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
    private static extern int SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
    private static extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess, int dwShareMode, IntPtr lpSecurityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile);

    private const uint DIGCF_PRESENT = 0x00000002U;
    private const uint DIGCF_DEVICEINTERFACE = 0x00000010U;
    private const int ERROR_INSUFFICIENT_BUFFER = 122;
    private const uint CR_SUCCESS = 0;

    private const int FILE_SHARE_READ = 1;
    private const int FILE_SHARE_WRITE = 2;
    private const uint GENERIC_READ = 0x80000000;
    private const uint GENERIC_WRITE = 0x40000000;
    private const int OPEN_EXISTING = 3;

    private static readonly Guid GUID_PRINTER_INSTALL_CLASS = new Guid(0x4d36e979, 0xe325, 0x11ce, 0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18);
    private static readonly Guid GUID_DEVINTERFACE_USBPRINT = new Guid(0x28d78fad, 0x5a12, 0x11D1, 0xae, 0x5b, 0x00, 0x00, 0xf8, 0x03, 0xa8, 0xc2);
    private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);


    private static string GetPrinterRegistryInstanceID(string PrinterName) {
        if (string.IsNullOrEmpty(PrinterName)) throw new ArgumentNullException("PrinterName");

        const string key_template = @"SYSTEM\CurrentControlSet\Control\Print\Printers\{0}\PNPData";

        using (var hk = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(
                            string.Format(key_template, PrinterName),
                            Microsoft.Win32.RegistryKeyPermissionCheck.Default,
                            System.Security.AccessControl.RegistryRights.QueryValues
                        )
               )
        {

            if (hk == null) throw new ArgumentOutOfRangeException("PrinterName", "This printer does not have PnP data.");

            return (string)hk.GetValue("DeviceInstanceId");
        }
    }

    private static string GetPrinterParentDeviceId(string RegistryInstanceID) {
        if (string.IsNullOrEmpty(RegistryInstanceID)) throw new ArgumentNullException("RegistryInstanceID");

        IntPtr hdi = SetupDiGetClassDevs(GUID_PRINTER_INSTALL_CLASS, RegistryInstanceID, IntPtr.Zero, DIGCF_PRESENT);
        if (hdi.Equals(INVALID_HANDLE_VALUE)) throw new System.ComponentModel.Win32Exception();

        try
        {
            SP_DEVINFO_DATA printer_data = new SP_DEVINFO_DATA();
            printer_data.cbSize = (uint)Marshal.SizeOf(typeof(SP_DEVINFO_DATA));

            if (SetupDiEnumDeviceInfo(hdi, 0, ref printer_data) == 0) throw new System.ComponentModel.Win32Exception();   // Only one device in the set

            uint cmret = 0;

            uint parent_devinst = 0;
            cmret = CM_Get_Parent(out parent_devinst, printer_data.DevInst, 0);
            if (cmret != CR_SUCCESS) throw new Exception(string.Format("Failed to get parent of the device '{0}'. Error code: 0x{1:X8}", RegistryInstanceID, cmret));


            uint parent_device_id_size = 0;
            cmret = CM_Get_Device_ID_Size(out parent_device_id_size, parent_devinst, 0);
            if (cmret != CR_SUCCESS) throw new Exception(string.Format("Failed to get size of the device ID of the parent of the device '{0}'. Error code: 0x{1:X8}", RegistryInstanceID, cmret));

            parent_device_id_size++;  // To include the null character

            string parent_device_id = new string('\0', (int)parent_device_id_size);
            cmret = CM_Get_Device_ID(parent_devinst, parent_device_id, parent_device_id_size, 0);
            if (cmret != CR_SUCCESS) throw new Exception(string.Format("Failed to get device ID of the parent of the device '{0}'. Error code: 0x{1:X8}", RegistryInstanceID, cmret));

            return parent_device_id;
        }
        finally
        {
            SetupDiDestroyDeviceInfoList(hdi);
        }
    }

    private static string GetUSBInterfacePath(string SystemDeviceInstanceID) {
        if (string.IsNullOrEmpty(SystemDeviceInstanceID)) throw new ArgumentNullException("SystemDeviceInstanceID");

        IntPtr hdi = SetupDiGetClassDevs(GUID_DEVINTERFACE_USBPRINT, SystemDeviceInstanceID, IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
        if (hdi.Equals(INVALID_HANDLE_VALUE)) throw new System.ComponentModel.Win32Exception();

        try
        {
            SP_DEVINFO_DATA device_data = new SP_DEVINFO_DATA();
            device_data.cbSize = (uint)Marshal.SizeOf(typeof(SP_DEVINFO_DATA));

            if (SetupDiEnumDeviceInfo(hdi, 0, ref device_data) == 0) throw new System.ComponentModel.Win32Exception();  // Only one device in the set

            SP_DEVICE_INTERFACE_DATA interface_data = new SP_DEVICE_INTERFACE_DATA();
            interface_data.cbSize = (uint)Marshal.SizeOf(typeof(SP_DEVICE_INTERFACE_DATA));

            if (SetupDiEnumDeviceInterfaces(hdi, ref device_data, GUID_DEVINTERFACE_USBPRINT, 0, ref interface_data) == 0) throw new System.ComponentModel.Win32Exception();   // Only one interface in the set


            // Get required buffer size
            uint required_size = 0;
            SetupDiGetDeviceInterfaceDetail(hdi, ref interface_data, IntPtr.Zero, 0, out required_size, IntPtr.Zero);

            int last_error_code = Marshal.GetLastWin32Error();
            if (last_error_code != ERROR_INSUFFICIENT_BUFFER) throw new System.ComponentModel.Win32Exception(last_error_code);

            IntPtr interface_detail_data = Marshal.AllocCoTaskMem((int)required_size);

            try
            {

                // FIXME, don't know how to calculate the size.
                // See https://stackoverflow.com/questions/10728644/properly-declare-sp-device-interface-detail-data-for-pinvoke

                switch (IntPtr.Size)
                {
                    case 4:
                        Marshal.WriteInt32(interface_detail_data, 4 + Marshal.SystemDefaultCharSize);
                        break;
                    case 8:
                        Marshal.WriteInt32(interface_detail_data, 8);
                        break;

                    default:
                        throw new NotSupportedException("Architecture not supported.");
                }

                if (SetupDiGetDeviceInterfaceDetail(hdi, ref interface_data, interface_detail_data, required_size, out required_size, IntPtr.Zero) == 0) throw new System.ComponentModel.Win32Exception();

                // TODO: When upgrading to .NET 4, replace that with IntPtr.Add
                return Marshal.PtrToStringAuto(new IntPtr(interface_detail_data.ToInt64() + Marshal.OffsetOf(typeof(SP_DEVICE_INTERFACE_DETAIL_DATA), "DevicePath").ToInt64()));

            }
            finally
            {
                Marshal.FreeCoTaskMem(interface_detail_data);
            }
        }
        finally
        {
            SetupDiDestroyDeviceInfoList(hdi);
        }
    }


    public static string GetUSBPath(string PrinterName) {
        return GetUSBInterfacePath(GetPrinterParentDeviceId(GetPrinterRegistryInstanceID(PrinterName)));
    }

    public static Microsoft.Win32.SafeHandles.SafeFileHandle OpenUSBPrinter(string PrinterName) {
        return new Microsoft.Win32.SafeHandles.SafeFileHandle(CreateFile(GetUSBPath(PrinterName), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero), true);
    }

}

用法:

using (var sh = UsbPrinterResolver.OpenUSBPrinter("Zebra Large"))
{
    using (var f = new System.IO.FileStream(sh, System.IO.FileAccess.ReadWrite))
    {
        // Read from and write to the stream f
    }
}
于 2012-05-23T22:30:25.270 回答
4

试试这个(Python代码):

import _winreg

HKLM = _winreg.HKEY_LOCAL_MACHINE

#------------------------------------------------------------------------------
def getDevicePath(printerName):
    key = _winreg.OpenKey(HKLM,
        r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Print\Printers\%s" \
        % printerName)

    value =_winreg.QueryValueEx(key, "Port")[0]
    assert value.startswith("USB"), \
           "Port does not start with 'USB': %s" % value

    printerPortNumber = int(value.replace(u"USB", u""))

    key = _winreg.OpenKey(HKLM,
            r"SYSTEM\CurrentControlSet\Control\DeviceClasses" \
            r"\{28d78fad-5a12-11d1-ae5b-0000f803a8c2}")

    idx = 0
    devicePath = None
    while True:
        try:
            subKeyName = _winreg.EnumKey(key, idx)
            subKey = _winreg.OpenKey(key, subKeyName)

            try:
                subSubKey = _winreg.OpenKey(subKey, r"#\Device Parameters")
                baseName = _winreg.QueryValueEx(subSubKey, "Base Name")[0]
                portNumber = _winreg.QueryValueEx(subSubKey, "Port Number")[0]
                if baseName == "USB" and portNumber == printerPortNumber:
                    devicePath = subKeyName.replace("##?#USB", r"\\?\usb")
                    break

            except WindowsError:
                continue

            finally:
                idx += 1

        except WindowsError:
            break

    return devicePath
于 2012-11-23T17:19:54.347 回答
1

试试这个......让我知道这是否有帮助......

    static void Main(string[] args)
    {
        ManagementObjectSearcher s = new ManagementObjectSearcher(@"Select * From Win32_PnPEntity");
        foreach (ManagementObject device in s.Get())
        {
            // Try Name, Caption and/or Description (they seem to be same most of the time).
            string Name = (string)device.GetPropertyValue("Name");

            // >>>>>>>>>>>>>>>>>>>> Query String ...
            if (Name == "O2Micro Integrated MMC/SD controller")
            {
                /*
                 class Win32_PnPEntity : CIM_LogicalDevice
                {
                  uint16   Availability;
                  string   Caption;
                  string   ClassGuid;
                  string   CompatibleID[];
                  uint32   ConfigManagerErrorCode;
                  boolean  ConfigManagerUserConfig;
                  string   CreationClassName;
                  string   Description;
                  string   DeviceID;
                  boolean  ErrorCleared;
                  string   ErrorDescription;
                  string   HardwareID[];
                  datetime InstallDate;
                  uint32   LastErrorCode;
                  string   Manufacturer;
                  string   Name;
                  string   PNPDeviceID;
                  uint16   PowerManagementCapabilities[];
                  boolean  PowerManagementSupported;
                  string   Service;
                  string   Status;
                  uint16   StatusInfo;
                  string   SystemCreationClassName;
                  string   SystemName;
                };
                */

                try
                {
                    Console.WriteLine("Name         : {0}", Name);
                    Console.WriteLine("DeviceID     : {0}", device.GetPropertyValue("DeviceID"));
                    Console.WriteLine("PNPDeviceID  : {0}", device.GetPropertyValue("PNPDeviceID"));
                    Console.WriteLine("ClassGuid    : {0}", device.GetPropertyValue("ClassGuid"));
                    Console.WriteLine("HardwareID   :\n{0}", JoinStrings(device.GetPropertyValue("HardwareID") as string[]));
                    Console.WriteLine("CompatibleID :\n{0}", JoinStrings(device.GetPropertyValue("CompatibleID") as string[]));
                }
                catch (Exception e)
                {
                    Console.WriteLine("ERROR: {0}", e.Message);
                }
            }
        }
    }

    static string JoinStrings(string[] sarray)
    {
        StringBuilder b = new StringBuilder();
        if (sarray != null)
        {
            foreach (string s in sarray)
                b.Append("        '" + s + "'\n");
        }
        return b.ToString();
    }

没有要测试的 USB 打印机,但这提供了您正在寻找的信息(包括 USB 设备)...

Description  : O2Micro Integrated MMC/SD controller
DeviceID     : PCI\VEN_1217&DEV_8221&SUBSYS_04931028&REV_05\4&26B31A7F&0&00E5
PNPDeviceID  : PCI\VEN_1217&DEV_8221&SUBSYS_04931028&REV_05\4&26B31A7F&0&00E5
ClassGuid    : {4d36e97b-e325-11ce-bfc1-08002be10318}
HardwareID   :
        'PCI\VEN_1217&DEV_8221&SUBSYS_04931028&REV_05'
        'PCI\VEN_1217&DEV_8221&SUBSYS_04931028'
        'PCI\VEN_1217&DEV_8221&CC_080501'
        'PCI\VEN_1217&DEV_8221&CC_0805'

CompatibleID :         'PCI\VEN_1217&DEV_8221&REV_05'
        'PCI\VEN_1217&DEV_8221'
        'PCI\VEN_1217&CC_080501'
        'PCI\VEN_1217&CC_0805'
        'PCI\VEN_1217'
        'PCI\CC_080501'
        'PCI\CC_0805'

此外,对于 URI,请将您要构建的 URI 中的 '\' 更改为 '#'。

所以 ..

usb\vid_0a5f&pid_0027\46a072900549\{28d78fad-5a12-11d1-ae5b-0000f803a8c2}

变成

usb#vid_0a5f&pid_0027#46a072900549#{28d78fad-5a12-11d1-ae5b-0000f803a8c2}

====

正如 GSerg 指出的那样,Win32_Printer 类有助于上述代码,但不提供设备 ID。

但是,如果我使用 Win32_Printer 类并打印出“PortName”属性,那么对于我安装的打印机,我可以使用 CreateFile() 并打开设备的端口/文件名。

例如:

Name         : Microsoft XPS Document Writer
Description  :
DeviceID     : Microsoft XPS Document Writer
PNPDeviceID  :
PortName  : XPSPort:


Name         : Fax
Description  :
DeviceID     : Fax
PNPDeviceID  :
PortName  : SHRFAX:

在这里,写入“XPSPORT:”或“SHRFAX:”会将数据发送到打印机。这对您的 USB 打印机有什么作用?

于 2012-05-22T22:54:26.063 回答
1

使用WinObjMicrosoft 获取特定设备名称。http://technet.microsoft.com/en-us/sysinternals/bb896657.aspx。这将很快为您提供正确的设备名称,用于CreateFile直接写入您的 USB 打印机或直接写入带有老式并行端口输出的 USB 打印机适配器,用于自定义电路!

要打开与特定打印机关联的端口,您可能需要使用ntcreatefile. 使用该EnumPrinters函数返回printer_info_2包含每台打印机的端口名称的结构。然后可以使用ntcreatefile(NT 的内部版本CreateFile)打开此端口名称,此处解释:http: //msdn.microsoft.com/en-us/library/bb432380 (v=vs.85).aspx

为什么这行得通?Windows NT 文件/设备名称中有三个名称空间级别,从中检索的端口名称EnumPrinters只能打开,ntcreatefile因为它仅在 NT 名称空间中。某些设备可能有一个等效的 win32 命名空间链接,以及将它们与打印机名称匹配的迂回方式,但这很困难,因为其他人在之前的答案中已经显示了这一点。

查看工具中的Global??文件夹WinObj以显示您机器上 win32 命名空间和 NT 命名空间之间的符号链接。老式的COM1, COM2,LPT1等设备名称也只是简单的 windows NT 名称空间符号链接。谷歌“win32 nt 命名空间”以获得更详细的解释。(抱歉,作为新用户,我只能发布 2 个超链接。)

于 2013-01-27T19:33:02.630 回答
0

我不是一个真正的 C++ 人,但我真的不认为尝试从名称生成设备 ID 是要走的路。但是,您可以

  1. 使用EnumPrinters并读取PRINTER_INFO_2 structure驱动程序名称,然后从注册表中读取驱动程序详细信息,如本例所示

  2. 通过可能从PRINTER INFO 结构中找出打印机详细信息并正确构造它来为自己生成名称。有关详细信息,请参阅http://msdn.microsoft.com/en-us/library/windows/hardware/ff553356(v=vs.85).aspx

编辑:您实际上可以从注册表中获取打印机的名称 + 设备实例 ID:

HKLM\System\CurrentControlSet\Control\Print\Printers\

于 2012-05-26T22:04:27.087 回答