-2

Trying to convert VB6 line of code to VB.net. Code will serve as identification if printer is OFF or ON Thanks

"PRINTERFOUND = OpenPrinterW(StrPtr(PrinterName), hPrinter)" Particular StrPtr function...

Can't get OpenPrinter to work - Tries to print I simply want to to know if printer is OFF or ON

changing line to

PRINTERFOUND = OpenPrinterW(PrinterName.Normalize(), hPrinter, Nothing)

does not work, thanks

Tried to convert VB6 declaration to VB.net per previous suggestions but it still a same error cant convert String to Integer please see below

'Private Declare Function GetPrinterApi Lib "winspool.drv" Alias "GetPrinterA" (ByVal hPrinter As Long, ByVal Level As Long, ByRef BUFFER As  Long, ByVal pbSize As Long, ByRef pbSizeNeeded As Long) As Long     
 <DllImport("winspool.Drv", EntryPoint:="GetPrinterA", SetLastError:=True, CharSet:=CharSet.Ansi, ExactSpelling:=True, CallingConvention:=CallingConvention.StdCall)> _
Public Function GetPrinterApi(<MarshalAs(UnmanagedType.LPStr)> ByVal hPrinter As String, ByVal Level As IntPtr, ByVal BUFFER As IntPtr, ByVal pbSize As IntPtr, ByRef pbSizeNeeded As IntPtr) As Boolean
End Function

'Private Declare Function OpenPrinterW Lib "winspool.drv" (ByVal pPrinterName As Long, ByRef phPrinter As Long, Optional ByVal pDefault As Long = 0) As Long  
 <DllImport("winspool.Drv", SetLastError:=True, CharSet:=CharSet.Ansi, ExactSpelling:=True, CallingConvention:=CallingConvention.StdCall)> _
Public Function OpenPrinterW(ByVal pPrinterName As IntPtr, ByVal phPrinter As Int32, <[In](), MarshalAs(UnmanagedType.LPStruct)> ByVal pDefault As IntPtr) As Boolean
End Function

'Private Declare Function ClosePrinter Lib "winspool.drv" (ByVal hPrinter As Long) As Long
 <DllImport("winspool.Drv", EntryPoint:="ClosePrinter", SetLastError:=True, ExactSpelling:=True, CallingConvention:=CallingConvention.StdCall)> _
Public Function ClosePrinter(ByVal hPrinter As IntPtr) As Boolean
End Function

'Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Long, ByRef Source As Long, ByVal Length As Long)  
 <DllImport("kernel32.dll", SetLastError:=True, EntryPoint:="RtlMoveMemory")> _
Public Function CopyMemory(ByRef Destination As Long, ByVal Source As IntPtr, ByVal Length As String) As IntPtr
End Function

Full code Below

'Acknowledgements : This program has been written making extensive use of the
'Merrion article http://www.merrioncomputing.com/Programming/PrintStatus.htm
'It has also benefited from the contributors to VBForums thread # 733849
'http://www.vbforums.com/showthread.php?t=733849&goto=newpost - especially the code
'suggested by "Bonnie West"

'Program written 14 Sept. 2013 by C.A. Moore

Option Explicit

Dim PRINTERFOUND As Long
Dim GETPRINTER As Long
Dim BUFFER() As Long
Dim pbSizeNeeded As Long
Dim PRINTERINFO As PRINTER_INFO_2
Dim N As Integer
Dim M As Integer
Dim CHAR As String
Dim prnPrinter As Printer
Dim BUF13BINARY As String

' Note : PRINTERREADY as an Integer variable is Dim'd
'"Public PRINTERREADY As Integer" at Form1 Option Explicit

Private Type PRINTER_INFO_2
    pServerName As String
    pPrinterName As String
    pShareName As String
    pPortName As String
    pDriverName As String
    pComment As String
    pLocation As String
    pDevMode As Long
    pSepFile As String
    pPrintProcessor As String
    pDatatype As String
    pParameters As String
    pSecurityDescriptor As Long
    Attributes As Long
    Priority As Long
    DefaultPriority As Long
    StartTime As Long
    UntilTime As Long
    Status As Long
    JobsCount As Long
    AveragePPM As Long
End Type

Private Declare Function GetPrinterApi Lib "winspool.drv" Alias "GetPrinterA" (ByVal hPrinter As Long, ByVal Level As Long, BUFFER As Long, ByVal pbSize As Long, pbSizeNeeded As Long) As Long
Private Declare Function OpenPrinterW Lib "winspool.drv" (ByVal pPrinterName As Long, ByRef phPrinter As Long, Optional ByVal pDefault As Long) As Long
Private Declare Function ClosePrinter Lib "winspool.drv" (ByVal hPrinter As Long) As Long
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)

Public Function StringFromPointer(lpString As Long, lMaxLength As Long) As String
    'this service function extracts a string (sRet) when fed with a pointer (lpstring)
    'from a buffer
    Dim sRet As String
    Dim lret As Long

    If lpString = 0 Then
        StringFromPointer = ""
        Exit Function
    End If

    '\\ Pre-initialise the return string...
    sRet = Space$(lMaxLength)
    CopyMemory ByVal sRet, ByVal lpString, ByVal Len(sRet)
    If Err.LastDllError = 0 Then
        If InStr(sRet, Chr$(0)) > 0 Then
            sRet = Left$(sRet, InStr(sRet, Chr$(0)) - 1)
        End If
    End If

    StringFromPointer = sRet
End Function

Public Function IsPrinterReady(ByRef PrinterName As String)
    Form1.PRINTERREADY = 0

    'first select the named printer and check if it is installed
    For Each prnPrinter In Printers
        CHAR = prnPrinter.DeviceName
        If CHAR = PrinterName Then
            Set Printer = prnPrinter   'sets this as printer
            Form1.PRINTERREADY = 1
        End If
    Next

    If Form1.PRINTERREADY = 0 Then GoTo Line1000     'exit. printer not installed
        Dim hPrinter As Long
        Dim PI6 As PRINTER_INFO_2

        PRINTERFOUND = 0
        Form1.PRINTERREADY = 0
        PRINTERFOUND = OpenPrinterW(StrPtr(PrinterName), hPrinter)
        '(OpenPrinterW(ByVal pPrinterName As Long, ByRef phPrinter As Long) As Long)

        If PRINTERFOUND = 0 Then                'ie. printer not found
            Form1.PRINTERREADY = 0
            Debug.Assert ClosePrinter(hPrinter)
            GoTo Line100
        End If

        'If we get here named printer was found and accessed and its hPrinter handle is
        'known
         'Dim BUFFER() As Long
         'Dim pbSizeNeeded As Long

         ReDim Preserve BUFFER(0 To 1) As Long
         GETPRINTER = GetPrinterApi(hPrinter, 2&, BUFFER(0), UBound(BUFFER), pbSizeNeeded)
         ReDim Preserve BUFFER(0 To (pbSizeNeeded / 4) + 3) As Long
         GETPRINTER = GetPrinterApi(hPrinter, 2&, BUFFER(0), UBound(BUFFER) * 4, pbSizeNeeded)
         If GETPRINTER = 0 Then              'ie. some problem with printer access
             Form1.PRINTERREADY = 0
             GoTo Line100
         End If

         'If we get here then GETPRINTER = 1, ie. printer found and accessed OK
         With PRINTERINFO '\\ This variable is of type PRINTER_INFO_2
            'These quantities are defined here because the Merrion article
            'so specifies. However they are not used by this program, and most
            'have been found to be void

            .pServerName = StringFromPointer(BUFFER(0), 1024)
            .pPrinterName = StringFromPointer(BUFFER(1), 1024)
            .pShareName = StringFromPointer(BUFFER(2), 1024)
            .pPortName = StringFromPointer(BUFFER(3), 1024)
            .pDriverName = StringFromPointer(BUFFER(4), 1024)
            .pComment = StringFromPointer(BUFFER(5), 1024)
            .pLocation = StringFromPointer(BUFFER(6), 1024)
            .pDevMode = BUFFER(7)
            .pSepFile = StringFromPointer(BUFFER(8), 1024)
            .pPrintProcessor = StringFromPointer(BUFFER(9), 1024)
            .pDatatype = StringFromPointer(BUFFER(10), 1024)
            .pParameters = StringFromPointer(BUFFER(11), 1024)
            .pSecurityDescriptor = BUFFER(12)
            .Attributes = BUFFER(13)
            .Priority = BUFFER(14)
            .DefaultPriority = BUFFER(15)
            .StartTime = BUFFER(16)
            .UntilTime = BUFFER(17)
            .Status = BUFFER(18)
            .JobsCount = BUFFER(19)
            .AveragePPM = BUFFER(20)
        End With



        'This next code is for interest and program development only.
        'It writes into List1 the value of each buffer 1 - 20
        'To by-pass it, add a "Go To Line15" statement at this point.

        Form1.List1.Clear
        N = 0
Line5:
        On Error GoTo Line15
        Form1.List1.AddItem "Buffer No. " & N & "  Buffer Value " & BUFFER(N)
        N = (N + 1)
        If N = 21 Then GoTo Line15
        GoTo Line5

        'Now to convert the decimal value of Buffer(13) into a binary
        'bit pattern and store this in BUF13BINARY
Line15: 'and to show Buffer(13) as a binary bit pattern at Form1.Label1

        N = BUFFER(13)
        BUF13BINARY = ""
        M = 4196
Line16:
        If N < M Then
            BUF13BINARY = BUF13BINARY & "0"
            GoTo Line20
        End If

        BUF13BINARY = BUF13BINARY & "1"
        N = (N - M)
Line20:
        If M = 1 Then GoTo Line10
            M = M / 2
            GoTo Line16

Line10: 'BUF13BINARY is now the 13 bit binary value of Buffer(13)
        'eg. 0011000100010

        Form1.Label1.Caption = BUF13BINARY  'display this binary value at form 1

        'we now examine the value of the third binary bit in BUF13BINARY
        If Mid$(BUF13BINARY, 3, 1) = "0" Then Form1.PRINTERREADY = 1
        If Mid$(BUF13BINARY, 3, 1) = "1" Then Form1.PRINTERREADY = 0
Line100:
        ClosePrinter (hPrinter)
Line1000:
End Function

and

Option Explicit
Public PRINTERREADY As Integer

Private Sub Command1_Click()
    IsPrinterReady ("Brother QL-500")
    'IsPrinterReady ("EPSON Stylus CX5400")
    MsgBox PRINTERREADY                     '0 = Not Ready   1 = Ready
End Sub
Alek
  • 35
  • 1
  • 9
  • 5
    It's not really possible to read through the question in its current form. If you can [format it](http://stackoverflow.com/help/formatting) and strip it down to the necessary information, that would be very helpful. – sstan Aug 25 '16 at 15:33
  • I edit it directly to the point, thanks – Alek Aug 25 '16 at 16:04
  • Basically need to convert VB6 StrPtr to VB.net – Alek Aug 25 '16 at 16:10
  • 1
    Possible duplicate of [Can't get OpenPrinter to work](http://stackoverflow.com/questions/12847352/cant-get-openprinter-to-work) – Hackerman Aug 25 '16 at 16:27
  • I checked that one and it is different but I tried to use - PRINTERFOUND = OpenPrinterW(PrinterName.Normalize(), hPrinter, Nothing) instead of PRINTERFOUND = OpenPrinterW(StrPtr(PrinterName), hPrinter) and program is not working – Alek Aug 25 '16 at 16:46
  • You should translate the VB6 `Declare Function` declaractions to the proper VB.Net syntax. You can find a lot of examples on [http://pinvoke.net/index.aspx](http://pinvoke.net/index.aspx). There is no need to use equivalents of VB6 pointer functions. – TnTinMn Aug 25 '16 at 16:57
  • Found this http://pinvoke.net/search.aspx?search=winspool.drv&namespace=[All] seems kinda the same... still calls on lib or am I missing something? I assume proper conversion will be without using winspool.drv and kernel32? – Alek Aug 25 '16 at 18:33
  • By the way this maybe be a solution by changing line to PRINTERFOUND = OpenPrinterW((PrinterName), hPrinter) returns an error " Conversion from String (PRINTERNAME) to Integer is not valid... defiantly has to do something with declaration – Alek Aug 25 '16 at 18:37
  • Private Declare Function OpenPrinterW Lib "winspool.drv" (ByVal pPrinterName As Long, ByRef phPrinter As Long, Optional ByVal pDefault As Long) As Long - Error comes from here pPrinterName as Long or Integer changing this to String crashes App – Alek Aug 25 '16 at 20:46
  • I tried to convert to Declaration for VB6 to VB.net per previous suggestions but its a same error cannot convert string to long... – Alek Aug 25 '16 at 21:27
  • Please use a four space indent on your lines of code in the post. – LarsTech Aug 25 '16 at 21:42
  • I tried to format it, not sure why it still came up the same – Alek Aug 25 '16 at 21:54

2 Answers2

1

You cannot convert VarPtr, StrPtr, or ObjPtr, because in .NET you do not directly control memory. These functions were used to extract a pointer from an instance, a variable, or a Unicode string. But in .NET, object locations in memory is managed by the garbage collector, and the GC can move objects around in memory at any time.

Consider the following code, but do not use it! I put it here only to explain why these functions do not exist in .NET.

Private Function VarPtr(ByVal obj As Object) As Integer
    ' Obtain a pinned handle to the object
    Dim handle As GCHandle = GCHandle.Alloc(obj, GCHandleType.Pinned)
    Dim pointer As Integer = handle.AddrOfPinnedObject.ToInt32

    ' Free the allocated handle. At this point the GC can move the object in memory, this is 
    ' why this function does not exist in .NET. If you were to use this pointer as a destination 
    ' for memcopy for example, you could overwrite unintended memory, which would crash the 
    ' application or cause unexpected behavior. For this function to work you would need to
    ' maintain the handle until after you are finished using it.
    handle.Free()

    Return pointer
End Function

Edit:

The correct way to get the printer status is through the managed interface for it, in this case through WMI:

Imports System.Management

Public Class Form1

    Private Enum PrinterStatus As Integer
        Other = 1
        Unknown = 2
        Idle = 3
        Printing = 4
        Warmup = 5
        Stopped = 6
        Offline = 7
    End Enum

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Dim mos = New ManagementObjectSearcher("SELECT * FROM Win32_Printer")

        For Each p In mos.Get()
            Debug.Print(p.Properties("Name").Value.ToString() & " : " & CType(CInt(p.Properties("PrinterStatus").Value), PrinterStatus).ToString())
        Next
    End Sub

End Class

You can get more information about the Win32_Printer class here: https://msdn.microsoft.com/en-us/library/aa394363(v=vs.85).aspx

In particular take note of this:

Note If you are retrieving PrinterStatus = 3 or PrinterState = 0, the printer driver may not be feeding accurate information into WMI. WMI retrieves the printer information from the spoolsv.exe process. It is possible the printer driver does not report its status to the spooler. In this case, Win32_Printer reports the printer as Idle.

From there you can get information about and manage just about any peripheral connected. Just figure out the appropriate WMI classes and read up on the docs.

Drunken Code Monkey
  • 1,796
  • 1
  • 14
  • 18
  • Thank you for information, I did research on all this undocumented functions for VB6, and found that some people suggested using GChandle but as your explanation shows .NET doesn't work like that.... But there must be a way around it no? – Alek Aug 25 '16 at 22:33
  • Well you CAN use the code above to allocate a handle and extract a pointer, but not in a function. You need to call handle.Free() only AFTER you are completely done with the pointer. Sometimes it's not exactly clear when that is... – Drunken Code Monkey Aug 25 '16 at 22:42
  • The better solution would be to stop using winspool.drv P/Invokes and instead use the managed .NET native way to interact with the printer. Trying to directly translate VB6 to .NET always tends to yield garbage code, instead you should think about what the code does, and figure out how to do this in .NET natively, and then rewrite the code in question functionally. – Drunken Code Monkey Aug 25 '16 at 22:46
  • Also note that you will need to marshall the actual string in memory if you use this approach, since VB6 strings are value types, and .NET strings are reference types. They are not stored the same way in memory. – Drunken Code Monkey Aug 25 '16 at 22:51
  • Agree transferring VB6 code to VB.net is not a good idea... I think I do understand what this code does it just I was not able to achieve the same results in VB.net... All I want to do is to have an identification on my application when printer is OFF or ON and that's basically it... I found code that shows what all printers a ready even when they off but code is correct because even Windows properties show as printers are READY when they OFF that led me to believe that there is no legit way around... and then I found this and it works perfectly, so kinda stuck at the moment. – Alek Aug 25 '16 at 23:29
  • I have added an example of code to get the printer status in my answer above. – Drunken Code Monkey Aug 25 '16 at 23:58
  • As for the detection issue, you could still try to "poke" each printer through WMI. Use one of the methods or properties of the Win32_Printer class to provoke a response, through another property, to force the printer to report *something*. – Drunken Code Monkey Aug 26 '16 at 02:23
  • Thank you for the answer and information you provided I had to modify this code slightly for it work "For Each printer As ManagementObject In mos.Get()" It did however showed that all printers are IDLE... I guess I will have to look into POKE function and see how can I create a code that will POKE the printer to get status on it my only concern is will this interface with a printer operation? – Alek Aug 26 '16 at 14:37
0

Thanks Everyone, especially Drunken Code Monkey for all the information provided, I was able to finally achieve what I wanted after reading on https://msdn.microsoft.com/en-us/library/aa394363(v=vs.85).aspx figured need to is boolean WorkOffline; Pew... made it more simple then VB6 language even using that feels kinda hmm old. Also lucky me found codes that did the same thing as I wanted here is link to VB.NET "https://bytes.com/topic/visual-basic-net/answers/524957-detecting-if-printer-connected-pc" And my modification of this code below

Imports System.Management
Public Class Form1
Public Class CheckPrinterStatus
    Public Function PrinterIsOnline(ByVal sPrinterName As String) As Boolean
        '// Set management scope
        Dim scope As ManagementScope = New ManagementScope("\root\cimv2")
        scope.Connect()

        '// Select Printers from WMI Object Collections
        Dim searcher As ManagementObjectSearcher = New ManagementObjectSearcher("SELECT * FROM Win32_Printer")

        Dim printerName As String = String.Empty
        For Each printer As ManagementObject In searcher.Get()
            printerName = printer("Name").ToString() '.ToLower()
            If (printerName.Equals(sPrinterName)) Then
                MsgBox(printerName)
                If (printer("WorkOffline").ToString().ToLower().Equals("true")) Then

                    MsgBox("Offline")
                    ' Printer is offline by user
                    Return False
                Else
                    ' Printer is not offline
                    MsgBox("Online")
                    Return True
                End If
            End If
        Next
        Return False
    End Function ' PrinterIsOnline
End Class
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    Dim PrinterSatus As New CheckPrinterStatus
    PrinterSatus.PrinterIsOnline("Brother QL-500") 'Name of The printer HERE
End Sub
End Class
Alek
  • 35
  • 1
  • 9