1

Hopefully somebody here can help me out as I am banging my head against a wall trying to get this to work. Basically I need to print using the winspool.Drv win32 API to send raw text/PCL commands to the printer. This works fine, however, none of the printer options you choose in the printer dialog / preferences do anything.

Eventually I want to get the DEVMODE from the dialogs PrinterSettings, and use that for the raw printing, but I cannot even get it to work setting it manually.

I have tried changing the DEVMODE right before WritePrinter, or before in a separate Open/Close printer, I have tried using only DocumentProperties calls, and using GetPrinter/SetPrinter with a PRINTER_INFO_8 structure, nothing sticks. The DEVMODE structure seems to fill out correctly, I change it, and it seems to change correctly, but the printer always prints the same exact way no matter what.

Here is the code ive been using:

Method1:

public static bool SetPrinterSettings(PrinterSettings settings, IntPtr hPrinter)
{
    IntPtr hDevMode; // a handle to our current DEVMODE
    IntPtr pDevMode; // a pointer to our current DEVMODE
    String sPrinterName; // normalized printer name
    DEVMODE dm;

    // Setup
    sPrinterName = settings.PrinterName.Normalize();

    // Obtain the current DEVMODE from the PrinterSettings
    hDevMode = settings.GetHdevmode(settings.DefaultPageSettings);

    // Obtain a lock on the handle and get an actual pointer so Windows won't move
    // it around while we're futzing with it
    pDevMode = GlobalLock(hDevMode);

    // test
    dm = (DEVMODE)Marshal.PtrToStructure(pDevMode, typeof(DEVMODE));

    // Our DEVMODE is 188 bytes, but dmSize returns 220, does this matter?
    // This code doesnt seem to work
    //dm.dmSize = (short)Marshal.SizeOf(dm); // Set size to our implementation of DevMode
    //int isize = GlobalSize(pDevMode).ToInt32() - (int)dm.dmSize; // Set the print drivers extra size
    //dm.dmDriverExtra = Convert.ToInt16(isize);

    // Change things
    dm.dmFields = DM_FIELD_TYPE.DM_ORIENTATION;
    dm.dmOrientation = 2;

    // Load the structure back into the buffer
    Marshal.StructureToPtr(dm, pDevMode, true);

    //Tell the printer about the new property
    int ret = DocumentProperties(IntPtr.Zero, hPrinter, sPrinterName, pDevMode, pDevMode, (DM_IN_BUFFER | DM_OUT_BUFFER));

    if (ret <= 0)
    {
        return false;
    }



    // We're done futzing
    GlobalUnlock(hDevMode);

    // Tell our printer settings to use the one we just overwrote
    //settings.SetHdevmode(hDevMode);

    // It's copied to our printer settings, so we can free the OS-level one
    GlobalFree(hDevMode);

    //test
    dm = (DEVMODE)Marshal.PtrToStructure(pDevMode, typeof(DEVMODE));

    // ^^ this contains correct orientation

    return true;
}

Method2:

public static bool SetLandscapeMode(PrinterSettings settings, IntPtr hPrinter)
{
    // Setup
    String sPrinterName = settings.PrinterName.Normalize();

    // get current printer settings
    int memNeeded = DocumentProperties(IntPtr.Zero, hPrinter, sPrinterName, IntPtr.Zero, IntPtr.Zero, 0);
    IntPtr pFullDevMode = Marshal.AllocHGlobal(memNeeded);
    DocumentProperties(IntPtr.Zero, hPrinter, sPrinterName, pFullDevMode, IntPtr.Zero, DM_OUT_BUFFER);

    DEVMODE dm = (DEVMODE)Marshal.PtrToStructure(pFullDevMode, typeof(DEVMODE));

    // change the settings
    dm.dmFields = DM_FIELD_TYPE.DM_ORIENTATION;
    dm.dmOrientation = 2;

    Marshal.StructureToPtr(dm, pFullDevMode, true);

    PRINTER_INFO_8 PI8 = new PRINTER_INFO_8();
    PI8.pDevMode = pFullDevMode;

    IntPtr pPI8 = Marshal.AllocHGlobal(Marshal.SizeOf(PI8));

    Marshal.StructureToPtr(PI8, pPI8, true);

    // save the printer settings
    SetPrinter(hPrinter, 9, pPI8, 0);

    Marshal.FreeHGlobal(pPI8);

    return true;
}

Code that tests the printing:

private void testPrint_Click(object sender, EventArgs e)
{
    IntPtr hPrinter;

    if (printDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK)
    {
        hPrinter = RawPrint.OpenRAWPrinter(printDialog1.PrinterSettings.PrinterName);

        if (hPrinter == IntPtr.Zero)
        {
            MessageBox.Show("Error opening printer");
            return;
        }

        if (!RawPrint.StartRAWDocument(hPrinter, "Test document"))
        {
            MessageBox.Show("Error starting raw document");
            RawPrint.CloseRAWPrinter(hPrinter);
            return;
        }

        // Start a page.
        if (RawPrint.StartRawPage(hPrinter))
        {
            // Set the printers settings
            //if (!RawPrint.SetPrinterSettings(printDialog1.PrinterSettings, hPrinter))
            if(!RawPrint.SetLandscapeMode(printDialog1.PrinterSettings, hPrinter))
            {
                MessageBox.Show("Error setting printer settings");
                RawPrint.CloseRAWPrinter(hPrinter);
                return;
            }

            String test = "This is \r\n test print data";
            byte[] buf = Encoding.Default.GetBytes(test);

            GCHandle gch = GCHandle.Alloc(buf, GCHandleType.Pinned);
            RawPrint.writeRAW(hPrinter, gch.AddrOfPinnedObject(), buf.Length);
            gch.Free();

            RawPrint.EndRawPage(hPrinter);
        }

        RawPrint.EndRAWDocument(hPrinter);
        RawPrint.CloseRAWPrinter(hPrinter);
    }
    else
    {
        MessageBox.Show("Error starting raw page");
    }
}

The only odd thing is that Marshal.SizeOf(typeof(DEVMODE)) or Marshal.SizeOf(dm) equals 188 whereas the dmSize that is returned into the structure is 220. Ive tried modifying this dmSize to match our structure as shown in the commented out code, but that seems to error out or doesnt work, not sure if that matters.

Besides that the returned info in the DevModes all seems to be correct and working. I can set the options in the print dialog, and see them changed in the PrinterSettings devmode. And at the end of these functions, Marshalling the pDevMode back to a struct has the changed settings.

However, none of this seems to have any effect on the print what so ever. Is there something im missing?

EDIT: The RawPrint calls in the test function are just wrappers around the spooler api calls. The page prints fine, it just always prints in portrait mode and no options work.

John Saunders
  • 160,644
  • 26
  • 247
  • 397
Joshjje
  • 220
  • 4
  • 14
  • 2
    You are digging yourself a pretty big hole. DEVMODE is a nasty struct, it is variable length with extra bytes padded at the end where the printer driver stores its own config. If you are truly using raw printer output, OpenPrinter etc, then changing DEVMODE will not have any effect, you are bypassing the printer driver. It is then completely up to you to rotate output. Quite painful if the printer itself doesn't support it. The kind where bypassing the driver makes sense never do. – Hans Passant Mar 18 '13 at 15:24
  • Yeah, I realize theres extra driver info at the end of the DEVMODE, but the code handles that, or is supposed to. Makes sense though what you are saying since this is by passing the driver. Is there no way to use the spool api (WritePrinter) and set some of these options such as Landscape mode, or the output tray (driver specific i know). Does this need to be done manually in PCL code, or is there an easier way to send PCL to the printer while using the driver? – Joshjje Mar 18 '13 at 15:42
  • 2
    Yes. A very simple way to generate the right PCL is to not bypass the driver. – Hans Passant Mar 18 '13 at 16:09
  • What @HansPassant said about not bypassing the driver if you want correct PCL. If you need to modify the resulting PCL, you need a port monitor or language monitor. – Carey Gregory Mar 18 '13 at 16:12
  • @HansPassant : hmm, is there some way to capture what the PCL would be for the chosen dialog options, and output that before the regular PCL that I need to send? Seems like there should be an easy solution to this :(. Or some way to use the driver, but still send custom commands. – Joshjje Mar 18 '13 at 16:31
  • I have edited your title. Please see, "[Should questions include “tags” in their titles?](http://meta.stackexchange.com/questions/19190/)", where the consensus is "no, they should not". – John Saunders Mar 18 '13 at 20:00
  • @Joshjje To answer your question to Hans: No. As I said before, what you're trying to do is what port and language monitors are for. Trying to force the logic into an application is pretty much doomed to failure. – Carey Gregory Mar 18 '13 at 22:47

1 Answers1

1

Rather than bypassing the printer driver by using WritePrinter, you should start the print job normally and inject the PCL using ExtEscape() and the PASSTHROUGH escape. This will allow all PrinterSettings values to apply to the job. Not all printer drivers support PASSTHROUGH, but you can check for the driver support by calling ExtEscape() with QUERYESCSUPPORT.

Craig Lebakken
  • 1,853
  • 15
  • 10
  • Sure, that will work for some printers, but not all, as you noted. And it won't stop the print driver from producing its normal output, so all you can do is inject into the middle of it. You can't affect the driver's output with `PASSTHROUGH`. It's really kind of a 1980s solution that should have been deprecated 20 years ago. – Carey Gregory Mar 19 '13 at 01:30