У нас есть служба печати, работающая как windows. Он получает задания на печать через REST API - PDF-файл с некоторыми настройками принтера (например, какой лоток, цветной или черно-белый, ...). Мы используем API диспетчера очереди печати в winspool.drv для отправки PDF-файла в режиме RAW на принтер.
Мы устанавливаем структуру PRINTER_DEFAULTS, устанавливаем поле pDevMode с нашей предварительно настроенной структурой DEVMODE, которая соответствует полученным настройкам принтера , Затем мы передаем PRINTER_DEFAULTS в функцию OpenPrinter
. Наконец, мы отправляем PDF с функцией WritePrinter(..)
.
Кажется, все работает, документ отображается в очереди принтера, настройки работы принтера соответствуют нашим настройкам структуры DEVMODE, НО принтер игнорирует спецификацию работы c настройки принтера, он печатается с настройками принтера по умолчанию.
Если я установлю настройки принтера по умолчанию с помощью функции SetPrinter
, тогда он будет напечатан в нужной конфигурации, но это не является правильным решением. для нашего варианта использования, потому что нам нужны параметры задания печати c.
Есть ли у вас какие-либо идеи, если в нашем коде есть ошибка или это нормальное поведение в режиме RAW? У вас есть идеи, как обойти эту проблему?
using System;
using System.IO;
using System.Runtime.InteropServices;
using Printing.GDI.API;
using static Printing.GDI.API.PrintApiGdi;
namespace RawPrint
{
class Program
{
static void Main(string[] args)
{
// choose the file type you want to test
string documentPath = @"PDF\Color1Page.pdf";
//documentPath = @"PDF\Color1Page.xps";
// read file from disk and save as bytes array
var docBytes = File.ReadAllBytes(documentPath);
// specify the printers name as shown in system settings, in this example will choose the default printers name.
string printerName = PrintApiGdiWrapper.GetDefaultPrinterName();
// send the document to the printer,
SendFileToPrinterRaw(printerName, docBytes, printWithColors: false);
}
/// <summary>
/// sets the dmColor flag in the DEVMODE structure & sends the document in RAW mode to the printer.
/// </summary>
/// <param name="printername"></param>
/// <param name="document"></param>
/// <param name="printWithColors"></param>
public static void SendFileToPrinterRaw(string printername, byte[] document, bool printWithColors)
{
IntPtr documentPtr = IntPtr.Zero;
IntPtr ptrDevmode = IntPtr.Zero;
IntPtr hPrinter = IntPtr.Zero;
bool success = false;
try
{
// print job structure
// - printjob name
// - data type is RAW
DOCINFOA docInfo = new DOCINFOA()
{
pDocName = "Test Print black & white",
pDataType = "RAW"
};
// create unmanaged pointer to document to print by allocating space of size of doc and copying to the unmanaged location
documentPtr = Marshal.AllocHGlobal(document.Length);
Marshal.Copy(document, 0, documentPtr, document.Length);
// Now create our print job configuration / DEVMODE structure.
// In this demo we just want to change the color to black white print
#region Printer setting configuration
if (OpenPrinter(printername, out hPrinter, IntPtr.Zero))
{
// get size of DEVMODE
int sizeNeeded = PrintApiGdi.DocumentProperties(IntPtr.Zero, hPrinter, printername, IntPtr.Zero, IntPtr.Zero, PrintApiGdi.DmBufferMode.DM_SIZEOF);
// Get the drivers current settings
ptrDevmode = Marshal.AllocHGlobal(sizeNeeded);
PrintApiGdi.DialogResultEnum dwRet = (PrintApiGdi.DialogResultEnum)PrintApiGdi.DocumentProperties(IntPtr.Zero, hPrinter, printername, ptrDevmode, IntPtr.Zero, PrintApiGdi.DmBufferMode.DM_OUT_BUFFER);
if (dwRet != PrintApiGdi.DialogResultEnum.IDOK)
{
throw new Exception("Couldn't get default devmode structure");
}
// Point to DEVMODE structure to perform printer settings changes
PrintApiGdi.DEVMODE devmode = Marshal.PtrToStructure<PrintApiGdi.DEVMODE>(ptrDevmode);
// the fields we are going to update
PrintApiGdi.DmFieldFeatures dmFieldsUpdated = 0;
if (IsFeatureSupported(devmode, PrintApiGdi.DmFieldFeatures.DM_COLOR))
{
devmode.dmColor = (printWithColors ? DmColorSupport.DMCOLOR_COLOR : DmColorSupport.DMCOLOR_MONOCHROME);
dmFieldsUpdated |= PrintApiGdi.DmFieldFeatures.DM_COLOR;
}
else
{
throw new Exception("The selected printer does not support specifying the color / bw flag.");
}
// inform the driver which fields have changed
devmode.dmFields = dmFieldsUpdated;
// our DEVMODE structure back to unamanged memory
Marshal.StructureToPtr<DEVMODE>(devmode, ptrDevmode, true);
// Merge our changes with the printers current changes, let the driver validate our settings, get the merged DEVMODE strucutre back as a pointer
var rslt = (DialogResultEnum)PrintApiGdi.DocumentProperties(IntPtr.Zero, hPrinter, printername, ptrDevmode, ptrDevmode, PrintApiGdi.DmBufferMode.DM_IN_BUFFER | PrintApiGdi.DmBufferMode.DM_OUT_BUFFER);
if (rslt != DialogResultEnum.IDOK)
{
throw new Exception("DocumentProperties returned NOK. DEVMODE structure problem");
}
success = ClosePrinter(hPrinter);
hPrinter = IntPtr.Zero;
if (success == false)
throw new Exception("ClosePrinter() returned false");
}
#endregion
#region Create print job & send to printer
// set default settings for this OpenPrinter session with our previously created DEVMODE
PRINTER_DEFAULTS pd = new PRINTER_DEFAULTS();
pd.pDevMode = ptrDevmode;
pd.DesiredAccess = PrinterAccessMode.PRINTER_ACCESS_USE;
// Open the printer.
if (OpenPrinter(printername, out hPrinter, ref pd)) // this is atm the only known way to "inject" our printer configuration in RAW mode without overwriting default printer settings.
{
// Start a print job in the spooler, returns job id
int printJobId = StartDocPrinter(hPrinter, 1, docInfo); // level must be 1, refers to DOC_INFO_1
if (printJobId > 0)
{
// Start a page.
if (StartPagePrinter(hPrinter))
{
int writtenBytes = 0;
success = WritePrinter(hPrinter, documentPtr, document.Length, out writtenBytes);
if (success == false)
throw new Exception($"WritePrinter returned false. Win32 Error: {Marshal.GetLastWin32Error()}");
if (document.Length != writtenBytes)
throw new Exception($"Not all document data was written in WritePrinter function. Win32 Error: {Marshal.GetLastWin32Error()}");
EndPagePrinter(hPrinter);
}
EndDocPrinter(hPrinter);
}
ClosePrinter(hPrinter);
}
else
{
new Exception($"OpenPrinterA API call in winspool.drv failed, propably wrong printer name. Win32 Error: {Marshal.GetLastWin32Error()}");
}
#endregion
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
finally
{
if (documentPtr != IntPtr.Zero)
Marshal.FreeHGlobal(documentPtr);
if (ptrDevmode != IntPtr.Zero)
Marshal.FreeHGlobal(ptrDevmode);
}
}
/// <summary>
/// Checks if the driver supports the given feature by checking the dmFields attribute of DEVMODE and applying a bitmask
/// </summary>
/// <param name="devmode"></param>
/// <param name="feature"></param>
/// <returns></returns>
public static bool IsFeatureSupported(PrintApiGdi.DEVMODE devmode, PrintApiGdi.DmFieldFeatures feature)
{
return ((devmode.dmFields & feature) == feature);
}
}
}