我们有一个作为Windows服务运行的打印服务。它通过REST API接收打印作业-带有某些打印机设置的PDF(例如哪个进纸匣,彩色或黑白)。 我们正在winspool.drv中使用Print Spooler API,以RAW模式将PDF发送到打印机。
我们设置PRINTER_DEFAULTS结构,并使用与接收到的打印机设置相对应的预配置DEVMODE结构设置pDevMode字段。
然后,我们将PRINTER_DEFAULTS传递给OpenPrinter
函数。
最后,我们使用WritePrinter(..)
函数发送PDF。
一切似乎都正常,文档显示在打印机队列中,打印机作业设置与我们的DEVMODE结构设置匹配,但打印机忽略作业特定的打印机设置,它将使用打印机的默认设置进行打印。
如果我使用SetPrinter
功能设置了打印机的默认设置,那么它将以所需的配置进行打印,但这对于我们的用例来说不是一个合适的解决方案,因为我们需要特定于打印作业的设置。
您是否知道我们的代码中是否有错误,或者在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);
}
}
}