-
-
Notifications
You must be signed in to change notification settings - Fork 182
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Native USB Printing support windows #234
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
using System.IO; | ||
|
||
namespace ESCPOS_NET.Printers | ||
{ | ||
public class USBPrinter : BasePrinter | ||
{ | ||
private readonly FileStream _rfile; | ||
private readonly FileStream _wfile; | ||
public USBPrinter(string usbPath) | ||
: base() | ||
{ | ||
//keeping separate file streams performs better | ||
//while using 1 file stream printers were having intermittent issues while printing | ||
//your milege may vary | ||
_rfile = File.Open(usbPath, FileMode.Open, FileAccess.Read); | ||
_wfile = File.Open(usbPath, FileMode.Open, FileAccess.Write); | ||
Writer = new BinaryWriter(_wfile); | ||
Reader = new BinaryReader(_rfile); | ||
} | ||
|
||
~USBPrinter() | ||
{ | ||
Dispose(false); | ||
} | ||
|
||
protected override void OverridableDispose() | ||
{ | ||
_rfile?.Close(); | ||
_rfile?.Dispose(); | ||
_wfile?.Close(); | ||
_wfile?.Dispose(); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
| ||
namespace ESCPOS_NET | ||
{ | ||
public class DeviceDetails | ||
{ | ||
public string DisplayName { get; set; } | ||
/// <summary> | ||
/// DEVPKEY_Device_BusReportedDeviceDesc <see href="https://github.com/tpn/winsdk-10/blob/master/Include/10.0.16299.0/shared/devpkey.h">see reference</see> | ||
/// </summary> | ||
public string BusName { get; set; } | ||
public string SerialNum { get; set; } | ||
public string DeviceDescription { get; set; } | ||
public string DevicePath { get; set; } | ||
public string Manufacturer { get; set; } | ||
public ushort VID { get; set; } | ||
public ushort PID { get; set; } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.ComponentModel; | ||
using System.Globalization; | ||
using System.Runtime.InteropServices; | ||
using System.Text; | ||
using System.Text.RegularExpressions; | ||
|
||
namespace ESCPOS_NET | ||
{ | ||
public class DeviceFinder | ||
{ | ||
#region Win API | ||
private struct SP_DEVICE_INTERFACE_DATA | ||
{ | ||
internal int cbSize; | ||
|
||
internal Guid InterfaceClassGuid; | ||
|
||
internal int Flags; | ||
|
||
internal IntPtr Reserved; | ||
} | ||
private struct SP_DEVINFO_DATA | ||
{ | ||
internal int cbSize; | ||
|
||
internal Guid ClassGuid; | ||
|
||
internal int DevInst; | ||
|
||
internal IntPtr Reserved; | ||
} | ||
[StructLayout(LayoutKind.Sequential, Pack = 1)] | ||
private struct DEVPROPKEY | ||
{ | ||
public Guid fmtid; | ||
public uint pid; | ||
} | ||
private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); | ||
[DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure about this DLL Imports... I will defer the final decision to @lukevp , but if he is ok with these import, then the PR sounds great. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hey all, sorry I missed this. How does this work outside of Windows? This seems like old school COM stuff that's windows / full framework only (not .NET core / .NET standard) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is like USB1, USB2.... Planning to mergue it ??? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @lukevp I don't think those imports will work outside windows. From experience with Mono back in the day one would need to have corresponding .so files and etc. Nothing straight forward. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But, it could be a Windows-only feature, since it seems that only Windows has this issue with USB. Thoughts? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi all, |
||
private static extern IntPtr SetupDiGetClassDevs(ref Guid ClassGuid, IntPtr Enumerator, IntPtr hwndParent, int Flags); | ||
[DllImport("setupapi.dll", SetLastError = true)] | ||
private static extern bool SetupDiEnumDeviceInterfaces(IntPtr DeviceInfoSet, IntPtr DeviceInfoData, ref Guid InterfaceClassGuid, int MemberIndex, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData); | ||
[DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)] | ||
private static extern bool SetupDiGetDeviceInterfaceDetail(IntPtr DeviceInfoSet, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData, IntPtr DeviceInterfaceDetailData, int DeviceInterfaceDetailDataSize, ref int RequiredSize, IntPtr DeviceInfoData); | ||
[DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)] | ||
private static extern bool SetupDiGetDeviceInterfaceDetail(IntPtr DeviceInfoSet, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData, IntPtr DeviceInterfaceDetailData, int DeviceInterfaceDetailDataSize, ref int RequiredSize, ref SP_DEVINFO_DATA DeviceInfoData); | ||
[DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)] | ||
private static extern bool SetupDiGetDeviceProperty(IntPtr deviceInfoSet, ref SP_DEVINFO_DATA DeviceInfoData, ref DEVPROPKEY propertyKey, out uint propertyType, IntPtr propertyBuffer, uint propertyBufferSize, out uint requiredSize, uint flags); | ||
[DllImport("setupapi.dll", SetLastError = true)] | ||
private static extern int SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet); | ||
private static void DEFINE_DEVPROPKEY(out DEVPROPKEY key, uint l, ushort w1, ushort w2, byte b1, byte b2, byte b3, byte b4, byte b5, byte b6, byte b7, byte b8, uint pid) | ||
{ | ||
key.fmtid = new Guid(l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8); | ||
key.pid = pid; | ||
} | ||
#endregion | ||
#region Device Methods | ||
public static List<DeviceDetails> GetDevices() | ||
{ | ||
//https://learn.microsoft.com/en-us/windows-hardware/drivers/install/guid-devinterface-usb-device | ||
//USB devices: a5dcbf10-6530-11d2-901f-00c04fb951ed | ||
//Bluetooth devices: 00f40965-e89d-4487-9890-87c3abb211f4 | ||
var devices = GetDevicesbyClassID("a5dcbf10-6530-11d2-901f-00c04fb951ed"); | ||
return devices; | ||
} | ||
private static List<DeviceDetails> GetDevicesbyClassID(string classguid) | ||
{ | ||
IntPtr intPtr = IntPtr.Zero; | ||
var devices = new List<DeviceDetails>(); | ||
try | ||
{ | ||
Guid guid = new(classguid); | ||
intPtr = SetupDiGetClassDevs(ref guid, IntPtr.Zero, IntPtr.Zero, 18); | ||
if (intPtr == INVALID_HANDLE_VALUE) | ||
{ | ||
Win32Exception("Failed to enumerate devices."); | ||
} | ||
|
||
int num = 0; | ||
while (true) | ||
{ | ||
SP_DEVICE_INTERFACE_DATA DeviceInterfaceData = default; | ||
DeviceInterfaceData.cbSize = Marshal.SizeOf((object)DeviceInterfaceData); | ||
if (!SetupDiEnumDeviceInterfaces(intPtr, IntPtr.Zero, ref guid, num, ref DeviceInterfaceData)) | ||
{ | ||
break; | ||
} | ||
|
||
int RequiredSize = 0; | ||
if (!SetupDiGetDeviceInterfaceDetail(intPtr, ref DeviceInterfaceData, IntPtr.Zero, 0, ref RequiredSize, IntPtr.Zero) && Marshal.GetLastWin32Error() != 122) | ||
{ | ||
Win32Exception("Failed to get interface details buffer size."); | ||
} | ||
|
||
IntPtr intPtr2 = IntPtr.Zero; | ||
try | ||
{ | ||
intPtr2 = Marshal.AllocHGlobal(RequiredSize); | ||
Marshal.WriteInt32(intPtr2, (IntPtr.Size == 4) ? (4 + Marshal.SystemDefaultCharSize) : 8); | ||
SP_DEVINFO_DATA DeviceInfoData = default; | ||
DeviceInfoData.cbSize = Marshal.SizeOf((object)DeviceInfoData); | ||
if (!SetupDiGetDeviceInterfaceDetail(intPtr, ref DeviceInterfaceData, intPtr2, RequiredSize, ref RequiredSize, ref DeviceInfoData)) | ||
{ | ||
Win32Exception("Failed to get device interface details."); | ||
} | ||
string path = Marshal.PtrToStringUni(new IntPtr(intPtr2.ToInt64() + 4)); | ||
|
||
DeviceDetails deviceDetails = GetDeviceDetails(path, intPtr, DeviceInfoData); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For some devises (non printers) I got Maybe it will be a good idea to just to skip the problematic ones (try-catch-continue?) and go on instead of throwing an exception and failing to enumerate any of them. |
||
devices.Add(deviceDetails); | ||
} | ||
finally | ||
{ | ||
if (intPtr2 != IntPtr.Zero) | ||
{ | ||
Marshal.FreeHGlobal(intPtr2); | ||
intPtr2 = IntPtr.Zero; | ||
} | ||
} | ||
num++; | ||
} | ||
if (Marshal.GetLastWin32Error() != 259) | ||
{ | ||
Win32Exception("Failed to get device interface."); | ||
} | ||
} | ||
finally | ||
{ | ||
if (intPtr != IntPtr.Zero && intPtr != INVALID_HANDLE_VALUE) | ||
{ | ||
SetupDiDestroyDeviceInfoList(intPtr); | ||
} | ||
} | ||
return devices; | ||
} | ||
private static DeviceDetails GetDeviceDetails(string devicePath, IntPtr deviceInfoSet, SP_DEVINFO_DATA deviceInfoData) | ||
{ | ||
DeviceDetails result = new DeviceDetails | ||
{ | ||
DevicePath = devicePath | ||
}; | ||
if (!string.IsNullOrWhiteSpace(devicePath) && devicePath.Contains("#")) | ||
{ | ||
var spserial = devicePath.Split('#'); | ||
result.SerialNum = spserial[spserial.Length - 2]; | ||
//last in array is guid and last second is the serial number | ||
//serial number might not be the actual serial number for the device it may be system generated | ||
} | ||
DEFINE_DEVPROPKEY(out DEVPROPKEY key, 0x540b947e, 0x8b40, 0x45bc, 0xa8, 0xa2, 0x6a, 0x0b, 0x89, 0x4c, 0xbd, 0xa2, 4); | ||
result.BusName = GetPropertyForDevice(deviceInfoSet, deviceInfoData, key)[0]; | ||
DEFINE_DEVPROPKEY(out key, 0xb725f130, 0x47ef, 0x101a, 0xa5, 0xf1, 0x02, 0x60, 0x8c, 0x9e, 0xeb, 0xac, 10); | ||
result.DisplayName = GetPropertyForDevice(deviceInfoSet, deviceInfoData, key)[0]; | ||
DEFINE_DEVPROPKEY(out key, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 2); | ||
result.DeviceDescription = GetPropertyForDevice(deviceInfoSet, deviceInfoData, key)[0]; | ||
DEFINE_DEVPROPKEY(out key, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 13); | ||
result.Manufacturer = GetPropertyForDevice(deviceInfoSet, deviceInfoData, key)[0]; | ||
DEFINE_DEVPROPKEY(out key, 0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57, 256); | ||
string[] multiStringProperty = GetPropertyForDevice(deviceInfoSet, deviceInfoData, key); | ||
Regex regex = new Regex("VID_([0-9A-F]{4})&PID_([0-9A-F]{4})", RegexOptions.IgnoreCase); | ||
bool flag = false; | ||
string[] array = multiStringProperty; | ||
foreach (string input in array) | ||
{ | ||
Match match = regex.Match(input); | ||
if (match.Success) | ||
{ | ||
result.VID = ushort.Parse(match.Groups[1].Value, NumberStyles.AllowHexSpecifier); | ||
result.PID = ushort.Parse(match.Groups[2].Value, NumberStyles.AllowHexSpecifier); | ||
flag = true; | ||
break; | ||
} | ||
} | ||
|
||
if (!flag) | ||
{ | ||
Win32Exception("Failed to find VID and PID for USB device. No hardware ID could be parsed."); | ||
} | ||
|
||
return result; | ||
} | ||
private static string[] GetPropertyForDevice(IntPtr deviceInfoSet, SP_DEVINFO_DATA deviceInfoData, DEVPROPKEY key) | ||
{ | ||
IntPtr buffer = IntPtr.Zero; | ||
try | ||
{ | ||
uint buflen = 512; | ||
buffer = Marshal.AllocHGlobal((int)buflen); | ||
if (!SetupDiGetDeviceProperty(deviceInfoSet, ref deviceInfoData, ref key, out uint proptype, buffer, buflen, out uint outsize, 0)) | ||
{ | ||
Win32Exception("Failed to get property for device"); | ||
} | ||
byte[] lbuffer = new byte[outsize]; | ||
Marshal.Copy(buffer, lbuffer, 0, (int)outsize); | ||
var val = Encoding.Unicode.GetString(lbuffer); | ||
var aval = val.Split('\0'); | ||
return aval; | ||
} | ||
finally | ||
{ | ||
if (buffer != IntPtr.Zero) | ||
Marshal.FreeHGlobal(buffer); | ||
} | ||
} | ||
private static void Win32Exception(string message) | ||
{ | ||
throw new Exception(message, new Win32Exception(Marshal.GetLastWin32Error())); | ||
} | ||
#endregion | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I got an error and cannot findout how to resolve, have you got this error too?
FileStream was asked to open a device that was not a file. For support for devices like 'com1:' or 'lpt1:', call CreateFile, then use the FileStream constructors that take an OS handle as an IntPtr.