Older Version Newer Version

RodBird RodBird Nov 25, 2015

Getting and Setting the Default Printer

- JanetTerra JanetTerra

Liberty BASIC's Printerdialog Command

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 ( 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 - StPendl StPendl . All of the code used in this article has been compiled from postings made by - StPendl StPendl , 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 code in this article is only valid for Windows 2k/XP/Vista/2k3/2k8 . This code will not work for Windows 9x/ME . For code that will work with Windows 9x/ME, see Problem with printerdialog at the Liberty BASIC Community Forum .

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 long
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 long
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

Getting, Listing, Setting the Default Printer (GUI)

A demo using a dialog window to select the user's choice of printer. The program returns the original default printer as default after each print.

 ' Get the original default printer 
GetDefaultPrinter = GetDefaultPrinter(origDefaultPrinter$)
origDefaultPrinter$ = origDefaultPrinter$
defaultPrinter$ = origDefaultPrinter$

' 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)
LocalPrinterInfo$ = PrinterInfo$
nNetworkPrinters = EnumPrinters(PrinterInfo$, PRINTER.ENUM.CONNECTIONS)
NetworkPrinterInfo$ = PrinterInfo$

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

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

WindowWidth = 809
WindowHeight = 600
UpperLeftX = Int((DisplayWidth - WindowWidth) / 2)
UpperLeftY = Int((DisplayHeight - WindowHeight) / 2)
menu #main, "&File", "&Print",[printerSelection], |,"E&xit", [quit]
Graphicbox #main.g, 1, 0, 800, 550
open "Printer Selection" for Window as #main
#main "trapclose [quit]"
call screenDisplay

wait

[quit]
close #main
end

[printerSelection]
WindowWidth = 250
WindowHeight = 150
UpperLeftX = 8
UpperLeftY = 8
listbox #dlg.sel, availablePrinters$(), [printScreen], 14, 20, 214, 54
button #dlg.prnt, "Print", [printScreen], UL, 14, 84, 70, 28
button #dlg.cncl, "Cancel", [closeDlg], UL, 160, 84, 70, 28
stylebits #dlg, _WS_POPUP or _WS_THICKFRAME, _WS_CAPTION, 0, 0
open "Select Printer" for dialog_modal as #dlg
#dlg "trapclose [closeDlg]"
#dlg.sel "select ";defaultPrinter$
wait

[printScreen]
#dlg.sel "selection? selPrinter$"
defaultPrinter$ = selPrinter$
' Set the default printer as the selected printer
SetDefaultPrinter = SetDefaultPrinter(selPrinter$)
#main.g "print svga"
' Return the default printer to the original default printer
SetDefaultPrinter = SetDefaultPrinter(origDefaultPrinter$)

[closeDlg]
close #dlg
wait

sub screenDisplay
#main.g "down; font verdana 14 bold; place 300 200"
#main.g, "\Hello World"
#main.g, "flush"
end sub

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 long
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

Printer Dialog Clone


- robmcal robmcal offers code for a look alike printer dialog box .