Windows print spooler API - Print RAW, DEVMODE Printer settings ignored

Hi everybody,

We have a print service running as a windows service. It receives print jobs via REST API - a PDF with some printer settings (e.g. which tray, color or Blackwhite, …).
We are using the Print Spooler API in winspool.drv to send the PDF in RAW mode to the printer.

We set the PRINTER_DEFAULTS structure, set the pDevMode field with our preconfigured DEVMODE structure which corresponds to the printer settings received.
Then we pass the PRINTER_DEFAULTS to the OpenPrinter Function.
Finally we send the PDF with the WritePrinter(…) function.

Everything seems to work, the Document shows up in the printer queue, the printer job settings match our DEVMODE structure settings, BUT the printer ignores the job specific printer settings, it gets printed with the printer’s default settings.

If i set the printer defaults settings with the SetPrinter function, then it gets printed in the desired configuration, but this is no a proper solution for our usecase, because we need print job specific settings.

Do you have any idea if there is a bug in our code or is this behaviour normal in the RAW mode?
Do you have any idea how to workaround this problem?

Thank you very much!

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);
        }
    }
}

.NET Foundation Website | Blog | Projects | Code of Conduct