Issue 4 - 08 September 2009 Go Back

Control Printers Programmatically using Borland Delphi and C# using DevMode,SetPrinter,GetPrinter,GlobalLock,GlobalUnlock

Recently I was working on some projects for production to built end of line testers (EOLT) applications. In production environment it’s very important to find a way to avoid any interference from operators. If a printer requires reinstallation it’s critical to continue the production process with minimum delay.   

There were a number of printers connected to the same computer to print labels and reports. In addition to that the system had to recognise if the required printers are connected and switched on. It would be messy and slow for an operator to choose printers every time to print labels or test reports. So the only solution was to get the direct control on all the connected printers.

My first project with EOLT was done using Borland Delphi and second time I used C#. Please note that you need to play with printer settings, using control panel in order to finalised correct settings for the connected printers. You need to set printer names, paper sources and so on. after the initial manual setup is complete you can read and save all settings and restore them at any time. All you need to use are existing functions like GlobalLock, GlobalUnlock, SetPrinter, GetPrinter, OpenPrinter, ClosePrinter and WritePrinter.

For my first Delphi project I read all settings and hardcoded them into the application. Also I added the register keys as the following "\System\CurrentControlSet\Control\Print\Printers\PassLabels", where "PassLabels" is the name of the printer attached to computer. So far it is running for over two years without any complaints. For my second C# project I’ve decided to read and save all settings into a text file. Every time the main application starts, it reads the file and updates all printer settings before further processing. In my view having an external file gives more freedom in case you need to replace printer with different type. It would require just a replacing of the txt file with correct settings for the new printer and there are no need to recompile the entire application. Anyway,the time will tell which way is better and more practical.

In case if you like to compile the code you will need to add uses Printers, Registry, WinSpool And define the following in your Form definitions LabelPrinter : TPrinter;

The following procedure written in Borland Delphi and shows how to use the register keys and how to print any text at any position on the label or page.

procedure PrintLabel;
var
myYear, myMonth, myDay : Word;
PrintStr : String;
PrinterIsOnLine : Boolean;
begin
    PrinterIsOnLine:= false;
// here we check if printer is ready by reading register key.
    with TRegistry.Create do
      try
        RootKey := HKEY_CURRENT_CONFIG;
        if OpenKey('\System\CurrentControlSet\Control\Print\Printers\LabelsPrinter', False) then
                PrinterIsOnLine:= ReadBool('PrinterOnLine');
        CloseKey;
      finally
        Free;
    end;

// printer is off or not connected, so you need to take care of this situation in your own application.
    if PrinterIsOnLine = false then
        exit;

// here we are printing our information by setting positions of each line on the label ( page ). It is //just example and positions are hard coded.
with LabelPrinter do  
    begin
      // Start printing
      BeginDoc;
      // Set up font and size
      Canvas.Font.Name := ‘Arial’;
      Canvas.Font.Size := 16;
      // Print String
      Canvas.TextOut(14, 10, 'PASS');
      // Get date
      DecodeDate(Date , myYear, myMonth, myDay);
      PrintStr := IntToStr(myDay) + '/' + IntToStr(myMonth) + '/' + IntToStr(myYear);
     
      // Set up font size   
      Canvas.Font.Size := 10;
      // Print String
      Canvas.TextOut(280, 20, PrintStr);

      // Set up font size
      Canvas.Font.Size := 6;
      // Print String
      Canvas.TextOut(14, 100, 'Part Number : ' + ‘123123123’ );  //just an example
      Canvas.TextOut(14, 130, ‘SN123123123’); );  //just an example

      // Set up new font for bar code and define font size
      Canvas.Font.Name := 'IDAutomationHC39M';
      Canvas.Font.Size := 10;
      // Print String
      Canvas.TextOut(80, 240, '*' + 'PASS' + '-' + ‘123123123’ + '*'); );  //just an axample

      // Finish printing
      EndDoc;
    end;
end;

The following procedure shows an example of how to read and control printers from your application in Borland Delphi. You can write your own procedure, but here I do it on Form Create event.

procedure TForm2.FormCreate(Sender: TObject);
var
  myYear, myMonth, myDay : Word;
  j: Integer;
  Device : PChar;
  Driver : Pchar;
  Port : Pchar;
  HdeviceMode: Thandle;
  myPrinterName : string;
  DevMode: PDeviceMode;
Begin
// here the printer name defined in control panel on computer
    myPrinterName := ‘LabelsPrinter’;

    Printer.PrinterIndex := -1;
    getmem(Device, 255) ;
    getmem(Driver, 255) ;
    getmem(Port, 255) ;

    LabelPrinter:= TPrinter.create;

// next code shows how to read and change printer settings, using GlobalLock, SetPrinter,
// GetPrinter and GlobalUnlock functions.

    for j := 0 to Printer.printers.Count-1 do
    begin
       if Printer.printers[j] = myPrinterName then
       begin
           LabelPrinter.printerindex := j;
           LabelPrinter.GetPrinter(Device, Driver, Port, HdeviceMode) ;
     
           DevMode := GlobalLock(HdeviceMode);
     
           DevMode^.dmDeviceName       := 'PassLabels';
           DevMode^.dmSpecVersion      := 1025;
           DevMode^.dmDriverVersion    := 1543;
           DevMode^.dmSize             := 156;
           DevMode^.dmDriverExtra      := 1296;
           DevMode^.dmFields           := 75023;
           DevMode^.dmOrientation      := 1;
           DevMode^.dmPaperSize        := 256;
           DevMode^.dmPaperLength      := 478;
           DevMode^.dmPaperWidth       := 736;
           DevMode^.dmScale            := 100;
           DevMode^.dmCopies           := 1;
           DevMode^.dmDefaultSource    := 256;
           DevMode^.dmPrintQuality     := 203;
           DevMode^.dmColor            := 1;
           DevMode^.dmDuplex           := 1;
           DevMode^.dmYResolution      := 203;
           DevMode^.dmTTOption         := 1;
           DevMode^.dmCollate          := 0;
           DevMode^.dmFormName         := 'USER';
           DevMode^.dmLogPixels        := 0;
           DevMode^.dmBitsPerPel       := 0;
           DevMode^.dmPelsWidth        := 0;
           DevMode^.dmPelsHeight       := 0;
           DevMode^.dmDisplayFlags     := 0;
           DevMode^.dmDisplayFrequency := 0;
           DevMode^.dmICMMethod        := 2;
           DevMode^.dmICMIntent        := 1;
           DevMode^.dmMediaType        := 1;
           DevMode^.dmDitherType       := 0;
           DevMode^.dmICCManufacturer  := 0;
           DevMode^.dmICCModel         := 0;
           DevMode^.dmPanningWidth     := 0;
           DevMode^.dmPanningHeight    := 0;
     
           //set a printer settings
           LabelPrinter.SetPrinter(Device, Driver, Port, HdeviceMode);

           //unlock a device
           GlobalUnlock(HdeviceMode);
       end;      
    end;
end;

The next example of the code is written in C# and as I mentioned above it reads printer settings, saves them into a text file and restore them on every start up of the main application. I tested the code with a network printer.

using System;
using System.Drawing.Printing;
using System.Runtime.InteropServices;
using System.IO;
using System.Drawing;

namespace mySpace
{
    [StructLayout(LayoutKind.Sequential)]
    public struct DEVMODE
    {
        public const int CCHDEVICENAME = 32;
        public const int CCHFORMNAME = 32;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)]
        public string dmDeviceName;
        public short dmSpecVersion;
        public short dmDriverVersion;
        public short dmSize;
        public short dmDriverExtra;
        public int dmFields;

        public short dmOrientation;
        public short dmPaperSize;
        public short dmPaperLength;
        public short dmPaperWidth;

        public short dmScale;
        public short dmCopies;
        public short dmDefaultSource;
        public short dmPrintQuality;
        public short dmColor;
        public short dmDuplex;
        public short dmYResolution;
        public short dmTTOption;
        public short dmCollate;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHFORMNAME)]
        public string dmFormName;
        public short dmLogPixels;
        public int dmBitsPerPel;   
        public int dmPelsWidth;
        public int dmPelsHeight;
        public int dmDisplayFlags;
        public int dmDisplayFrequency;

        public int dmICMMethod;
        public int dmICMIntent;
        public int dmMediaType;
        public int dmDitherType;
        public int dmReserved1;
        public int dmReserved2;
        public int dmPanningWidth;
        public int dmPanningHeight;

        public int dmPositionX;
        public int dmPositionY;
    }

    class myPrinter
    {
        [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 = "WritePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
        public static extern bool WritePrinter(IntPtr hPrinter, IntPtr pBytes, Int32 dwCount, out Int32 dwWritten);
               
        [DllImport("kernel32.dll", ExactSpelling = true)]
        public static extern IntPtr GlobalFree(IntPtr handle);

        [DllImport("kernel32.dll", ExactSpelling = true)]
        public static extern IntPtr GlobalLock(IntPtr handle);

        [DllImport("kernel32.dll", ExactSpelling = true)]
        public static extern IntPtr GlobalUnlock(IntPtr handle);

        public static string testReport;
        public static string prnName;

        // somewhere in your code you will need to add
        // myPrinter.prnSettings = new PrinterSettings();
        public static PrinterSettings prnSettings;

        public static bool GetPrinterSettings()
        {
            IntPtr hDevMode;
            IntPtr pDevMode;                       
            DEVMODE devMode; 
                      
            IntPtr hPrinter = new IntPtr(0);
//it is just an axample of network printer
            prnSettings.PrinterName = "\\\\ipp://myprint.mydomain.com.au\\myPrinter1";
            // Open the printer.
            if (OpenPrinter(prnSettings.PrinterName.Normalize(), out hPrinter, IntPtr.Zero))
            {
                // Get the default printer settings
                hDevMode = prnSettings.GetHdevmode(prnSettings.DefaultPageSettings);

                // Obtain a lock on the handle
                pDevMode = GlobalLock(hDevMode);

                // Marshal the memory into our DEVMODE
                devMode = (DEVMODE)Marshal.PtrToStructure(pDevMode, typeof(DEVMODE));

              
                // file stream
                using (FileStream fs = new FileStream("C:\\My_PRN_SETTINGS\\prnSettings.txt", FileMode.Create))
                {
                    for (int i = 0; i < devMode.dmSize + devMode.dmDriverExtra; ++i)
                    {
                        fs.WriteByte(Marshal.ReadByte(pDevMode, i));
                    }
                }

                // Unlock the handle                GlobalUnlock(hDevMode);

                GlobalFree(hDevMode);
            }
            else
            {
                return false;
            }

            return true;
        }

        public static bool UploadPrinterSettings()
        {
            IntPtr hDevMode;                       
            IntPtr pDevMode;                                   
byte[] savedDevMode;                   
            Stream savedDevStream;                 

            IntPtr hPrinter = new IntPtr(0);

//it is just an axample of network printer
            prnSettings.PrinterName = "\\\\ipp://myprint.mydomain.com.au\\myPrinter1";

           
            // Open the printer.
            if (OpenPrinter(prnSettings.PrinterName.Normalize(), out hPrinter, IntPtr.Zero))
            {
                // get the current DEVMODE position in memory
                hDevMode = prnSettings.GetHdevmode(prnSettings.DefaultPageSettings);

                // Obtain a lock
                pDevMode = GlobalLock(hDevMode);

                // Load the saved DEVMODE
                savedDevStream = new FileStream("C:\\My_PRN_SETTINGS\\prnSettings.txt", FileMode.Open, FileAccess.Read);
                savedDevMode = new byte[savedDevStream.Length];
                savedDevStream.Read(savedDevMode, 0, savedDevMode.Length);
                savedDevStream.Close();
                savedDevStream.Dispose();

                // Overwrite the current DEVMODE
                for (int i = 0; i < savedDevMode.Length; ++i)
                {
                    Marshal.WriteByte(pDevMode, i, savedDevMode[i]);
                }

                // It is good to go. All done
                GlobalUnlock(hDevMode);

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

                GlobalFree(hDevMode);

                prnName = prnSettings.PrinterName;
            }
            else
            {
                return false;
            }

            return true;
        }

        public static bool StartPrintintg()
        {
            if( UploadPrinterSettings() )
            {
                PrintDocument prnDoc = new PrintDocument();

                prnDoc.PrinterSettings = prnSettings.DefaultPageSettings.PrinterSettings;
               
                prnDoc.PrintPage += new PrintPageEventHandler(PrintPage);

                prnDoc.Print();

                prnDoc.Dispose();
            }
            else
            {
                return false;
            }

            return true;
        }

        public static void PrintPage(object sender, PrintPageEventArgs e)
        {
            int y;
            // Set Font atributes
            Font myFont = new Font("IDAutomationHC39M", 16, FontStyle.Bold);
            y = e.MarginBounds.Y;
           
            // Print string
            e.Graphics.DrawString("*testing the printers*", myFont, Brushes.DarkRed, e.MarginBounds.X, y);
            myFont.Dispose();
            // Set another Font and it’s attributes
            myFont = new Font("Free 3 of 9 Extended", 16, FontStyle.Bold);
            y += 200;
            // Print String
            e.Graphics.DrawString("Pass", myFont, Brushes.DarkRed, e.MarginBounds.X, y);
            myFont.Dispose();
        }
    }
}

Let me give you some recommendations on how you can test C# code from you application. Add a couple of buttons and on click event call
myPrinter.StartPrintintg();

and on second button click call

if (myPrinter.GetPrinterSettings())
{
// Now you can set breakpoint here and go to control panel and
// change your printer settings to something completely wrong.
Application.DoEvents();
}

Do not forget to add somewhere

myPrinter.prnSettings = new PrinterSettings();

I wish you good luck and hope it will help you in your design. It worked for me.

 


© 2011 E-SIGHT PTY.LTD. Mentone Vic 3194 Melbourne                   :: Installation PHP, Apache and MySQL :: |    Home    |    Contact Us    |    Sitemap    |