398 lines
16 KiB
C#
398 lines
16 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace KohaCompanion.Shared
|
|
{
|
|
/// <summary>
|
|
/// Eine Klasse zum Kapseln eines Druckers. Mit der Methode
|
|
/// <see cref="RawPrinter.CreateDocument">CreateDocument(String)</see>
|
|
/// kann ein neues Dokument im RAW-Format erstellt werden.
|
|
///
|
|
/// Pro RawPrinter-Instanz kann immer nur eine RawDocumentStream-Instanz
|
|
/// gleichzeitig aktiv sein. Nach der Freigabe einer RawDocumentStream-
|
|
/// Instanz kann wieder ein neues Dokument erstellt werden.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Diese Klasse ist nicht threadsicher. Wenn verschiedene Threads drucken
|
|
/// wollen, muss für jeden Thread ein eigenens RawPrinter-Objekt
|
|
/// erstellt werden.
|
|
///
|
|
/// Quelle: https://www.vbarchiv.net/tipps/tipp_2375-kassenbon-drucker-mit-vbnet-oder-c-per-esc-pos-befehle-ansprechen.html
|
|
/// Originalautor: Konstantin Preißer
|
|
/// Originallizenz: MIT
|
|
/// Angepaßt an C# von Anna Christina Naß
|
|
/// </remarks>
|
|
|
|
public class RawPrinter : IDisposable
|
|
{
|
|
// API-Deklarationen
|
|
[DllImport("winspool.Drv", EntryPoint = "OpenPrinterW", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]
|
|
private static extern bool OpenPrinter(string szPrinter, ref IntPtr hPrinter, IntPtr pd);
|
|
|
|
[DllImport("winspool.Drv", EntryPoint = "ClosePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]
|
|
private static extern bool ClosePrinter(IntPtr hPrinter);
|
|
|
|
[DllImport("winspool.drv", EntryPoint = "GetPrinterDriver2W", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]
|
|
private static extern bool GetPrinterDriver2(IntPtr hWnd, IntPtr hPrinter, string? pEnvironment, int Level, IntPtr pDriverInfo, int cbBuf, ref int pcbNeeded);
|
|
|
|
private const int ERROR_INSUFFICIENT_BUFFER = 122;
|
|
private const int PRINTER_DRIVER_XPS = 0x2;
|
|
|
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
|
private class DRIVER_INFO_8
|
|
{
|
|
public uint cVersion;
|
|
public string? pName;
|
|
public string? pEnvironment;
|
|
public string? pDriverPath;
|
|
public string? pDataFile;
|
|
public string? pConfigFile;
|
|
public string? pHelpFile;
|
|
public string? pDependentFiles;
|
|
public string? pMonitorName;
|
|
public string? pDefaultDataType;
|
|
public string? pszzPreviousNames;
|
|
public System.Runtime.InteropServices.ComTypes.FILETIME ftDriverDate;
|
|
public ulong dwlDriverVersion;
|
|
public string? pszMfgName;
|
|
public string? pszOEMUrl;
|
|
public string? pszHardwareID;
|
|
public string? pszProvider;
|
|
public string? pszPrintProcessor;
|
|
public string? pszVendorSetup;
|
|
public string? pszzColorProfiles;
|
|
public string? pszInfPath;
|
|
public uint dwPrinterDriverAttributes;
|
|
public string? pszzCoreDriverDependencies;
|
|
public System.Runtime.InteropServices.ComTypes.FILETIME ftMinInboxDriverVerDate;
|
|
public ulong dwlMinInboxDriverVerVersion;
|
|
}
|
|
|
|
// Instanz-Variablen
|
|
private readonly IntPtr hPrinter = IntPtr.Zero;
|
|
private readonly bool xpsDriver;
|
|
private RawDocumentStream? currentDoc = null;
|
|
private bool disposed = false;
|
|
|
|
private IntPtr HandlePrinter
|
|
{
|
|
get
|
|
{
|
|
if (disposed)
|
|
throw new ObjectDisposedException("RawPrinter");
|
|
|
|
return hPrinter;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Erstellt eine neue Instanz.
|
|
/// </summary>
|
|
/// <param name="printerName">Der Name des zu verwendenden Druckers</param>
|
|
/// <exception cref="System.ComponentModel.Win32Exception">Beim Aufruf einer
|
|
/// Win32-API trat ein Fehler auf.</exception>
|
|
public RawPrinter(string printerName)
|
|
{
|
|
CheckApiCall(OpenPrinter(printerName, ref hPrinter, IntPtr.Zero));
|
|
|
|
xpsDriver = IsXpsDriver();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Überprüft den Rückgabewert einer Win32-API-Funktion und wirft bei Bedarf
|
|
/// eine Exception.
|
|
/// </summary>
|
|
/// <param name="retVal"></param>
|
|
private static void CheckApiCall(bool retVal)
|
|
{
|
|
if (!retVal)
|
|
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gibt die nativen Resourcen dieses Objekts frei.
|
|
/// </summary>
|
|
/// <exception cref="System.ComponentModel.Win32Exception">Beim Aufruf einer
|
|
/// Win32-API trat ein Fehler auf.</exception>
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (!disposed)
|
|
{
|
|
ClosePrinter(HandlePrinter);
|
|
disposed = true;
|
|
}
|
|
}
|
|
|
|
~RawPrinter()
|
|
{
|
|
Dispose(false);
|
|
}
|
|
|
|
private bool canCreateDocument = false;
|
|
/// <summary>
|
|
/// Erstellt ein neues RawPrintDocument.
|
|
/// </summary>
|
|
/// <param name="docName">Der Name des Dokuments</param>
|
|
/// <returns>Das neue Dokument</returns>
|
|
/// <exception cref="System.ComponentModel.Win32Exception">Beim Aufruf einer
|
|
/// Win32-API trat ein Fehler auf.</exception>
|
|
/// <exception cref="InvalidOperationException">Es wurde bereits ein Dokument
|
|
/// für diesen Drucker erstellt und noch nicht freigegeben.</exception>
|
|
public RawDocumentStream CreateDocument(string docName)
|
|
{
|
|
if (disposed)
|
|
throw new ObjectDisposedException("RawPrinter");
|
|
|
|
if (currentDoc != null)
|
|
throw new InvalidOperationException("Pro RawPrinter kann immer nur " + "1 RawDocumentStream-Objekt erzeugt werden.");
|
|
|
|
canCreateDocument = true;
|
|
try
|
|
{
|
|
currentDoc = new RawDocumentStream(this, docName);
|
|
return currentDoc;
|
|
}
|
|
finally
|
|
{
|
|
canCreateDocument = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Überprüft, ob es sich bei dem Treiber dieses Druckers um einen
|
|
/// XPS-Treiber handelt.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Bei Windows-Versionen kleiner als Windows 8 (NT 6.2), also
|
|
/// Windows Vista und Windows 7, wird immer
|
|
/// false zurückgegeben.
|
|
/// </remarks>
|
|
/// <returns>true, wenn es sich um einen XPS-Treiber handelt</returns>
|
|
private bool IsXpsDriver()
|
|
{
|
|
int level = 8;
|
|
|
|
// Nachschauen, wieviel Bytes als Buffer reserviert werden müssen.
|
|
// In diesem Fall muss GetPrinterDriver2 mit ERROR_INSUFFICIENT_BUFFER
|
|
// failen.
|
|
int bytesNeeded = 0;
|
|
GetPrinterDriver2(IntPtr.Zero, HandlePrinter, null, level, IntPtr.Zero, 0, ref bytesNeeded);
|
|
if (Marshal.GetLastWin32Error() != ERROR_INSUFFICIENT_BUFFER)
|
|
CheckApiCall(false);
|
|
|
|
DRIVER_INFO_8 driverInf;
|
|
|
|
// Nativen Speicher mit geforderter Größe erstellen
|
|
int pBytesLength = Math.Max(bytesNeeded, Marshal.SizeOf(typeof(DRIVER_INFO_8)));
|
|
IntPtr pBytes = Marshal.AllocHGlobal(pBytesLength);
|
|
try
|
|
{
|
|
CheckApiCall(GetPrinterDriver2(IntPtr.Zero, HandlePrinter, null, level, pBytes, pBytesLength, ref bytesNeeded));
|
|
|
|
// Der Anfang des Bytesarrays enthält die Structure; der hintere Teil kann
|
|
// die Strings enthalten, auf die die Pointer in der Structure zeigen.
|
|
// Hier jetzt die Structure in ein verwaltetes Objekt marshallen.
|
|
driverInf = (DRIVER_INFO_8)Marshal.PtrToStructure(pBytes, typeof(DRIVER_INFO_8));
|
|
}
|
|
finally
|
|
{
|
|
Marshal.FreeHGlobal(pBytes);
|
|
pBytes = IntPtr.Zero;
|
|
}// Pointer clearen
|
|
|
|
// Prüfen, ob das Flag PRINTER_DRIVER_XPS gesetzt ist
|
|
return (driverInf.dwPrinterDriverAttributes & PRINTER_DRIVER_XPS) != 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ermöglicht das Senden von RAW-Dokumenten (Bytes) an den Drucker.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Die Methoden StartPage() und EndPage() dienen zum Unterteilen des
|
|
/// Druckauftrags in Seiten (wichtig, wenn der Windows Spooler verwendet
|
|
/// wird und man Dokumente senden will, die lange zum Erstellen brauchen,
|
|
/// da dieser bis zur Fertigestellung einer Seite wartet, bevor diese
|
|
/// tatsächlich zum Drucker gesendet wird).
|
|
/// </remarks>
|
|
public class RawDocumentStream : Stream
|
|
{
|
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
|
private class DOCINFO
|
|
{
|
|
public string? pDocName;
|
|
public string? pOutputFile;
|
|
public string? pDataType;
|
|
}
|
|
|
|
// API-Deklarationen
|
|
[DllImport("winspool.Drv", EntryPoint = "StartDocPrinterW", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]
|
|
private static extern bool StartDocPrinter(IntPtr hPrinter, Int32 level, [In()][MarshalAs(UnmanagedType.LPStruct)] DOCINFO di);
|
|
|
|
[DllImport("winspool.Drv", EntryPoint = "EndDocPrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]
|
|
private static extern bool EndDocPrinter(IntPtr hPrinter);
|
|
|
|
[DllImport("winspool.Drv", EntryPoint = "StartPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]
|
|
private static extern bool StartPagePrinter(IntPtr hPrinter);
|
|
|
|
[DllImport("winspool.Drv", EntryPoint = "EndPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]
|
|
private static extern bool EndPagePrinter(IntPtr hPrinter);
|
|
|
|
[DllImport("winspool.Drv", EntryPoint = "WritePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]
|
|
private static extern bool WritePrinter(IntPtr hPrinter, IntPtr pBytes, int dwCount, ref int dwWritten);
|
|
|
|
private readonly RawPrinter printer;
|
|
private bool pageStarted = false;
|
|
private bool disposed = false;
|
|
|
|
internal RawDocumentStream(RawPrinter printer, string docName)
|
|
{
|
|
if (!printer.canCreateDocument)
|
|
// Schauen, dass das über den Printer erstellt wird
|
|
throw new InvalidOperationException();
|
|
|
|
this.printer = printer;
|
|
|
|
DOCINFO di = new DOCINFO();
|
|
// Wenn es sich um einen v4-Treiber (XPS-basiert, eingeführt mit
|
|
// Windows 8) handelt, muss "XPS_PASS" statt "RAW" verwendet werden.
|
|
// Siehe: http://support.microsoft.com/kb/2779300
|
|
di.pDataType = printer.xpsDriver ? "XPS_PASS" : "RAW";
|
|
di.pDocName = docName;
|
|
|
|
CheckApiCall(StartDocPrinter(printer.HandlePrinter, 1, di));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Startet eine neue Seite (nur wichtig für den Windows Spooler).
|
|
/// </summary>
|
|
/// <exception cref="System.ComponentModel.Win32Exception">Beim
|
|
/// Aufruf einer Win32-API trat ein Fehler auf.</exception>
|
|
public void StartPage()
|
|
{
|
|
if (!pageStarted)
|
|
{
|
|
CheckApiCall(StartPagePrinter(printer.HandlePrinter));
|
|
pageStarted = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Beendet die aktuelle Seite.
|
|
/// </summary>
|
|
/// <exception cref="System.ComponentModel.Win32Exception">Beim
|
|
/// Aufruf einer Win32-API trat ein Fehler auf.</exception>
|
|
public void FinishPage()
|
|
{
|
|
if (pageStarted)
|
|
{
|
|
CheckApiCall(EndPagePrinter(printer.HandlePrinter));
|
|
pageStarted = false;
|
|
}
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (!disposed)
|
|
{
|
|
if (disposing)
|
|
{
|
|
if (pageStarted)
|
|
FinishPage();
|
|
|
|
CheckApiCall(EndDocPrinter(printer.HandlePrinter));
|
|
|
|
printer.currentDoc = null;
|
|
}
|
|
|
|
disposed = true;
|
|
}
|
|
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
public override bool CanRead { get { return false; } }
|
|
|
|
public override bool CanSeek { get { return false; } }
|
|
|
|
public override bool CanWrite { 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)
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
public override long Seek(long offset, SeekOrigin origin)
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
public override void SetLength(long value)
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sendet die angegebenen Bytes an den Drucker.
|
|
/// </summary>
|
|
/// <param name="buffer"></param>
|
|
/// <param name="offset"></param>
|
|
/// <param name="count"></param>
|
|
/// <exception cref="System.ComponentModel.Win32Exception">Beim Aufruf
|
|
/// einer Win32-API trat ein Fehler auf.</exception>
|
|
public override void Write(byte[] buffer, int offset, int count)
|
|
{
|
|
if (disposed)
|
|
throw new ObjectDisposedException("RawDocumentStream");
|
|
|
|
if (offset < 0 || count < 0 || offset + count > buffer.Length)
|
|
throw new ArgumentException();
|
|
|
|
if (!pageStarted)
|
|
StartPage();
|
|
|
|
// Pointer auf Byte-Array holen.
|
|
// In C#: fixed (byte* pBytes = buffer) { ... },
|
|
// dann Pointerarithmetik verwenden
|
|
GCHandle hgc = GCHandle.Alloc(buffer, GCHandleType.Pinned);
|
|
try
|
|
{
|
|
int bytesCompleted = 0;
|
|
while (count - bytesCompleted > 0)
|
|
{
|
|
int tempBytesWritten = 0;
|
|
|
|
CheckApiCall(WritePrinter(printer.HandlePrinter, Marshal.UnsafeAddrOfPinnedArrayElement(buffer, offset + bytesCompleted), count - bytesCompleted, ref tempBytesWritten));
|
|
|
|
if (!(tempBytesWritten > 0))
|
|
break;
|
|
|
|
bytesCompleted += tempBytesWritten;
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
hgc.Free();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|