Skip to content
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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 43 additions & 3 deletions ESCPOS_NET.ConsoleTest/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Runtime.InteropServices;
using Microsoft.Extensions.Logging;
using System.Threading;
using ESCPOS_NET.Printers;

namespace ESCPOS_NET.ConsoleTest
{
Expand Down Expand Up @@ -32,13 +33,15 @@ static void Main(string[] args)
Console.WriteLine("1 ) Test Serial Port");
Console.WriteLine("2 ) Test Network Printer");
Console.WriteLine("3 ) Test Samba-Shared Printer");
Console.WriteLine("4 ) Test USB Printer");
Console.Write("Choice: ");
string comPort = "";
string ip;
string networkPort;
string smbPath;
string usbPort = string.Empty;
response = Console.ReadLine();
var valid = new List<string> { "1", "2", "3" };
var valid = new List<string> { "1", "2", "3", "4" };
if (!valid.Contains(response))
{
response = "1";
Expand All @@ -58,8 +61,8 @@ static void Main(string[] args)
{
comPort = "COM5";
}
}
Console.Write("Baud Rate (enter for default 115200): ");
}
Console.Write("Baud Rate (enter for default 115200): ");
if (!int.TryParse(Console.ReadLine(), out var baudRate))
{
baudRate = 115200;
Expand Down Expand Up @@ -100,6 +103,43 @@ static void Main(string[] args)

printer = new SambaPrinter(tempFileBasePath: @"C:\Temp", filePath: smbPath);
}
else if (choice == 4)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var usboptions = DeviceFinder.GetDevices();//gets the usb devices connected to the pc
if (usboptions.Count > 0)
{
int i = 0;
int num = 1;
while (i < usboptions.Count)
{

Console.WriteLine($"{i + num}. Name: {usboptions[i].BusName} S/N: {usboptions[i].SerialNum}");
i++;
//serial number and name for printer. Name reported might just be USB Printing Support or something generic
//the property necessary for printing is Device Path this is just for UI
}
Console.Write("Choose Printer (eg. 1): ");
string c = Console.ReadLine();
if (int.TryParse(c, out int chosen) && chosen > 0)
{
usbPort = usboptions[chosen - 1].DevicePath;
}
}
printer = new USBPrinter(usbPort);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
Console.Write("File / usb path (eg. /dev/usb/lp0): ");
usbPort = Console.ReadLine();
if (string.IsNullOrWhiteSpace(usbPort))
{
comPort = "/dev/usb/lp0";
}
printer = new FilePrinter(filePath: usbPort, false);
}
}

bool monitor = false;
Thread.Sleep(500);
Expand Down
34 changes: 34 additions & 0 deletions ESCPOS_NET/Printers/USBPrinter.cs
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);

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.

_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();
}
}
}
18 changes: 18 additions & 0 deletions ESCPOS_NET/Utils/DeviceDetails.cs
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; }
}
}
211 changes: 211 additions & 0 deletions ESCPOS_NET/Utils/DeviceFinder.cs
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)]
Copy link
Collaborator

Choose a reason for hiding this comment

The 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.

Copy link
Owner

Choose a reason for hiding this comment

The 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)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is like USB1, USB2.... Planning to mergue it ???

Copy link
Collaborator

Choose a reason for hiding this comment

The 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.

Copy link
Collaborator

Choose a reason for hiding this comment

The 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?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi all,
I developed this feature as a windows only feature, because there wasn't any way of printing to USB printer on Windows without using virtual serial port drivers.
@lukevp As far as I understand Devicefinder.cs doesn't have any other dependency outside .netStandard but yes it only works on windows (finding devices connected).

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);
Copy link

@Fasjeit Fasjeit Oct 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some devises (non printers) I got System.ComponentModel.Win32Exception (1168) here - GetDeviceDetails(...).

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
}
}