Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use PrintSpoolerAPI function SetForm() in C#

I am working on a Windows Forms Application in which I want to print a custom document. This custom document is of custom size and I have to set the default print page size using C# code.

I did some google search and came across PrintSpoolerAPI. The code which I found will add a custom form/page using AddForm() method to the available pages list for printing. I also want to set this newly added page as default printing page.

I tried writing this line in the code

bool x = SetForm(hPrinter, paperName, 1, ref formInfo);

It returns a true value but its not setting the default printing page.

Does dmPaperSize plays any role in this?

Here is the complete code:

using System;
using System.Text;
using System.Runtime.InteropServices;
using System.Security;
using System.ComponentModel;
using System.Drawing.Printing;

namespace WindowsFormsApplication1
{
    public class CustomPrintForm
    {
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        internal struct structPrinterDefaults
        {
            [MarshalAs(UnmanagedType.LPTStr)]
            public String pDatatype;
            public IntPtr pDevMode;
            [MarshalAs(UnmanagedType.I4)]
            public int DesiredAccess;
        };

    [DllImport("winspool.Drv", EntryPoint = "OpenPrinter", SetLastError = true,
         CharSet = CharSet.Unicode, ExactSpelling = false, CallingConvention = CallingConvention.StdCall),
    SuppressUnmanagedCodeSecurityAttribute()]
    internal static extern bool OpenPrinter([MarshalAs(UnmanagedType.LPTStr)]
        string printerName,
        out IntPtr phPrinter,
        ref structPrinterDefaults pd);

    [DllImport("winspool.Drv", EntryPoint = "ClosePrinter", SetLastError = true,
         CharSet = CharSet.Unicode, ExactSpelling = false,
         CallingConvention = CallingConvention.StdCall), SuppressUnmanagedCodeSecurityAttribute()]
    internal static extern bool ClosePrinter(IntPtr phPrinter);

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    internal struct structSize
    {
        public Int32 width;
        public Int32 height;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    internal struct structRect
    {
        public Int32 left;
        public Int32 top;
        public Int32 right;
        public Int32 bottom;
    }

    [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)]
    internal struct FormInfo1
    {
        [FieldOffset(0), MarshalAs(UnmanagedType.I4)]
        public uint Flags;
        [FieldOffset(4), MarshalAs(UnmanagedType.LPWStr)]
        public String pName;
        [FieldOffset(8)]
        public structSize Size;
        [FieldOffset(16)]
        public structRect ImageableArea;
    };

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi/* changed from CharSet=CharSet.Auto */)]
    internal struct structDevMode
    {
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public String
            dmDeviceName;
        [MarshalAs(UnmanagedType.U2)]
        public short dmSpecVersion;
        [MarshalAs(UnmanagedType.U2)]
        public short dmDriverVersion;
        [MarshalAs(UnmanagedType.U2)]
        public short dmSize;
        [MarshalAs(UnmanagedType.U2)]
        public short dmDriverExtra;
        [MarshalAs(UnmanagedType.U4)]
        public int dmFields;
        [MarshalAs(UnmanagedType.I2)]
        public short dmOrientation;
        [MarshalAs(UnmanagedType.I2)]
        public short dmPaperSize;
        [MarshalAs(UnmanagedType.I2)]
        public short dmPaperLength;
        [MarshalAs(UnmanagedType.I2)]
        public short dmPaperWidth;
        [MarshalAs(UnmanagedType.I2)]
        public short dmScale;
        [MarshalAs(UnmanagedType.I2)]
        public short dmCopies;
        [MarshalAs(UnmanagedType.I2)]
        public short dmDefaultSource;
        [MarshalAs(UnmanagedType.I2)]
        public short dmPrintQuality;
        [MarshalAs(UnmanagedType.I2)]
        public short dmColor;
        [MarshalAs(UnmanagedType.I2)]
        public short dmDuplex;
        [MarshalAs(UnmanagedType.I2)]
        public short dmYResolution;
        [MarshalAs(UnmanagedType.I2)]
        public short dmTTOption;
        [MarshalAs(UnmanagedType.I2)]
        public short dmCollate;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public String dmFormName;
        [MarshalAs(UnmanagedType.U2)]
        public short dmLogPixels;
        [MarshalAs(UnmanagedType.U4)]
        public int dmBitsPerPel;
        [MarshalAs(UnmanagedType.U4)]
        public int dmPelsWidth;
        [MarshalAs(UnmanagedType.U4)]
        public int dmPelsHeight;
        [MarshalAs(UnmanagedType.U4)]
        public int dmNup;
        [MarshalAs(UnmanagedType.U4)]
        public int dmDisplayFrequency;
        [MarshalAs(UnmanagedType.U4)]
        public int dmICMMethod;
        [MarshalAs(UnmanagedType.U4)]
        public int dmICMIntent;
        [MarshalAs(UnmanagedType.U4)]
        public int dmMediaType;
        [MarshalAs(UnmanagedType.U4)]
        public int dmDitherType;
        [MarshalAs(UnmanagedType.U4)]
        public int dmReserved1;
        [MarshalAs(UnmanagedType.U4)]
        public int dmReserved2;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    internal struct PRINTER_INFO_9
    {
        public IntPtr pDevMode;
    }

    [DllImport("winspool.Drv", EntryPoint = "AddFormW", SetLastError = true,
         CharSet = CharSet.Unicode, ExactSpelling = true,
         CallingConvention = CallingConvention.StdCall), SuppressUnmanagedCodeSecurityAttribute()]
    internal static extern bool AddForm(
     IntPtr phPrinter,
        [MarshalAs(UnmanagedType.I4)] int level,
     ref FormInfo1 form);

    [DllImport("winspool.Drv", EntryPoint = "SetForm", SetLastError = true,
         CharSet = CharSet.Unicode, ExactSpelling = false,
         CallingConvention = CallingConvention.StdCall), SuppressUnmanagedCodeSecurityAttribute()]
    internal static extern bool SetForm(IntPtr phPrinter, string paperName,
        [MarshalAs(UnmanagedType.I4)] int level, ref FormInfo1 form);

    [DllImport("winspool.Drv", EntryPoint = "DeleteForm", SetLastError = true,
         CharSet = CharSet.Unicode, ExactSpelling = false, CallingConvention = CallingConvention.StdCall),
    SuppressUnmanagedCodeSecurityAttribute()]
    internal static extern bool DeleteForm(
     IntPtr phPrinter,
        [MarshalAs(UnmanagedType.LPTStr)] string pName);

    [DllImport("kernel32.dll", EntryPoint = "GetLastError", SetLastError = false,
         ExactSpelling = true, CallingConvention = CallingConvention.StdCall),
    SuppressUnmanagedCodeSecurityAttribute()]
    internal static extern Int32 GetLastError();

    [DllImport("GDI32.dll", EntryPoint = "CreateDC", SetLastError = true,
         CharSet = CharSet.Unicode, ExactSpelling = false,
         CallingConvention = CallingConvention.StdCall),
    SuppressUnmanagedCodeSecurityAttribute()]
    internal static extern IntPtr CreateDC([MarshalAs(UnmanagedType.LPTStr)]
        string pDrive,
        [MarshalAs(UnmanagedType.LPTStr)] string pName,
        [MarshalAs(UnmanagedType.LPTStr)] string pOutput,
        ref structDevMode pDevMode);

    [DllImport("GDI32.dll", EntryPoint = "ResetDC", SetLastError = true,
         CharSet = CharSet.Unicode, ExactSpelling = false,
         CallingConvention = CallingConvention.StdCall),
    SuppressUnmanagedCodeSecurityAttribute()]
    internal static extern IntPtr ResetDC(
     IntPtr hDC,
     ref structDevMode
        pDevMode);

    [DllImport("GDI32.dll", EntryPoint = "DeleteDC", SetLastError = true,
         CharSet = CharSet.Unicode, ExactSpelling = false,
         CallingConvention = CallingConvention.StdCall),
    SuppressUnmanagedCodeSecurityAttribute()]
    internal static extern bool DeleteDC(IntPtr hDC);

    [DllImport("winspool.Drv", EntryPoint = "SetPrinterA", SetLastError = true,
        CharSet = CharSet.Auto, ExactSpelling = true,
        CallingConvention = CallingConvention.StdCall), SuppressUnmanagedCodeSecurityAttribute()]
    internal static extern bool SetPrinter(
       IntPtr hPrinter,
       [MarshalAs(UnmanagedType.I4)] int level,
       IntPtr pPrinter,
       [MarshalAs(UnmanagedType.I4)] int command);

    /*
     LONG DocumentProperties(
       HWND hWnd,               // handle to parent window 
       HANDLE hPrinter,         // handle to printer object
       LPTSTR pDeviceName,      // device name
       PDEVMODE pDevModeOutput, // modified device mode
       PDEVMODE pDevModeInput,  // original device mode
       DWORD fMode              // mode options
       );
     */
    [DllImport("winspool.Drv", EntryPoint = "DocumentPropertiesA", SetLastError = true,
    ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
    public static extern int DocumentProperties(
       IntPtr hwnd,
       IntPtr hPrinter,
       [MarshalAs(UnmanagedType.LPStr)] string pDeviceName /* changed from String to string */,
       IntPtr pDevModeOutput,
       IntPtr pDevModeInput,
       int fMode
       );

    [DllImport("winspool.Drv", EntryPoint = "GetPrinterA", SetLastError = true,
    ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
    public static extern bool GetPrinter(
       IntPtr hPrinter,
       int dwLevel /* changed type from Int32 */,
       IntPtr pPrinter,
       int dwBuf /* chagned from Int32*/,
       out int dwNeeded /* changed from Int32*/
       );

    // SendMessageTimeout tools
    [Flags]
    public enum SendMessageTimeoutFlags : uint
    {
        SMTO_NORMAL = 0x0000,
        SMTO_BLOCK = 0x0001,
        SMTO_ABORTIFHUNG = 0x0002,
        SMTO_NOTIMEOUTIFNOTHUNG = 0x0008
    }
    const int WM_SETTINGCHANGE = 0x001A;
    const int HWND_BROADCAST = 0xffff;

    [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    public static extern IntPtr SendMessageTimeout(
       IntPtr windowHandle,
       uint Msg,
       IntPtr wParam,
       IntPtr lParam,
       SendMessageTimeoutFlags flags,
       uint timeout,
       out IntPtr result
       );

    public static void AddMjm80MmPaperSizeToDefaultPrinter()
    {
        AddCustomPaperSizeToDefaultPrinter("MJM 80mm * Receipt Length", 80.1f, 4003.9f);
    }

    public static void AddMjm104MmPaperSizeToDefaultPrinter()
    {
        AddCustomPaperSizeToDefaultPrinter("MJM 104mm * Receipt Length", 104.1f, 4003.9f);
    }

    /// <summary>
    /// Adds the printer form to the default printer
    /// </summary>
    /// <param name="paperName">Name of the printer form</param>
    /// <param name="widthMm">Width given in millimeters</param>
    /// <param name="heightMm">Height given in millimeters</param>
    public static void AddCustomPaperSizeToDefaultPrinter(string paperName, float widthMm, float heightMm)
    {
        PrintDocument pd = new PrintDocument();
        string sPrinterName = pd.PrinterSettings.PrinterName;
        AddCustomPaperSize(sPrinterName, paperName, widthMm, heightMm);
    }

    /// <summary>
    /// Add the printer form to a printer 
    /// </summary>
    /// <param name="printerName">The printer name</param>
    /// <param name="paperName">Name of the printer form</param>
    /// <param name="widthMm">Width given in millimeters</param>
    /// <param name="heightMm">Height given in millimeters</param>
    public static void AddCustomPaperSize(string printerName, string paperName, float
        widthMm, float heightMm)
    {
        if (PlatformID.Win32NT == Environment.OSVersion.Platform)
        {
            // The code to add a custom paper size is different for Windows NT then it is
            // for previous versions of windows

            const int PRINTER_ACCESS_USE = 0x00000008;
            const int PRINTER_ACCESS_ADMINISTER = 0x00000004;
            const int FORM_PRINTER = 0x00000002;

            structPrinterDefaults defaults = new structPrinterDefaults();
            defaults.pDatatype = null;
            defaults.pDevMode = IntPtr.Zero;
            defaults.DesiredAccess = PRINTER_ACCESS_ADMINISTER | PRINTER_ACCESS_USE;

            IntPtr hPrinter = IntPtr.Zero;

            // Open the printer.
            if (OpenPrinter(printerName, out hPrinter, ref defaults))
            {
                try
                {
                    // delete the form incase it already exists
                    DeleteForm(hPrinter, paperName);
                    // create and initialize the FORM_INFO_1 structure
                    FormInfo1 formInfo = new FormInfo1();
                    formInfo.Flags = 0;
                    formInfo.pName = paperName;
                    // all sizes in 1000ths of millimeters
                    formInfo.Size.width = (int)(widthMm * 1000.0);
                    formInfo.Size.height = (int)(heightMm * 1000.0);
                    formInfo.ImageableArea.left = 0;
                    formInfo.ImageableArea.right = formInfo.Size.width;
                    formInfo.ImageableArea.top = 0;
                    formInfo.ImageableArea.bottom = formInfo.Size.height;
                    if (!AddForm(hPrinter, 1, ref formInfo))
                    {
                        StringBuilder strBuilder = new StringBuilder();
                        strBuilder.AppendFormat("Failed to add the custom paper size {0} to the printer {1}, System error number: {2}",
                            paperName, printerName, GetLastError());
                        throw new ApplicationException(strBuilder.ToString());
                    }
                    else
                    {
                        bool x = SetForm(hPrinter, paperName, 1, ref formInfo);
                    }

                    // INIT
                    const int DM_OUT_BUFFER = 2;
                    const int DM_IN_BUFFER = 8;
                    structDevMode devMode = new structDevMode();
                    IntPtr hPrinterInfo, hDummy;
                    PRINTER_INFO_9 printerInfo;
                    printerInfo.pDevMode = IntPtr.Zero;
                    int iPrinterInfoSize, iDummyInt;


                    // GET THE SIZE OF THE DEV_MODE BUFFER
                    int iDevModeSize = DocumentProperties(IntPtr.Zero, hPrinter, printerName, IntPtr.Zero, IntPtr.Zero, 0);

                    if (iDevModeSize < 0)
                        throw new ApplicationException("Cannot get the size of the DEVMODE structure.");

                    // ALLOCATE THE BUFFER
                    IntPtr hDevMode = Marshal.AllocCoTaskMem(iDevModeSize + 100);

                    // GET A POINTER TO THE DEV_MODE BUFFER 
                    int iRet = DocumentProperties(IntPtr.Zero, hPrinter, printerName, hDevMode, IntPtr.Zero, DM_OUT_BUFFER);

                    if (iRet < 0)
                        throw new ApplicationException("Cannot get the DEVMODE structure.");

                    // FILL THE DEV_MODE STRUCTURE
                    devMode = (structDevMode)Marshal.PtrToStructure(hDevMode, devMode.GetType());

                    // SET THE FORM NAME FIELDS TO INDICATE THAT THIS FIELD WILL BE MODIFIED
                    devMode.dmFields = 0x10000; // DM_FORMNAME 
                    // SET THE FORM NAME
                    devMode.dmFormName = paperName;

                    // PUT THE DEV_MODE STRUCTURE BACK INTO THE POINTER
                    Marshal.StructureToPtr(devMode, hDevMode, true);

                    // MERGE THE NEW CHAGES WITH THE OLD
                    iRet = DocumentProperties(IntPtr.Zero, hPrinter, printerName,
                             printerInfo.pDevMode, printerInfo.pDevMode, DM_IN_BUFFER | DM_OUT_BUFFER);

                    if (iRet < 0)
                        throw new ApplicationException("Unable to set the orientation setting for this printer.");

                    // GET THE PRINTER INFO SIZE
                    GetPrinter(hPrinter, 9, IntPtr.Zero, 0, out iPrinterInfoSize);
                    if (iPrinterInfoSize == 0)
                        throw new ApplicationException("GetPrinter failed. Couldn't get the # bytes needed for shared PRINTER_INFO_9 structure");

                    // ALLOCATE THE BUFFER
                    hPrinterInfo = Marshal.AllocCoTaskMem(iPrinterInfoSize + 100);

                    // GET A POINTER TO THE PRINTER INFO BUFFER
                    bool bSuccess = GetPrinter(hPrinter, 9, hPrinterInfo, iPrinterInfoSize, out iDummyInt);

                    if (!bSuccess)
                        throw new ApplicationException("GetPrinter failed. Couldn't get the shared PRINTER_INFO_9 structure");

                    // FILL THE PRINTER INFO STRUCTURE
                    printerInfo = (PRINTER_INFO_9)Marshal.PtrToStructure(hPrinterInfo, printerInfo.GetType());
                    printerInfo.pDevMode = hDevMode;

                    // GET A POINTER TO THE PRINTER INFO STRUCTURE
                    Marshal.StructureToPtr(printerInfo, hPrinterInfo, true);

                    // SET THE PRINTER SETTINGS
                    bSuccess = SetPrinter(hPrinter, 9, hPrinterInfo, 0);

                    if (!bSuccess)
                        throw new Win32Exception(Marshal.GetLastWin32Error(), "SetPrinter() failed.  Couldn't set the printer settings");

                    // Tell all open programs that this change occurred.
                    SendMessageTimeout(new IntPtr(HWND_BROADCAST), WM_SETTINGCHANGE, IntPtr.Zero, IntPtr.Zero, CustomPrintForm.SendMessageTimeoutFlags.SMTO_NORMAL, 1000, out hDummy);


                }
                finally
                {
                    ClosePrinter(hPrinter);
                }
            }
            else
            {
                StringBuilder strBuilder = new StringBuilder();
                strBuilder.AppendFormat("Failed to open the {0} printer, System error number: {1}",
                    printerName, GetLastError());
                throw new ApplicationException(strBuilder.ToString());
            }
        }
        else
        {
            structDevMode pDevMode = new structDevMode();
            IntPtr hDC = CreateDC(null, printerName, null, ref pDevMode);
            if (hDC != IntPtr.Zero)
            {
                const long DM_PAPERSIZE = 0x00000002L;
                const long DM_PAPERLENGTH = 0x00000004L;
                const long DM_PAPERWIDTH = 0x00000008L;
                pDevMode.dmFields = (int)(DM_PAPERSIZE | DM_PAPERWIDTH | DM_PAPERLENGTH);
                pDevMode.dmPaperSize = 256;
                pDevMode.dmPaperWidth = (short)(widthMm * 1000.0);
                pDevMode.dmPaperLength = (short)(heightMm * 1000.0);
                ResetDC(hDC, ref pDevMode);
                DeleteDC(hDC);
            }
        }
    }
}

}

Need help in solving this issue.

like image 490
Nilesh Barai Avatar asked Jan 08 '15 22:01

Nilesh Barai


2 Answers

Your code is very nearly flawless (needs work in 64-bit mode) and does what it intends to do:

enter image description here

That's however where the good news ends. The api you are using talks directly to the printer driver and it is responsible for ensuring that your DEVMODE changes become effective. That works fine for the XPS printer driver, not fine for many other kind of drivers. Like the Hewlett Packard driver I also tried.

Everything you do has an immediately visible side-effect in the registry. You can see it with Regedit.exe. Which is a good way to double-check your code. And to see that it you need to stop trying because the printer driver will not co-operate. The keys you want to look at:

  • HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print\Forms. This key lists the form you add. You should not have a problem with that one.

  • HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print\Forms\MJM 80mm * Receipt Length, "FormKeyword" value. Write this down, that's the "keyword" by which your form is selected by the printer.

  • HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print\Printers\printername, "Default DevMode" value. This is written when you call SetPrinter(). You can look at the hex dump of the value. The FormKeyword you written down in the previous step is visible at offset 0x348

  • HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print\Printers\printername\DsDriver, printMediaSupported value. Not entirely sure about this one, but should show a list of paper sizes and your custom form ought to be visible there.

Sorry for the bad news, printers suck.

like image 80
Hans Passant Avatar answered Nov 05 '22 10:11

Hans Passant


for x64 use

[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)]
        internal struct FormInfo1
        {
            [FieldOffset(0), MarshalAs(UnmanagedType.I4)]
            public uint Flags;
            [FieldOffset(8), MarshalAs(UnmanagedType.LPWStr)]
            public String pName;
            [FieldOffset(16)]
            public structSize Size;
            [FieldOffset(24)]
            public structRect ImageableArea;
        };
like image 20
Luigimon Carrillo Avatar answered Nov 05 '22 10:11

Luigimon Carrillo