Windows API очереди печати - печать RAW, настройки принтера DEVMODE игнорируются - PullRequest
0 голосов
/ 20 марта 2020

У нас есть служба печати, работающая как 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);
        }
    }
}
...