Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Mike Lewis on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Streaming a file to a local printer

Status
Not open for further replies.

JScannell

Programmer
Jan 9, 2001
306
US
I have an application that creates a file (it happens to be a PDF file, but my problem occurs with .DOCX and .XLSX files). After creating it, it sends it to your default printer. That's where it gets a little crazy.

If the default printer is a system network one, the printer starts immediately after the file is streamed to it. If it is a locally connected printer, the streaming is immediate, but the printer doesn't start up for several minutes.

To test the connectivity of the printer, I can separately open up the PDF with my default PDF reader and print it and it is instantaneous.

Another test I performed is printing via another application that uses the PrintDocument method and it is immediately printed. I don't know where the problem is, but this is my code that performs the streaming:
public static class myPrinters
{
[DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool SetDefaultPrinter(string Name);

}


private string SendToPrinter ( string strFileName, string strPrinterName )
{
FileInfo f = new FileInfo ( strFileName );
string fullname = f.FullName;

byte[] file = File.ReadAllBytes ( fullname );

// This is my ideal scenario in case the user wants to print to a different printer.
// I have tried with this line commented out and get the same results.
//
myPrinters.SetDefaultPrinter( strPrinterName );

PrintQueue printQueue = LocalPrintServer.GetDefaultPrintQueue();

using (var job = printQueue.AddJob())
using (var stream = job.JobStream)
{
stream.Write ( file, 0, file.Length );
}

Thanks in advance,
Jerry

Jerry Scannell
 
I'm not sure, but this sounds to me like a printer issue. Did a google search for local printer delay, and found this:
On server or desktops of your users (if you are not using a print server you should think of doing that) go to Start > Devices > look for the printer having issues - right-click go to Printer Properties (not properties specifically "Printer Properties") on the Advanced Tab make sure "Start printing immediately" is checked.

(In Windows 10, you find this by opening your start screen, typing "Printers," opening "Printers and Scanners," left-clicking on your default printer in the list, go to "Manage" then "Printer properties," then follow the Advanced Tab instruction above.)

Katie
 
Hrmmm..... how is this supposed to work?

Printers speak a different language than files....
Unless the file is in a printer output file, I wouldn't expect it to work.
For example, if you had a BMP file, you can't just write that BMP out to the printer, the printer wouldn't know it was a BMP file.

You need to load the program as an object, then load the file into the object, then print from the object. You can't just send bytes from assorted files to a printer and have it work, unless it's a pre-formatted for that printer file.



Just my $.02

"What the captain doesn't realize is that we've secretly replaced his Dilithium Crystals with new Folger's Crystals."

--Greg
 
JScannel, your approach is not wrong, but you should explicitly P/Invoke at least seven different Windows subroutines in a specific nested order like XML: openprinter, opendoc, openpage, writebytes, closepage, closedoc, closeprinter. I speculate that your printer driver is waiting for your C# code to close all (implicitly?) opened sections. I speculate that your printer driver is closing these sections for you after a timeout. My recommendation is to debug using winspool.drv DocumentEvent because this should expose to you the order and the time of each open and close event.

For the benefit of those who do not have your working code, two of the native subroutines are:

Code:
[DllImport("winspool.drv",SetLastError=true)]
static extern int OpenPrinter(string pPrinterName, out IntPtr phPrinter, IntPtr pDefault);

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

Usage example:

Code:
    // From: [URL unfurl="true"]http://support.microsoft.com/kb/322091[/URL]
    // 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.
    public static bool SendBytesToPrinter( string szPrinterName, IntPtr pBytes, Int32 dwCount)
    {
    Int32    dwError = 0, dwWritten = 0;
    IntPtr    hPrinter = new IntPtr(0);
    DOCINFOA    di = new DOCINFOA();
    bool    bSuccess = false; // Assume failure unless you specifically succeed.

    di.pDocName = "My C#.NET RAW Document";
    di.pDataType = "RAW";

    // Open the printer.
    if( OpenPrinter( szPrinterName.Normalize(), out hPrinter, IntPtr.Zero ) )
    {
        // Start a document. [URL unfurl="true"]https://msdn.microsoft.com/en-us/library/windows/desktop/dd145115(v=vs.85).aspx[/URL]
        if( StartDocPrinter(hPrinter, 1, di) )
        {
        // Start a page. [URL unfurl="true"]https://msdn.microsoft.com/en-us/library/windows/desktop/dd145117(v=vs.85).aspx[/URL]
        if( StartPagePrinter(hPrinter) )
        {
            // Write your bytes.
            bSuccess = WritePrinter(hPrinter, pBytes, dwCount, out dwWritten);
            EndPagePrinter(hPrinter); // [URL unfurl="true"]https://msdn.microsoft.com/en-us/library/windows/desktop/dd162597(v=vs.85).aspx[/URL]
        }
        EndDocPrinter(hPrinter); // [URL unfurl="true"]https://msdn.microsoft.com/en-us/library/windows/desktop/dd162595(v=vs.85).aspx[/URL]
        }
        ClosePrinter(hPrinter); // [URL unfurl="true"]https://msdn.microsoft.com/en-us/library/windows/desktop/dd183446(v=vs.85).aspx[/URL]
    }
    // If you did not succeed, GetLastError may give more information
    // about why not.
    if( bSuccess == false )
    {
        dwError = Marshal.GetLastWin32Error();
    }
    return bSuccess;
    }

The full list of relevant native subroutines that you can P/Invoke is documented here:
A potentially easier approach to P/Invoke is the Windows Driver Kit available at with C# Printer demo at but in my experience the native methods are more performant and more empowering.

The biggest risk I foresee is the Garbage Collector freeing the managed bytes before the unmanaged printer driver has stopped reading them. The WDK should prevent this from happening, but in my experience Microsoft's (other) managed assemblies work better during lab unit tests than they do in the wild. The wild can stress apps with a mix of unexpectedly large data sets and unexpected order/frequency of user commands. When the GC frees something that the native libraries later read, its the assemblies that misbehaved, and its the user interface that gets blamed. Many programmers disagree with me, but I would put faith in the P/Invoke approach because that empowers us to try and fix some unforeseen memory issues.
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top