Windows Print Spooler API-打印RAW,忽略DEVMODE打印机设置

时间:2020-03-20 13:44:59

标签: c# pdf printing interop spooler

我们有一个作为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);
        }
    }
}

0 个答案:

没有答案