Getting and Setting the Default Printer

- JanetTerra JanetTerra

Liberty BASIC's Native Printer Dialog

The printerdialog function brings up the standard Windows printer dialog box. From the Liberty BASIC helpfile
    • PRINTERDIALOG

    • Description

    • This command opens the standard Windows Common Printer Dialog. If the user chooses a printer and accepts, the next print job will go to this printer. Accepting a printer also sets the global variables PrinterName$, PrintCollate and PrintCopies to reflect what the user chose for the Printer Name, Collate and Copies. If no printer is accepted, then PrinterName$ is set to an empty string.


Unfortunately, Liberty BASIC doesn't interact well with Windows printer dialog. Despite what information PrinterName$ holds, all documents, whether text ( ldump lprint, dump ) or graphics ( vga , svga , xga ) will be sent to the default printer. A work-around is to set the desired printer as default before printing the document. This work-around is based upon contributions of Stefan Pendl. All of the code used in this article has been compiled from postings made by Stefan Pendl , both at Liberty BASIC Conforums and at the Official Liberty BASIC Support Group . The compiled snippets have been modified slightly for consistency with descriptive variable names, but otherwise remain intact.

The first step is to identify (get) the current default printer.

Get Default Printer

The information derived from the winspool.drv DLL will be placed in a struct. The struct must first be defined, here it's pcchBuffer , and the length of the struct element value set to _MAX_PATH. The call to the DLL is then made and the name of the default printer is placed into pcchBuffer.value.struct . Note that byRef is used so that the changes to currentDefaultPrinter$ are made both locally and globally. The function itself returns a number, 0 for a failure, non-zero for success.

 GetDefaultPrinter = GetDefaultPrinter(currentDefaultPrinter$) 
if GetDefaultPrinter = 0 then
print "Call failed"
else
print "DefaultPrinter = ";currentDefaultPrinter$
end if
end

function GetDefaultPrinter(byref currentDefaultPrinter$)
' Returns zero if call fails
struct pcchBuffer, value as ulong
currentDefaultPrinter$ = space$(_MAX_PATH)
pcchBuffer.value.struct = _MAX_PATH

open "winspool.drv" for dll as #winspool

calldll #winspool, "GetDefaultPrinterA", _
currentDefaultPrinter$ as ptr, _
pcchBuffer as struct, _
GetDefaultPrinter as long

close #winspool
end function

Stefan's code has always been accompanied with an error catching routine. Should the call not work, Liberty BASIC can identify the cause of the failure.
 GetDefaultPrinter = GetDefaultPrinter(currentDefaultPrinter$) 
if GetDefaultPrinter = 0 then
print "Call failed"
else
print "DefaultPrinter = ";currentDefaultPrinter$
end if
end

function GetDefaultPrinter(byref currentDefaultPrinter$)
' Returns zero if call fails
struct pcchBuffer, value as ulong
currentDefaultPrinter$ = space$(_MAX_PATH)
pcchBuffer.value.struct = _MAX_PATH

open "winspool.drv" for dll as #winspool

calldll #winspool, "GetDefaultPrinterA", _
currentDefaultPrinter$ as ptr, _
pcchBuffer as struct, _
GetDefaultPrinter as long
if GetDefaultPrinter = 0 then
call DisplayError
else
currentDefaultPrinter$ = left$(currentDefaultPrinter$, pcchBuffer.value.struct - 1)
end if
close #winspool
end function

sub DisplayError
ErrorCode = GetLastError()

dwFlags = _FORMAT_MESSAGE_FROM_SYSTEM
nSize = 1024
lpBuffer$ = space$(nSize); chr$(0)
dwMessageID = ErrorCode

calldll #kernel32, "FormatMessageA", _
dwFlags as ulong, _
lpSource as ulong, _
dwMessageID as ulong, _
dwLanguageID as ulong, _
lpBuffer$ as ptr, _
nSize as ulong, _
Arguments as ulong, _
result as ulong

print "Error "; ErrorCode; ": "; left$(lpBuffer$, result)
end sub

function GetLastError()
calldll #kernel32, "GetLastError", _
GetLastError as ulong
end function

Storing the current default printer in a variable is important to allow that printer to be reassigned as default once the document has been printed.

List All Printers

The next step is to get a list of all available printers. This requires a little more work and three more structs. Stefan's function loops around to obtain all the printer names, until there are no names left. The printer names are concatenated, deliminated with a semicolon, in the string variable PrinterInfo$. The loop ends when error #122 (The data area passed to a system call is too small) is encounered. Once again, byRef is used so that PrinterInfo$ remains the same locally and globally.

Enumerating the printers requires allocating and searching blocks of memory, resulting in rather complex code. Also, listing printers available to the computer by a local (physical) connection requires different variables than listing printers available to the computer by a network or wireless connection. The EnumPrinters() function must be accessed twice, first for local printers then for network printers. The appropriate variables should be passed to the function each time.

    • Flag to List Local Printers
    • PRINTER.ENUM.LOCAL = hexdec("2")

    • Flag to List Network Printers
    • PRINTER.ENUM.CONNECTIONS = hexdec("4")

The first pass stores the retrieved information in LocalPrinterInfo$ and the second pass stores the retrieved information in NetworkPrinterInfo$ . These two stringes are then concatenated with a semicolon to hold all printers in PrinterInfo$ . Finally, an array is constructed to hold the individual printer names.

 ' Count and enumerate all printers 
' Need to access function twice, first for local printers, second for network printers
PRINTER.ENUM.LOCAL = hexdec("2")
PRINTER.ENUM.CONNECTIONS = hexdec("4")

nLocalPrinters = EnumPrinters(PrinterInfo$, PRINTER.ENUM.LOCAL)
print "nLocalPrinters = ";nLocalPrinters
LocalPrinterInfo$ = PrinterInfo$
print "LocalPrinterInfo$ = ";LocalPrinterInfo$
print
nNetworkPrinters = EnumPrinters(PrinterInfo$, PRINTER.ENUM.CONNECTIONS)
print "nNetworkPrinters = ";nNetworkPrinters
NetworkPrinterInfo$ = PrinterInfo$
print "NetworkPrinterInfo$ = ";NetworkPrinterInfo$
print

' Add both to total and combine the 2 printer strings
nPrinters = nLocalPrinters + nNetworkPrinters
Print "nPrinters = ";nPrinters
PrinterInfo$ = LocalPrinterInfo$;";";NetworkPrinterInfo$
print "PrinterInfo$ = ";PrinterInfo$

' Place all printers in an array
dim availablePrinters$(nPrinters)
for i = 1 to nPrinters
availablePrinters$(i) = word$(PrinterInfo$, i, ";")
next i
for i = 1 to nPrinters
print i, availablePrinters$(i)
next i
end

function EnumPrinters(byref PrinterInfo$, nFlags)
' Returns the number of printers found
' Fills the submitted variable with the printer names
' Separated by semicolons (;)
open "winspool.drv" for dll as #winspool
struct pcbNeeded, value as ulong
struct pcReturned, value as ulong
struct PrinterInfo4, _
pPrinterName$ as ptr, _
pServerName$ as ptr, _
Attributes as ulong
PrinterInfo4Len = len(PrinterInfo4.struct)
Level = 4
cbBuf = PrinterInfo4Len
uFlags = _LMEM_MOVEABLE or _LMEM_ZEROINIT
calldll #kernel32, "LocalAlloc", _
uFlags as uLong, _
cbBuf as uLong, _
hMem as uLong
calldll #kernel32, "LocalLock", _
hMem as uLong, _
pBuffer as uLong

[retryEnumPrinters]
calldll #winspool, "EnumPrintersA", _
nFlags as ulong, _
PrinterName as ulong, _
Level as ulong, _
pBuffer as ulong, _
cbBuf as ulong, _
pcbNeeded as struct, _
pcReturned as struct, _
result as boolean
if result = 0 then
if GetLastError() = 122 then
cbBuf = pcbNeeded.value.struct
hOldMem = hMem
calldll #kernel32, "LocalReAlloc", _
hOldMem as ulong, _
cbBuf as ulong, _
uFlags as ulong, _
hMem as ulong
calldll #kernel32, "LocalLock", _
hMem as uLong, _
pBuffer as ulong
goto [retryEnumPrinters]
else
call DisplayError
end if
else
EnumPrinters = pcReturned.value.struct
BufferPointer = pBuffer
for count = 0 to EnumPrinters - 1
calldll #kernel32, "RtlMoveMemory", _
PrinterInfo4 as struct, _
BufferPointer as ulong, _
PrinterInfo4Len as ulong, _
result as void
BufferPointer = BufferPointer + PrinterInfo4Len
pointer = PrinterInfo4.pPrinterName$.struct
PrinterInfo$ = winstring(pointer); ";"; PrinterInfo$
next count
PrinterInfo$ = left$(PrinterInfo$, len(PrinterInfo$)-1)
end if
calldll #kernel32, "LocalFree", _
hMem as uLong, _
result as uLong
close #winspool
end function

function GetLastError()
calldll #kernel32, "GetLastError", _
GetLastError as ulong
end function

The final step is to designate a different printer as default.

Set Default Printer

The code to set a default printer is the simplest of all, just passing a valid printer name to the winspool.drv dll. Like the GetDefaultPrinter() function, the SetDefaultPrinter() function returns a 0 for failure, a non-zero for success.

 selectedDefaultPrinter$ = "My Inkjet Printer" 
' Set new default printer
SetDefaultPrinter = SetDefaultPrinter(selectedDefaultPrinter$)
end

function SetDefaultPrinter(selectedDefaultPrinter$)
' Returns zero if call fails
open "winspool.drv" for dll as #winspool

calldll #winspool, "SetDefaultPrinterA",_
selectedDefaultPrinter$ as ptr,_
SetDefaultPrinter as long

close #winspool
end function

This is the same code, but with Stefan's error trapping included.

 selectedDefaultPrinter$ = "My Inkjet Printer" 
' Set new default printer
SetDefaultPrinter = SetDefaultPrinter(selectedDefaultPrinter$)
end

function SetDefaultPrinter(selectedDefaultPrinter$)
' Returns zero if call fails
open "winspool.drv" for dll as #winspool

calldll #winspool, "SetDefaultPrinterA",_
selectedDefaultPrinter$ as ptr,_
SetDefaultPrinter as long

close #winspool
if SetDefaultPrinter = 0 then call DisplayError
end function

sub DisplayError
ErrorCode = GetLastError()

dwFlags = _FORMAT_MESSAGE_FROM_SYSTEM
nSize = 1024
lpBuffer$ = space$(nSize); chr$(0)
dwMessageID = ErrorCode

calldll #kernel32, "FormatMessageA", _
dwFlags as ulong, _
lpSource as ulong, _
dwMessageID as ulong, _
dwLanguageID as ulong, _
lpBuffer$ as ptr, _
nSize as ulong, _
Arguments as ulong, _
result as ulong

print "Error "; ErrorCode; ": "; left$(lpBuffer$, result)
end sub

function GetLastError()
calldll #kernel32, "GetLastError", _
GetLastError as ulong
end function

Getting, Listing, Setting the Default Printer (Mainwindow)

Using all three components, the programmer now has full control of getting, listing, and setting the default printer.

 ' Get the original default printer 
GetDefaultPrinter = GetDefaultPrinter(origDefaultPrinter$)
print "origDefaultPrinter$ = ";origDefaultPrinter$
print

' Count and enumerate all printers
' Need to access function twice, first for local printers, second for network printers
PRINTER.ENUM.LOCAL = hexdec("2")
PRINTER.ENUM.CONNECTIONS = hexdec("4")

nLocalPrinters = EnumPrinters(PrinterInfo$, PRINTER.ENUM.LOCAL)
print "nLocalPrinters = ";nLocalPrinters
LocalPrinterInfo$ = PrinterInfo$
print "LocalPrinterInfo$ = ";LocalPrinterInfo$
print
nNetworkPrinters = EnumPrinters(PrinterInfo$, PRINTER.ENUM.CONNECTIONS)
print "nNetworkPrinters = ";nNetworkPrinters
NetworkPrinterInfo$ = PrinterInfo$
print "NetworkPrinterInfo$ = ";NetworkPrinterInfo$
print

' Add both to total and combine the 2 printer strings
nPrinters = nLocalPrinters + nNetworkPrinters
Print "nPrinters = ";nPrinters
PrinterInfo$ = LocalPrinterInfo$;";";NetworkPrinterInfo$
print "PrinterInfo$ = ";PrinterInfo$

' Place all printers in an array
dim availablePrinters$(nPrinters)
for i = 1 to nPrinters
availablePrinters$(i) = word$(PrinterInfo$, i, ";")
next i
for i = 1 to nPrinters
print i, availablePrinters$(i)
next i
print

' Select another default printer
Input "Printer to set as default > ";selectedDefaultPrinter
selectedDefaultPrinter$ = availablePrinters$(selectedDefaultPrinter)
print

' Set new default printer
SetDefaultPrinter = SetDefaultPrinter(selectedDefaultPrinter$)
end


function EnumPrinters(byref PrinterInfo$, nFlags)
' Returns the number of printers found
' Fills the submitted variable with the printer names
' Separated by semicolons (;)
open "winspool.drv" for dll as #winspool
struct pcbNeeded, value as ulong
struct pcReturned, value as ulong
struct PrinterInfo4, _
pPrinterName$ as ptr, _
pServerName$ as ptr, _
Attributes as ulong
PrinterInfo4Len = len(PrinterInfo4.struct)
Level = 4
cbBuf = PrinterInfo4Len
uFlags = _LMEM_MOVEABLE or _LMEM_ZEROINIT
calldll #kernel32, "LocalAlloc", _
uFlags as uLong, _
cbBuf as uLong, _
hMem as uLong
calldll #kernel32, "LocalLock", _
hMem as uLong, _
pBuffer as uLong

[retryEnumPrinters]
calldll #winspool, "EnumPrintersA", _
nFlags as ulong, _
PrinterName as ulong, _
Level as ulong, _
pBuffer as ulong, _
cbBuf as ulong, _
pcbNeeded as struct, _
pcReturned as struct, _
result as boolean
if result = 0 then
if GetLastError() = 122 then
cbBuf = pcbNeeded.value.struct
hOldMem = hMem
calldll #kernel32, "LocalReAlloc", _
hOldMem as ulong, _
cbBuf as ulong, _
uFlags as ulong, _
hMem as ulong
calldll #kernel32, "LocalLock", _
hMem as uLong, _
pBuffer as ulong
goto [retryEnumPrinters]
else
call DisplayError
end if
else
EnumPrinters = pcReturned.value.struct
BufferPointer = pBuffer
for count = 0 to EnumPrinters - 1
calldll #kernel32, "RtlMoveMemory", _
PrinterInfo4 as struct, _
BufferPointer as ulong, _
PrinterInfo4Len as ulong, _
result as void
BufferPointer = BufferPointer + PrinterInfo4Len
pointer = PrinterInfo4.pPrinterName$.struct
PrinterInfo$ = winstring(pointer); ";"; PrinterInfo$
next count
PrinterInfo$ = left$(PrinterInfo$, len(PrinterInfo$)-1)
end if
calldll #kernel32, "LocalFree", _
hMem as uLong, _
result as uLong
close #winspool
end function


function GetDefaultPrinter(byref currentDefaultPrinter$)
' Returns zero if call fails

struct pcchBuffer, value as ulong
currentDefaultPrinter$ = space$(_MAX_PATH)
pcchBuffer.value.struct = _MAX_PATH

open "winspool.drv" for dll as #winspool

calldll #winspool, "GetDefaultPrinterA", _
currentDefaultPrinter$ as ptr, _
pcchBuffer as struct, _
GetDefaultPrinter as long

close #winspool

if GetDefaultPrinter = 0 then
call DisplayError
else
currentDefaultPrinter$ = left$(currentDefaultPrinter$, pcchBuffer.value.struct - 1)
end if
end function

function SetDefaultPrinter(selectedDefaultPrinter$)
' Returns zero if call fails
open "winspool.drv" for dll as #winspool

calldll #winspool, "SetDefaultPrinterA",_
selectedDefaultPrinter$ as ptr,_
SetDefaultPrinter as long

close #winspool
if SetDefaultPrinter = 0 then call DisplayError
end function

sub DisplayError
ErrorCode = GetLastError()

dwFlags = _FORMAT_MESSAGE_FROM_SYSTEM
nSize = 1024
lpBuffer$ = space$(nSize); chr$(0)
dwMessageID = ErrorCode

calldll #kernel32, "FormatMessageA", _
dwFlags as ulong, _
lpSource as ulong, _
dwMessageID as ulong, _
dwLanguageID as ulong, _
lpBuffer$ as ptr, _
nSize as ulong, _
Arguments as ulong, _
result as ulong

print "Error "; ErrorCode; ": "; left$(lpBuffer$, result)
end sub

function GetLastError()
calldll #kernel32, "GetLastError", _
GetLastError as ulong
end function