c# - 在 C# 中使用 PrintSpoolerAPI 函数 SetForm()

标签 c# printing print-spooler-api

我正在开发一个 Windows 窗体应用程序,我想在其中打印自定义文档。此自定义文档具有自定义大小,我必须使用 C# 代码设置默认打印页面大小。

我进行了一些谷歌搜索,发现了 PrintSpoolerAPI。我找到的代码将使用 AddForm() 方法将自定义表单/页面添加到可用页面列表以进行打印。 我也想将这个新添加的页面设置为默认打印页面。

我试着在代码中写下这一行

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

它返回一个 true 值,但它没有设置默认打印页面。

dmPaperSize 在其中有什么作用吗?

完整代码如下:

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

需要帮助解决这个问题。

最佳答案

您的代码几乎完美无缺(需要在 64 位模式下工作)并且完成了它打算做的事情:

enter image description here

然而,好消息就到此为止了。您正在使用的 api 直接与打印机驱动程序对话,它负责确保您的 DEVMODE 更改生效。这适用于 XPS 打印机驱动程序,但不适用于许多其他类型的驱动程序。像惠普的驱动我也试过了。

您所做的一切都会在注册表中产生立即可见的副作用。您可以使用 Regedit.exe 查看它。这是仔细检查代码的好方法。要看到它,您需要停止尝试,因为打印机驱动程序不会合作。您要查看的键:

  • HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print\Forms。此键列出您添加的表单。你不应该有那个问题。

  • HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print\Forms\MJM 80mm * 收据长度,“FormKeyword”值。记下来,这是打印机选择您的表格的“关键字”。

  • HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print\Printers\printername,“默认 DevMode”值。这是在您调用 SetPrinter() 时写入的。您可以查看该值的十六进制转储。您在上一步中记下的 FormKeyword 在偏移量 0x348 处可见

  • HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print\Printers\printername\DsDriver,printMediaSupported 值。对此不完全确定,但应该显示纸张尺寸列表,并且您的自定义表单应该在那里可见。

很抱歉坏消息,打印机很烂。

关于c# - 在 C# 中使用 PrintSpoolerAPI 函数 SetForm(),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27850433/

相关文章:

c# - Process.Start 使用 UAC on 的不同凭据

c++ - 打印作业时,最后的作业状态是 JOB_STATUS_RETAINED,而不是 JOB_STATUS_PRINTED

c++ - 无法获得状态为 JOB_STATUS_DELETED 的打印作业通知

java - 打印所有 JVM 标志

c# - Mono 是否支持 System.Drawing 和 System.Drawing.Printing?

c# - 仅打印嵌入到 ASP.NET 页面中的图像

c++ - 如何以编程方式在 Windows 上获取给定制造商的所有打印机型号?

c# - 使用 SQL 具有不同日期的复合条目

c# - 将 null 分配给 SqlParameter

c# - 组合框出于某种原因被链接