C# winspool.drv call to WritePrinter not printing

2019-07-20 14:16发布

问题:

I'm using some code I found online which was a solution to someone else's similar printing problem. The code appears to run fine, and even errors out when I would expect it to (for example, when I purposefully enter in a bad printer name). The problem I'm having is that the interop call to winspool.drv's WritePrinter method does not seem to cause the printer to print anything, even though this method returns "true". Any ideas why the printer isn't actually printing???

public class PrintRaw
    {
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        public class DOC_INFO_1
        {
            [MarshalAs(UnmanagedType.LPStr)]
            public string pDocName;
            [MarshalAs(UnmanagedType.LPStr)]
            public string pOutputFile;
            [MarshalAs(UnmanagedType.LPStr)]
            public string pDataType;
        }

        [DllImport("winspool.Drv", EntryPoint = "OpenPrinterA", SetLastError = true, CharSet = CharSet.Ansi,
          ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
        public static extern bool OpenPrinter([MarshalAs(UnmanagedType.LPStr)] string szPrinter, out IntPtr hPrinter,
                                              IntPtr pd);

        [DllImport("winspool.Drv", EntryPoint = "ClosePrinter", SetLastError = true, ExactSpelling = true,
          CallingConvention = CallingConvention.StdCall)]
        public static extern bool ClosePrinter(IntPtr hPrinter);

        [DllImport("winspool.Drv", EntryPoint = "StartDocPrinterA", SetLastError = true, CharSet = CharSet.Ansi,
          ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
        public static extern bool StartDocPrinter(IntPtr hPrinter, Int32 level,
                                                  [In, MarshalAs(UnmanagedType.LPStruct)] DOC_INFO_1 di);

        [DllImport("winspool.Drv", EntryPoint = "EndDocPrinter", SetLastError = true, ExactSpelling = true,
          CallingConvention = CallingConvention.StdCall)]
        public static extern bool EndDocPrinter(IntPtr hPrinter);

        [DllImport("winspool.Drv", EntryPoint = "StartPagePrinter", SetLastError = true, ExactSpelling = true,
          CallingConvention = CallingConvention.StdCall)]
        public static extern bool StartPagePrinter(IntPtr hPrinter);

        [DllImport("winspool.Drv", EntryPoint = "EndPagePrinter", SetLastError = true, ExactSpelling = true,
          CallingConvention = CallingConvention.StdCall)]
        public static extern bool EndPagePrinter(IntPtr hPrinter);

        [DllImport("winspool.Drv", EntryPoint = "WritePrinter", SetLastError = true, ExactSpelling = true,
          CallingConvention = CallingConvention.StdCall)]
        public static extern bool WritePrinter(IntPtr hPrinter, IntPtr pBytes, Int32 dwCount, out Int32 dwWritten);

        public void Print(String printerName, String filename)
        {
            IntPtr lhPrinter;
            OpenPrinter(printerName, out lhPrinter, new IntPtr(0));

            if (lhPrinter.ToInt32() == 0)
                return; //Printer not found!!

            var rawPrinter = new DOC_INFO_1() { pDocName = "My Document", pDataType = "RAW" };

            StartDocPrinter(lhPrinter, 1, rawPrinter);

            using (var b = new BinaryReader(File.Open(filename, FileMode.Open)))
            {
                var length = (int)b.BaseStream.Length;
                const int bufferSize = 8192;

                var numLoops = length / bufferSize;
                var leftOver = length % bufferSize;


                for (int i = 0; i < numLoops; i++)
                {
                    var buffer = new byte[bufferSize];
                    int dwWritten;

                    b.Read(buffer, 0, bufferSize);
                    IntPtr unmanagedPointer = Marshal.AllocHGlobal(buffer.Length);
                    Marshal.Copy(buffer, 0, unmanagedPointer, buffer.Length);
                    WritePrinter(lhPrinter, unmanagedPointer, bufferSize, out dwWritten);
                    Marshal.FreeHGlobal(unmanagedPointer);
                }

                if (leftOver > 0)
                {
                    var buffer = new byte[leftOver];
                    int dwWritten;

                    b.Read(buffer, 0, leftOver);
                    IntPtr unmanagedPointer = Marshal.AllocHGlobal(buffer.Length);
                    Marshal.Copy(buffer, 0, unmanagedPointer, buffer.Length);
                    var result = WritePrinter(lhPrinter, unmanagedPointer, leftOver, out dwWritten);
                    Marshal.FreeHGlobal(unmanagedPointer);
                }
            }

            EndDocPrinter(lhPrinter);
            ClosePrinter(lhPrinter);
        }
    }

回答1:

In my case I found the solution changing the pDataType

For Win7 use RAW

For Win8+ use XPS_PASS

Example:

// SendBytesToPrinter()
// When the function is given a printer name and an unmanaged array
// of bytes, the function sends those bytes to the print queue.
// Returns true on success, false on failure.
private static bool SendBytesToPrinter(string szPrinterName, IntPtr pBytes, Int32 dwCount)
{
    bool bSuccess = false; // Assume failure unless you specifically succeed.
    try
    {
        Int32 dwError = 0, dwWritten = 0;
        IntPtr hPrinter = new IntPtr(0);
        DOCINFOA di = new DOCINFOA();

        di.pDocName = "RAW Document";
        // Win7
        //di.pDataType = "RAW";

        // Win8+
        di.pDataType = "XPS_PASS";

        // Open the printer.
        if (OpenPrinter(szPrinterName.Normalize(), out hPrinter, IntPtr.Zero))
        {
            // Start a document.
            if (StartDocPrinter(hPrinter, 1, di))
            {
                // Start a page.
                if (StartPagePrinter(hPrinter))
                {
                    // Write your bytes.
                    bSuccess = WritePrinter(hPrinter, pBytes, dwCount, out dwWritten);
                    EndPagePrinter(hPrinter);
                }
                EndDocPrinter(hPrinter);
            }
            ClosePrinter(hPrinter);
        }
        // If you did not succeed, GetLastError may give more information
        // about why not.
        if (!bSuccess)
        {
            dwError = Marshal.GetLastWin32Error();
        }
    }
    catch { }
    return bSuccess;
}


回答2:

Change your code like this:

 var rawPrinter = new DOC_INFO_1() { pDocName = "My Document", pDataType = "Text" };


回答3:

You need to call StartPagePrinter after StartDocPrinter and before WritePrinter



回答4:

[DllImport("winspool.drv", EntryPoint = "FlushPrinter", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool FlushPrinter(IntPtr hPrinter, IntPtr pBuf, Int32 cbBuf, out Int32 pcWritten, Int32 cSleep);

WritePrinter(lhPrinter, unmanagedPointer, bufferSize, out dwWritten);

after add:

FlushPrinter(hPrinter, pBytes, dwCount, out dwWritten,2);