Tuesday, June 08, 2010

USB communications with Zebra printers in C#

The best way to create your own application which uses a Zebra printer is to send ZPL commands directly to the printer. This is easy on RS232 or TCP/IP connections, but it's not so simple on USB, because Zebra does not offer (at least officially) an SDK which exposes APIs for USB.

I've found on the internet several articles about how to communicate with a Zebra printer through a USB connection. Most of them refer to an article by Microsoft's Knowledge Base which explains how to send raw data to the printer driver. Apart from performance issues, this is useful only if your communication is monodirectional (from the PC to the printer). It can be enough for most printers, but if you are using an RFID printer this is definitely not acceptable, as you probably need to read data from the printer in order to check the result of RFID operations for logging/printing purposes.

I searched the web for a C# based solution and I've found an excellent implementation of a communication framework using different media, like USB. It is part of a larger project hosted on SourceForge called ZBar (a collection of MS VS.NET components, controls and class libraries to aid development of printing functionality in .NET applications that make use of some barcode printers).

I took some classes from that project and with some modifications I got a nice USB stream for Zebra printers, which can be easily used to send and receive ZPL (or other scripting language) commands. The printer driver must be installed on the PC, but commands do not go through it.

Here are the files. Notice that they work both on x86 and x64 systems, but on x64 they must be compiled with the x86 option.

I collected all these files in this post because it was (at least for me) hard to find a solution for this issue, which is all in all very basic (just an I/O class for a printer!)



//-------------------------------
// ZebraUsbStream.cs
//-------------------------------
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Zebra.Printing;

namespace RfidPrinter
{
///


/// Stream subclass which incorporates low-level USB access to Zebra printers
///

public class ZebraUsbStream : Stream
{
UsbPrinterConnector usb;

public ZebraUsbStream(string port)
{
usb = new UsbPrinterConnector(port);
usb.IsConnected = true;
base.ReadTimeout = usb.ReadTimeout;
base.WriteTimeout = usb.WriteTimeout;
}

public ZebraUsbStream()
{
System.Collections.Specialized.NameValueCollection devs =
UsbPrinterConnector.EnumDevices(true, true, false);

if (devs.Count < 1)
throw new Exception("No Zebra printers found");

usb = new UsbPrinterConnector(devs[0].ToString());
usb.IsConnected = true;
}


public override bool CanRead
{
get { return true; }
}

public override bool CanSeek
{
get { return false; }
}

public override bool CanWrite
{
get { return true; }
}

public override bool CanTimeout
{
get { return true; }
}

public override void Flush()
{
;
}

public override long Length
{
get { throw new NotSupportedException(); }
}

public override long Position
{
get
{
throw new NotSupportedException();
}
set
{
throw new NotSupportedException();
}
}

public override int Read(byte[] buffer, int offset, int count)
{
return usb.Read(buffer, offset, count);
}

public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}

public override void SetLength(long value)
{
throw new NotSupportedException();
}

public override void Write(byte[] buffer, int offset, int count)
{
usb.Send(buffer, offset, count);
}

protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
usb.IsConnected = false;
}

public override void Close()
{
base.Close();
if (usb.IsConnected)
usb.IsConnected = false;
}

public override int ReadTimeout
{
get
{
return usb.ReadTimeout;
}
set
{
usb.ReadTimeout = value;
}
}

public override int WriteTimeout
{
get
{
return usb.WriteTimeout;
}
set
{
usb.WriteTimeout = value;
}
}
}
}


//-------------------------------
//FileIO.cs
//-------------------------------


#region License
/* ---------------------------------------------------------------------------
* Creative Commons License
* http://creativecommons.org/licenses/by/2.5/au/
*
* Attribution 2.5 Australia
*
* You are free:
*
* - to copy, distribute, display, and perform the work
* - to make derivative works
* - to make commercial use of the work
*
* Under the following conditions:
*
* Attribution: You must attribute the work in the manner specified by the
* author or licensor.
*
* For any reuse or distribution, you must make clear to others the license
* terms of this work. Any of these conditions can be waived if you get
* permission from the copyright holder. Your fair use and other rights
* are in no way affected by the above.
*
* This is a human-readable summary of the Legal Code (the full license).
* http://creativecommons.org/licenses/by/2.5/au/legalcode
* ------------------------------------------------------------------------ */

/* ---------------------------------------------------------------------------
* Special Note
*
* A special mention and thanks to the contributions of several parties in
* blogging and publishing this complex API.
*
* P/Invoke .NET - http://www.pinvoke.net
*
* MSDN Magazine
*
* ------------------------------------------------------------------------ */
#endregion License

// $Header: /cvsroot/z-bar/msvs/zbar/Zebra.Printing/fileio.cs,v 1.4 2006/11/16 10:55:04 vinorodrigues Exp $

#if WindowsCE || PocketPC
#define WINCE
#endif

using System;
using System.Runtime.InteropServices;
using System.Threading;

#if WINCE
#error This module is not intended for Mobile platform
#endif

namespace Zebra.Printing
{
internal class FileIO
{

internal const int INVALID_HANDLE_VALUE = -1;

internal const int ERROR_FILE_NOT_FOUND = 2;
internal const int ERROR_INVALID_NAME = 123;
internal const int ERROR_ACCESS_DENIED = 5;
internal const int ERROR_IO_PENDING = 997;
internal const int ERROR_IO_INCOMPLETE = 996;

internal class NullClass
{
public NullClass()
{
throw new Exception("Cannot create instance of NullClass");
}
}

#region CreateFile

[Flags]
internal enum FileAccess : uint // from winnt.h
{
GENERIC_READ = 0x80000000,
GENERIC_WRITE = 0x40000000,
GENERIC_EXECUTE = 0x20000000,
GENERIC_ALL = 0x10000000
}

[Flags]
internal enum FileShareMode : uint // from winnt.h
{
FILE_SHARE_READ = 0x00000001,
FILE_SHARE_WRITE = 0x00000002,
FILE_SHARE_DELETE = 0x00000004
}

internal enum FileCreationDisposition : uint // from winbase.h
{
CREATE_NEW = 1,
CREATE_ALWAYS = 2,
OPEN_EXISTING = 3,
OPEN_ALWAYS = 4,
TRUNCATE_EXISTING = 5
}

[Flags]
internal enum FileAttributes : uint // from winnt.h
{
FILE_ATTRIBUTE_READONLY = 0x00000001,
FILE_ATTRIBUTE_HIDDEN = 0x00000002,
FILE_ATTRIBUTE_SYSTEM = 0x00000004,
FILE_ATTRIBUTE_DIRECTORY = 0x00000010,
FILE_ATTRIBUTE_ARCHIVE = 0x00000020,
FILE_ATTRIBUTE_DEVICE = 0x00000040,
FILE_ATTRIBUTE_NORMAL = 0x00000080,
FILE_ATTRIBUTE_TEMPORARY = 0x00000100,
FILE_ATTRIBUTE_SPARSE_FILE = 0x00000200,
FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400,
FILE_ATTRIBUTE_COMPRESSED = 0x00000800,
FILE_ATTRIBUTE_OFFLINE = 0x00001000,
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x00002000,
FILE_ATTRIBUTE_ENCRYPTED = 0x00004000,

// from winbase.h
FILE_FLAG_WRITE_THROUGH = 0x80000000,
FILE_FLAG_OVERLAPPED = 0x40000000,
FILE_FLAG_NO_BUFFERING = 0x20000000,
FILE_FLAG_RANDOM_ACCESS = 0x10000000,
FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000,
FILE_FLAG_DELETE_ON_CLOSE = 0x04000000,
FILE_FLAG_BACKUP_SEMANTICS = 0x02000000,
FILE_FLAG_POSIX_SEMANTICS = 0x01000000,
FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000,
FILE_FLAG_OPEN_NO_RECALL = 0x00100000,
FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000
}

[DllImport("kernel32.dll", SetLastError = true)]
internal static extern IntPtr CreateFile(
string lpFileName,
FileAccess dwDesiredAccess,
FileShareMode dwShareMode,
IntPtr lpSecurityAttributes,
FileCreationDisposition dwCreationDisposition,
FileAttributes dwFlagsAndAttributes,
IntPtr hTemplateFile);

#endregion CreateFile

#region CloseHandle

[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool CloseHandle(IntPtr hObject);

#endregion CloseHandle

#region GetOverlappedResult

[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool GetOverlappedResult(
IntPtr hFile,
/* IntPtr */ ref System.Threading.NativeOverlapped lpOverlapped,
out uint nNumberOfBytesTransferred,
bool bWait);

#endregion GetOverlappedResult

#region WriteFile

[DllImport("kernel32.dll", SetLastError = true, EntryPoint = "WriteFile")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool WriteFile0(
IntPtr hFile,
[MarshalAs(UnmanagedType.LPArray)]
byte[] lpBuffer,
uint nNumberOfBytesToWrite,
out uint lpNumberOfBytesWritten,
NullClass lpOverlapped);

[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool WriteFile(
IntPtr hFile,
[MarshalAs(UnmanagedType.LPArray)] byte[] lpBuffer,
uint nNumberOfBytesToWrite,
out uint lpNumberOfBytesWritten,
[In] ref System.Threading.NativeOverlapped lpOverlapped);

[DllImport("kernel32.dll", SetLastError = true)]
internal static extern int WriteFileEx(
IntPtr hFile,
[MarshalAs(UnmanagedType.LPArray)] byte[] lpBuffer,
int nNumberOfBytesToWrite,
[In] ref System.Threading.NativeOverlapped lpOverlapped,
[MarshalAs(UnmanagedType.FunctionPtr)] IOCompletionCallback callback
);

#endregion WriteFile

#region ReadFile

[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool ReadFile(
IntPtr hFile,
[MarshalAs(UnmanagedType.LPArray)] [Out] byte[] lpBuffer,
uint nNumberOfBytesToRead,
out uint lpNumberOfBytesRead,
[In] ref System.Threading.NativeOverlapped lpOverlapped);

[DllImport("kernel32.dll", SetLastError = true, EntryPoint = "ReadFile")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool ReadFile0(
IntPtr hFile,
[MarshalAs(UnmanagedType.LPArray)] [Out] byte[] lpBuffer,
uint nNumberOfBytesToRead,
out uint lpNumberOfBytesRead,
NullClass lpOverlapped);

[DllImport("kernel32.dll", SetLastError = true)]
internal static extern int ReadFileEx(
IntPtr hFile,
[MarshalAs(UnmanagedType.LPArray)] byte[] lpBuffer,
int nNumberOfBytesToRead,
[In] ref System.Threading.NativeOverlapped lpOverlapped,
[MarshalAs(UnmanagedType.FunctionPtr)] IOCompletionCallback callback);

#endregion ReadFile

}
}


//-------------------------------
// PrinterConnector.cs
//-------------------------------

#region License
/* ---------------------------------------------------------------------------
* Creative Commons License
* http://creativecommons.org/licenses/by/2.5/au/
*
* Attribution 2.5 Australia
*
* You are free:
*
* - to copy, distribute, display, and perform the work
* - to make derivative works
* - to make commercial use of the work
*
* Under the following conditions:
*
* Attribution: You must attribute the work in the manner specified by the
* author or licensor.
*
* For any reuse or distribution, you must make clear to others the license
* terms of this work. Any of these conditions can be waived if you get
* permission from the copyright holder. Your fair use and other rights
* are in no way affected by the above.
*
* This is a human-readable summary of the Legal Code (the full license).
* http://creativecommons.org/licenses/by/2.5/au/legalcode
* ------------------------------------------------------------------------ */
#endregion License

// $Header: /cvsroot/z-bar/msvs/zbar/Zebra.Printing/Connect.cs,v 1.7 2006/11/16 10:55:04 vinorodrigues Exp $

using System;
using System.Collections.Generic;
using System.Text;

namespace Zebra.Printing
{

public abstract class PrinterConnector
{

protected abstract void SetConnected(bool value);

protected abstract bool GetConnected();

public bool IsConnected
{
get { return GetConnected(); }
set { SetConnected(value); }
}

public static readonly int DefaultReadTimeout = 200;

private int readTimeout = DefaultReadTimeout;

public int ReadTimeout
{
get { return readTimeout; }
set { readTimeout = value; }
}

public static readonly int DefaultWriteTimeout = 200;

private int writeTimeout = DefaultWriteTimeout;

public int WriteTimeout
{
get { return writeTimeout; }
set { writeTimeout = value; }
}

/* public bool Connect()
{
SetConnected(true);
return GetConnected();
} */

/* public void Disconnect()
{
SetConnected(false);
} */

public int Send(byte[] buffer)
{
return Send(buffer, 0, buffer.Length);
}

public abstract bool BeginSend();

public abstract void EndSend();

public abstract int Send(byte[] buffer, int offset, int count);

public virtual bool CanRead()
{
return false;
}

///
/// Reads data from the incomming connection.
///

/// populated data buffer or null if empty/unsuccessful
/// Number of bytes read or -1 if unsuccessful
public abstract int Read(byte[] buffer, int offset, int count);

}

}



//-------------------------------
// SetupApi.cs
//-------------------------------

#region License
/* ---------------------------------------------------------------------------
* Creative Commons License
* http://creativecommons.org/licenses/by/2.5/au/
*
* Attribution 2.5 Australia
*
* You are free:
*
* - to copy, distribute, display, and perform the work
* - to make derivative works
* - to make commercial use of the work
*
* Under the following conditions:
*
* Attribution: You must attribute the work in the manner specified by the
* author or licensor.
*
* For any reuse or distribution, you must make clear to others the license
* terms of this work. Any of these conditions can be waived if you get
* permission from the copyright holder. Your fair use and other rights
* are in no way affected by the above.
*
* This is a human-readable summary of the Legal Code (the full license).
* http://creativecommons.org/licenses/by/2.5/au/legalcode
* ------------------------------------------------------------------------ */

/* ---------------------------------------------------------------------------
* Special Note
*
* A special mention and thanks to the contributions of several parties in
* blogging and publishing this complex API. Converting to C# was not easy!
*
* The "setupapi.h" file from the Microsoft DDK for Windows XP SP2
*
* Peter Skarpetis for this blog "GETTING A HANDLE ON USBPRINT.SYS"
* http://blog.peter.skarpetis.com
*
* The Code Project - http://www.codeproject.com
*
* P/Invoke .NET - http://www.pinvoke.net
*
* ------------------------------------------------------------------------ */
#endregion License

// $Header: /cvsroot/z-bar/msvs/zbar/Zebra.Printing/setupapi.cs,v 1.4 2006/12/28 10:54:21 vinorodrigues Exp $

#if WindowsCE || PocketPC
#define WINCE
#endif

#if WINCE
#error This module is not intended for Mobile platform
#endif

using System;
using System.Text;
using System.Runtime.InteropServices;

namespace Zebra.Printing
{

internal class SetupApi
{

#region Consts

internal const UInt32 DIGCF_DEFAULT = 0x00000001; // only valid with DIGCF_DEVICEINTERFACE
internal const UInt32 DIGCF_PRESENT = 0x00000002;
internal const UInt32 DIGCF_ALLCLASSES = 0x00000004;
internal const UInt32 DIGCF_PROFILE = 0x00000008;
internal const UInt32 DIGCF_DEVICEINTERFACE = 0x00000010;

internal const UInt32 SPDRP_DEVICEDESC = 0x00000000; // DeviceDesc (R/W)
internal const UInt32 SPDRP_HARDWAREID = 0x00000001; // HardwareID (R/W)
internal const UInt32 SPDRP_COMPATIBLEIDS = 0x00000002; // CompatibleIDs (R/W)
internal const UInt32 SPDRP_UNUSED0 = 0x00000003; // unused
internal const UInt32 SPDRP_SERVICE = 0x00000004; // Service (R/W)
// internal const UInt32 SPDRP_UNUSED1 = 0x00000005; // unused
// internal const UInt32 SPDRP_UNUSED2 = 0x00000006; // unused
internal const UInt32 SPDRP_CLASS = 0x00000007; // Class (R--tied to ClassGUID)
internal const UInt32 SPDRP_CLASSGUID = 0x00000008; // ClassGUID (R/W)
internal const UInt32 SPDRP_DRIVER = 0x00000009; // Driver (R/W)
internal const UInt32 SPDRP_CONFIGFLAGS = 0x0000000A; // ConfigFlags (R/W)
internal const UInt32 SPDRP_MFG = 0x0000000B; // Mfg (R/W)
internal const UInt32 SPDRP_FRIENDLYNAME = 0x0000000C; // FriendlyName (R/W)
internal const UInt32 SPDRP_LOCATION_INFORMATION = 0x0000000D; // LocationInformation (R/W)
internal const UInt32 SPDRP_PHYSICAL_DEVICE_OBJECT_NAME = 0x0000000E; // PhysicalDeviceObjectName (R)
internal const UInt32 SPDRP_CAPABILITIES = 0x0000000F; // Capabilities (R)
internal const UInt32 SPDRP_UI_NUMBER = 0x00000010; // UiNumber (R)
internal const UInt32 SPDRP_UPPERFILTERS = 0x00000011; // UpperFilters (R/W)
internal const UInt32 SPDRP_LOWERFILTERS = 0x00000012; // LowerFilters (R/W)
internal const UInt32 SPDRP_BUSTYPEGUID = 0x00000013; // BusTypeGUID (R)
internal const UInt32 SPDRP_LEGACYBUSTYPE = 0x00000014; // LegacyBusType (R)
internal const UInt32 SPDRP_BUSNUMBER = 0x00000015; // BusNumber (R)
internal const UInt32 SPDRP_ENUMERATOR_NAME = 0x00000016; // Enumerator Name (R)
internal const UInt32 SPDRP_SECURITY = 0x00000017; // Security (R/W, binary form)
internal const UInt32 SPDRP_SECURITY_SDS = 0x00000018; // Security (W, SDS form)
internal const UInt32 SPDRP_DEVTYPE = 0x00000019; // Device Type (R/W)
internal const UInt32 SPDRP_EXCLUSIVE = 0x0000001A; // Device is exclusive-access (R/W)
internal const UInt32 SPDRP_CHARACTERISTICS = 0x0000001B; // Device Characteristics (R/W)
internal const UInt32 SPDRP_ADDRESS = 0x0000001C; // Device Address (R)
internal const UInt32 SPDRP_UI_NUMBER_DESC_FORMAT = 0x0000001D; // UiNumberDescFormat (R/W)
internal const UInt32 SPDRP_DEVICE_POWER_DATA = 0x0000001E; // Device Power Data (R)
internal const UInt32 SPDRP_REMOVAL_POLICY = 0x0000001F; // Removal Policy (R)
internal const UInt32 SPDRP_REMOVAL_POLICY_HW_DEFAULT = 0x00000020; // Hardware Removal Policy (R)
internal const UInt32 SPDRP_REMOVAL_POLICY_OVERRIDE = 0x00000021; // Removal Policy Override (RW)
internal const UInt32 SPDRP_INSTALL_STATE = 0x00000022; // Device Install State (R)
internal const UInt32 SPDRP_MAXIMUM_PROPERTY = 0x00000023; // Upper bound on ordinals

#endregion Consts

#region Structs

[StructLayout(LayoutKind.Sequential)]
internal struct SP_DEVINFO_DATA
{
///
/// Size of structure in bytes
///

public Int32 cbSize;
///
/// GUID of the device interface class
///

public Guid classGuid;
///
/// Handle to this device instance
///

public Int32 devInst;
///
/// Reserved; do not use.
///

public UIntPtr reserved;
};

[StructLayout(LayoutKind.Sequential)]
internal struct SP_DEVICE_INTERFACE_DATA
{
///
/// Size of the structure, in bytes
///

public Int32 cbSize;
///
/// GUID of the device interface class
///

public Guid interfaceClassGuid;
///
///
///

public UInt32 flags;
///
/// Reserved; do not use.
///

public UIntPtr reserved;
};

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 1)]
public struct SP_DEVICE_INTERFACE_DETAIL_DATA
{
public UInt32 cbSize;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)] // will never be more than 256 in length
public string devicePath;
}

public class NullClass
{
public NullClass()
{
throw new Exception("Cannot create instance of NullClass");
}
}

#endregion Structs

#region DLLImports

[DllImport("setupapi.dll", CharSet = CharSet.Auto)]
internal static extern IntPtr SetupDiGetClassDevs(
ref Guid ClassGuid,
[MarshalAs(UnmanagedType.LPTStr)]
String Enumerator,
IntPtr hwndParent,
UInt32 Flags);

[DllImport(@"setupapi.dll", CharSet=CharSet.Auto, SetLastError = true)] // from PInvoke.net
internal static extern UInt16 SetupDiDestroyDeviceInfoList( IntPtr hDevInfo );

[DllImport(@"setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)] // from PInvoke.net
internal static extern Boolean SetupDiEnumDeviceInterfaces(
IntPtr hDevInfo,
ref SP_DEVINFO_DATA devInfo,
ref Guid interfaceClassGuid,
UInt32 memberIndex,
ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData );

[DllImport(@"setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern Boolean SetupDiEnumDeviceInterfaces(
IntPtr hDevInfo,
int zero_devInfo, // used for 0 (Zero)
ref Guid interfaceClassGuid,
UInt32 memberIndex,
ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData );

[DllImport(@"setupapi.dll", CharSet = CharSet.Auto, SetLastError = true, EntryPoint = "SetupDiGetDeviceInterfaceDetail")]
internal static extern Boolean SetupDiGetDeviceInterfaceDetail0(
IntPtr hDevInfo,
ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData,
ref SP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData, // OPTIONAL
UInt32 deviceInterfaceDetailDataSize,
/* out UInt32 */ NullClass requiredSize, // OPTIONAL
ref SP_DEVINFO_DATA deviceInfoData); // OPTIONAL

[DllImport(@"setupapi.dll", CharSet = CharSet.Auto, SetLastError = true, EntryPoint = "SetupDiGetDeviceInterfaceDetail")]
internal static extern Boolean SetupDiGetDeviceInterfaceDetail0(
IntPtr hDevInfo,
ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData,
NullClass deviceInterfaceDetailData, // OPTIONAL
UInt32 deviceInterfaceDetailDataSize,
out UInt32 requiredSize, // OPTIONAL
NullClass deviceInfoData); // OPTIONAL

/* [DllImport(@"setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern Boolean SetupDiGetDeviceInterfaceDetail(
IntPtr hDevInfo,
ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData,
Int32 zero_deviceInterfaceDetailData, // used for 0 (Zero)
UInt32 zero_deviceInterfaceDetailDataSize,
out UInt32 requiredSize,
Int32 zero_deviceInfoData); // used for 0 (Zero) */ // KEEP

[DllImport(@"setupapi.dll", CharSet = CharSet.Auto, SetLastError = true, EntryPoint = "SetupDiGetDeviceRegistryProperty")]
internal static extern Boolean SetupDiGetDeviceRegistryProperty0(
IntPtr hDevInfo,
ref SP_DEVINFO_DATA deviceInfoData,
UInt32 property,
/* out IntPtr */ NullClass propertyRegDataType, // OPTIONAL
StringBuilder propertyBuffer,
UInt32 propertyBufferSize,
/* out IntPtr */ NullClass requiredSize); // OPTIONAL

[DllImport(@"setupapi.dll", CharSet = CharSet.Auto, SetLastError = true, EntryPoint = "SetupDiGetDeviceRegistryProperty")] // overloaded for null refs
internal static extern Boolean SetupDiGetDeviceRegistryProperty0(
IntPtr hDevInfo,
ref SP_DEVINFO_DATA deviceInfoData,
UInt32 property,
out IntPtr propertyRegDataType, // OPTIONAL
NullClass propertyBuffer,
UInt32 propertyBufferSize, // set to 0 (Zero)
out UInt32 requiredSize); // OPTIONAL

#endregion DLLImports

}
}




//-------------------------------
// UsbPrinterconnector.cs
//-------------------------------

#region License
/* ---------------------------------------------------------------------------
* Creative Commons License
* http://creativecommons.org/licenses/by/2.5/au/
*
* Attribution 2.5 Australia
*
* You are free:
*
* - to copy, distribute, display, and perform the work
* - to make derivative works
* - to make commercial use of the work
*
* Under the following conditions:
*
* Attribution: You must attribute the work in the manner specified by the
* author or licensor.
*
* For any reuse or distribution, you must make clear to others the license
* terms of this work. Any of these conditions can be waived if you get
* permission from the copyright holder. Your fair use and other rights
* are in no way affected by the above.
*
* This is a human-readable summary of the Legal Code (the full license).
* http://creativecommons.org/licenses/by/2.5/au/legalcode
* ------------------------------------------------------------------------ */

/* ---------------------------------------------------------------------------
* Special Note
*
* A special mention and thanks to the contributions of several parties in
* blogging and publishing this complex API. Converting to C# was not easy!
*
* The "setupapi.h" file from the Microsoft DDK for Windows XP SP2
*
* Peter Skarpetis for this blog "GETTING A HANDLE ON USBPRINT.SYS"
* http://blog.peter.skarpetis.com
*
* The Code Project - http://www.codeproject.com
*
* P/Invoke .NET - http://www.pinvoke.net
*
* ------------------------------------------------------------------------ */
#endregion License

// $Header: /cvsroot/z-bar/msvs/zbar/Zebra.Printing/ConUsb.cs,v 1.7 2006/12/28 10:54:21 vinorodrigues Exp $

#if WindowsCE || PocketPC
#define WINCE
#endif

#if WINCE
#error This module is not intended for Mobile platform
#endif

using System;
using System.Text;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Collections.Specialized;
using System.IO;
using Microsoft.Win32;
using System.Threading;

namespace Zebra.Printing
{

///
/// Connector leveraging usbmon.dll/usbprint.sys.

/// Note: This cannot be used in more than one process at a time, so if the spooler is running you cannot use it.
///

public class UsbPrinterConnector : PrinterConnector
{

#region EnumDevices

static Guid GUID_DEVICEINTERFACE_USBPRINT = new Guid(
0x28d78fad, 0x5a12, 0x11D1,
0xae, 0x5b, 0x00, 0x00, 0xf8, 0x03, 0xa8, 0xc2);
// static IntPtr INVALID_HANDLE_VALUE = (IntPtr)(-1);

public static NameValueCollection EnumDevices()
{
return EnumDevices(true);
}

public static NameValueCollection EnumDevices(bool presentOnly)
{
return EnumDevices(presentOnly, true);
}

public static NameValueCollection EnumDevices(bool presentOnly, bool zebraOnly)
{
return EnumDevices(presentOnly, zebraOnly, true);
}

public static NameValueCollection EnumDevices(bool PresentOnly, bool ZebraOnly, bool fullDetail)
{
NameValueCollection res = new NameValueCollection();
String name, path, desc, port;

Guid intfce;
IntPtr devs;
SetupApi.SP_DEVINFO_DATA devinfo = new SetupApi.SP_DEVINFO_DATA();
SetupApi.SP_DEVICE_INTERFACE_DATA devinterface = new SetupApi.SP_DEVICE_INTERFACE_DATA();
SetupApi.SP_DEVICE_INTERFACE_DETAIL_DATA interface_detail;
UInt32 devcount;
UInt32 size;

RegistryKey regKey, subKey;

intfce = GUID_DEVICEINTERFACE_USBPRINT;

UInt32 flags = SetupApi.DIGCF_DEVICEINTERFACE;
if (PresentOnly)
flags |= SetupApi.DIGCF_PRESENT;

devs = SetupApi.SetupDiGetClassDevs(ref intfce,
null,
IntPtr.Zero,
flags);
if (devs == (IntPtr)FileIO.INVALID_HANDLE_VALUE)
return null;

devcount = 0;
devinterface.cbSize = Marshal.SizeOf(typeof(SetupApi.SP_DEVICE_INTERFACE_DATA));

while (SetupApi.SetupDiEnumDeviceInterfaces(
devs,
0,
ref intfce,
devcount,
ref devinterface))
{
devcount++;

SetupApi.SetupDiGetDeviceInterfaceDetail0(
devs,
ref devinterface,
null,
0,
out size,
null);

if ((size > 0) &&
(size <= (Marshal.SizeOf(typeof(SetupApi.SP_DEVICE_INTERFACE_DETAIL_DATA))) - sizeof(UInt32)))
{

interface_detail = new SetupApi.SP_DEVICE_INTERFACE_DETAIL_DATA();
interface_detail.cbSize = (UInt32)(sizeof(UInt32) + sizeof(Char)); // Wow! This is a gotcha!

devinfo = new SetupApi.SP_DEVINFO_DATA();
devinfo.cbSize = Marshal.SizeOf(typeof(SetupApi.SP_DEVINFO_DATA));

if (SetupApi.SetupDiGetDeviceInterfaceDetail0(
devs,
ref devinterface,
ref interface_detail,
size,
null,
ref devinfo))
{
path = interface_detail.devicePath.ToString();
name = GetDeviceRegistryProperty(devs, ref devinfo, SetupApi.SPDRP_LOCATION_INFORMATION);
if (fullDetail)
{
desc = "";
port = "";

if (path.StartsWith("\\\\?\\"))
{
string key = "##?#" + path.Substring(4);

try
{
regKey = Registry.LocalMachine.OpenSubKey("SYSTEM\\CurrentControlSet\\Control\\DeviceClasses\\" +
GUID_DEVICEINTERFACE_USBPRINT.ToString("B"));
try
{
subKey = regKey.OpenSubKey(key);
if (subKey != null)
{
subKey = subKey.OpenSubKey("#\\Device Parameters");
desc = subKey.GetValue("Port Description").ToString();
port = subKey.GetValue("Port Number").ToString();
subKey.Close();
}
}
finally
{
regKey.Close();
}
}
catch
{
// do nothing
}
}

if (ZebraOnly && (!desc.StartsWith("Zebra")))
continue;

res.Add(name, path);
res.Add(name, desc);
res.Add(name, port);
}
else
res.Add(name, path);
}
else
throw new Win32Exception(Marshal.GetLastWin32Error());
}

}
SetupApi.SetupDiDestroyDeviceInfoList(devs);

return res;
}

private static String GetDeviceRegistryProperty(IntPtr hDevInfo,
ref SetupApi.SP_DEVINFO_DATA deviceInfoData,
UInt32 property)
{
IntPtr dataType = IntPtr.Zero;
uint size;
StringBuilder buffer;

SetupApi.SetupDiGetDeviceRegistryProperty0(
hDevInfo,
ref deviceInfoData,
property,
out dataType,
null,
0,
out size);

if (size > 0)
{
buffer = new StringBuilder((int)size);

if (SetupApi.SetupDiGetDeviceRegistryProperty0(
hDevInfo,
ref deviceInfoData,
property,
null,
buffer,
size,
null))
{
return buffer.ToString();
}
else
throw new Win32Exception(Marshal.GetLastWin32Error());
}
else
return String.Empty;
}

#endregion EnumDevices

private string interfaceName;

private IntPtr usbHandle = IntPtr.Zero;

public static readonly uint ReadBufferSize = 512;

private byte[] readBuffer;

///
/// Constructor
///

///
public UsbPrinterConnector(string InterfaceName)
: base()
{
if (!InterfaceName.StartsWith("\\"))
{
NameValueCollection plist = EnumDevices(true, false, false);
if (plist.GetValues(InterfaceName) != null)
InterfaceName = plist.GetValues(InterfaceName)[0];
else
throw new Exception("Cannot locate USB device");
}
this.interfaceName = InterfaceName;
}

///
/// Destructor
///

~UsbPrinterConnector()
{
SetConnected(false);
}

protected override void SetConnected(bool value)
{
if (value)
{
if ((int)usbHandle > 0)
SetConnected(false);

/* C++ Decl.
usbHandle = CreateFile(
interfacename,
GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
NULL);
*/

usbHandle = FileIO.CreateFile(
interfaceName,
FileIO.FileAccess.GENERIC_WRITE | FileIO.FileAccess.GENERIC_READ,
FileIO.FileShareMode.FILE_SHARE_READ,
IntPtr.Zero,
FileIO.FileCreationDisposition.OPEN_ALWAYS,
FileIO.FileAttributes.FILE_ATTRIBUTE_NORMAL |
FileIO.FileAttributes.FILE_FLAG_SEQUENTIAL_SCAN |
FileIO.FileAttributes.FILE_FLAG_OVERLAPPED,
IntPtr.Zero);
if ((int)usbHandle <= 0)
throw new Win32Exception(Marshal.GetLastWin32Error());
}
else
if ((int)usbHandle > 0)
{
FileIO.CloseHandle(usbHandle);
usbHandle = IntPtr.Zero;
}
}

protected override bool GetConnected()
{
return ((int)usbHandle > 0);
}

public override bool BeginSend()
{
return GetConnected();
}

public override void EndSend()
{
// do nothing
}

public override int Send(byte[] buffer, int offset, int count)
{
// USB 1.1 WriteFile maximum block size is 4096
uint size;
byte[] bytes;

if (!GetConnected())
throw new Exception("Not connected");

if (count > 4096)
{
throw new NotImplementedException(); // TODO: Copy byte array loop
}
else
{
bytes = new byte[count];
Array.Copy(buffer, offset, bytes, 0, count);
ManualResetEvent wo = new ManualResetEvent(false);
NativeOverlapped ov = new NativeOverlapped();
// ov.OffsetLow = 0; ov.OffsetHigh = 0;
ov.EventHandle = wo.Handle;
if (!FileIO.WriteFile(usbHandle, bytes, (uint)count, out size, ref ov))
{
if (Marshal.GetLastWin32Error() == FileIO.ERROR_IO_PENDING)
wo.WaitOne(WriteTimeout, false);
else
throw new Win32Exception(Marshal.GetLastWin32Error());
}

FileIO.GetOverlappedResult(usbHandle, ref ov, out size, true);
return (int)size;
}
}

public override bool CanRead()
{
return true;
}

public override int Read(byte[] buffer, int offset, int count)
{
// USB 1.1 ReadFile in block chunks of 64 bytes
// USB 2.0 ReadFile in block chunks of 512 bytes
uint read;

if (readBuffer == null)
readBuffer = new byte[ReadBufferSize];

AutoResetEvent sg = new AutoResetEvent(false);
NativeOverlapped ov = new NativeOverlapped();
ov.OffsetLow = 0;
ov.OffsetHigh = 0;
ov.EventHandle = sg.Handle;

if (!FileIO.ReadFile(usbHandle, readBuffer, ReadBufferSize, out read, ref ov))
{
if (Marshal.GetLastWin32Error() == FileIO.ERROR_IO_PENDING)
sg.WaitOne(ReadTimeout, false);
else
throw new Win32Exception(Marshal.GetLastWin32Error());
}
FileIO.GetOverlappedResult(usbHandle, ref ov, out read, true);

Array.Copy(readBuffer, 0, buffer, offset, read);
return (int)read;
}

}

}


18 comments:

Julio César said...

Library from the hell!!
My printer printing like a thunder bolt!!!
thanks a lot!!!

Ahmad Al-Salhi said...

How can I use your library!!

Safdar Ali said...

Great post. Is there any printer best from zebra using for Plastic card printing purpose?

Louis Kruger said...

Thanks for this! I got my KR403 printer printing using your library.

The only thing I'm having trouble with is reading the status of the printer using the "~HS" ZPL command. After sending it to the printer I use your ZebraUsbStream.Read method to read the result, but it always seems to return FileIO.ERROR_IO_PENDING in the UsbPrinterconnector.Read method. Could you perhaps show me an example of how I can read the status back please?

Louis Kruger said...
This comment has been removed by the author.
Unknown said...

Hi, im using GX420T, it work with test page but not with the library, when i send the command it flash the green light, but nothing happen, any clue?
thx

Hareesh said...

Please tell me how I can Use this one in My application

BujwA Bihi said...

Thank you. Thank you Thank you...
Great Help, This was

test said...

Would appreciate if you can show how to use this library. I am trying to send the ZPL commands to read the RFID tag and later write some data.

adavis32225 said...

Example:
var enumDevices = Zebra.Printing.UsbPrinterConnector.EnumDevices();
if (enumDevices.Keys.Count > 0)
{
string key = enumDevices.Keys[0];
Zebra.Printing.UsbPrinterConnector connector = new Zebra.Printing.UsbPrinterConnector(key);

string command = @"
^XA

^FO200,20^A0N,32,32^FDIX-ID: XXX123456789^FS
^FO200,60^A0N,32,32^FDUPC: 00123456789^FS
^FO200,100^A0N,32,32^FDName: Organic Spaghetti Pasta^FS
^FO200,140^A0N,32,32^FDBrand: Organic Pastas^FS
^FO200,180^A0N,32,32^FDManufacturer: Liverite^FS

^FO15,300^B3N^FDXXX123456789^FS

^XZ";
byte[] buffer = ASCIIEncoding.ASCII.GetBytes(command);
connector.IsConnected = true;
connector.Send(buffer);
}

Unknown said...

When we use your code to get printer status using ~hs command.
It stuck on following line and wait for ever.
FileIO.GetOverlappedResult(usbHandle, ref ov, out read, true);

This problem happens with 3.5 framework.

NeedHelp said...

How I use the codes? There are several namespaces and using... Do I need to cut and create different class for each ? TIA

seenu said...

can you please tell me how to get low paper status with this code

Unknown said...
This comment has been removed by the author.
Mysterious..!..!! said...

can you please tell me how to get low paper or head open status with this code?

Kent K said...

10+ years later from this post, wanted to say that there does exist an SDK supported by the company for last couple years: https://www.zebra.com/us/en/support-downloads/printer-software/link-os-multiplatform-sdk.html

Lucifer Smith said...

UHF RFID tag manufacturer

Bar Code India said...

Thanks for sharing the blog.
The Zebra Printer is known for its printing process. You can easily buy such printers in India at the cheapest barcode printers price.