Вопрос: Определение того, какое имя принтера соответствует идентификатору устройства


Моя цель - открыть принтер, подключенный через USB, используя CreateFile (а затем выдать некоторые WriteFileс и ReadFileс).

Если бы принтер был LPT, я бы просто сделал CreateFile("LPT1:", ...), Но для USB-принтеров существует специальный путь к устройству, который необходимо передать CreateFile чтобы открыть этот принтер.

Этот путь устройства, как я смог найти , извлекается через SetupDiGetClassDevs -> SetupDiEnumDeviceInterfaces -> SetupDiGetDeviceInterfaceDetail -> DevicePath член  и выглядит так:

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

Все в порядке, но то, что у меня есть в качестве входных данных, - это имя для чтения с помощью человека, как видно из Devices and Printers, SetupDi* функции, похоже, не используются, они работают только идентификаторы экземпляров устройства , Итак, вопрос сейчас как получить идентификатор экземпляра устройства из имени принтера, который будет передан OpenPrinter,

Нетрудно заметить, что часть GUID, указанная выше, является GUID_DEVINTERFACE_USBPRINT, а также \\?\usb фиксируется, поэтому единственный бит, который мне действительно интересен, - это vid_0a5f&pid_0027#46a072900549#, Этот путь я могу легко найти вручную в диалоговом окне свойств принтера:

Перейти к Устройствам и принтерам
  Щелкните правой кнопкой мыши принтер
  свойства
  Переключение на вкладку «Оборудование»
  Выберите печатающее устройство, например ZDesigner LP2844-Z
  свойства
  Перейдите на вкладку «Подробности»
  В раскрывающемся списке выберите «Родитель».

Но я понятия не имею, как это сделать программно, при условии, что единственное, что указано, - это имя принтера, как показано на панели «Устройство и принтеры».


Постскриптум 1: Я не заинтересован в открытии принтера OpenPrinter и затем используя WritePrinter / ReadPrinter, Это было сделано, отлично работает, но теперь цель другая.

Постскриптум 2: Я буду в порядке с более простым способом конвертировать читаемое имя принтера в то, что может быть передано CreateFile,

Постскриптум 3: Этот вопрос , на который я опубликовал ответ , очень связано с тем, что я в конечном итоге хочу сделать.

Постскриптум 4: Точно так же отлично: Если можно получить читаемое имя из SP_DEVINFO_DATA структура, которая также будет ответом , хотя и менее удобный.


9


источник


Ответы:


Ниже приводится то, что я, наконец, смог придумать.

Пожалуйста, подтвердите, что 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
    }
}

3



Попробуйте это (код 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

3



Попробуйте это ... дайте мне знать, если это поможет ...

    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 помогает с приведенным выше кодом, но не содержит идентификатор устройства.

Но если я использую класс 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-принтера?


1



использование WinObj от Microsoft, чтобы получить конкретное имя устройства. 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. Google «win32 nt namespace» для более подробного объяснения. (Извините, но как новый пользователь, я могу опубликовать только 2 гиперссылки.)


1



Я на самом деле не парень из c ++, но я действительно не думаю, что попытка сгенерировать идентификатор устройства из названия - это путь. Однако вы можете

  1. использование EnumPrinters и читать PRINTER_INFO_2 structure для получения имени драйвера, а затем прочтите данные о драйвере из реестра, как в этот пример ,

  2. Создайте имя для себя, узнав детали принтера, возможно, из Структуры PRINTER INFO  и построить его правильно. Видеть http://msdn.microsoft.com/en-us/library/windows/hardware/ff553356(v=vs.85).aspx  для деталей.

РЕДАКТИРОВАТЬ:  Фактически вы можете получить имена и идентификаторы экземпляров экземпляров принтеров из реестра:

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


0