=Getting and Setting the Default Printer= 
//[[user:JanetTerra]]//
[[toc]]
----
==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 [[user:StPendl]].  All of the code used in this article has been compiled from postings made by [[user:StPendl]], both at [[http://libertybasic.conforums.com|Liberty BASIC Conforums]] and at the [[http://groups.yahoo.com/group/libertybasic/|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.

[[code format="vbnet"]]
    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
[[code]]

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.
[[code format="vbnet"]]
    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
[[code]]

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.

[[code format="vbnet"]]
' 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
[[code]]

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.

[[code format="vbnet"]]
    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
[[code]]

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

[[code format="vbnet"]]
    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
[[code]]

==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.

[[code format="vbnet"]]
' 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
[[code]]



----