c++ DLL call - bad parameter?

charliee1151

Member
Joined
Oct 22, 2013
Messages
6
Programming Experience
10+
I need to call a legacy DLL.

Here is the definition of one of the functions (abbreviated):

GetNumber(char *sNumber, int *length); <==returns int


I change it to VB.net:

GetNumber Lib "Foo.dll" (ByVal Number As IntPtr, ByRef Length As Int32) As Int32
'(intent is to return data into locations as pointed to by Number and Length pointers which are passed in; Int32 is return value = error code)


Here is VB.net code:
Sub Bar
        Dim Result As Int32
        Dim DataLength As Int32
        Dim NumberData As IntPtr = Marshal.AllocHGlobal(128)
        Dim TempData(128) As Byte
        
            Result = GetNumber(NumberData, DataLength)

            If Result = SuccessFlag Then
                Marshal.Copy(NumberData, TempData, 0, DataLength)
                lblNumber.Text = System.Text.ASCIIEncoding.ASCII.GetString(TempData)
            Else
                MessageBox.Show("Error reading Number.", "Data error", MessageBoxButtons.OK, MessageBoxIcon.Warning)
                lblNumber.Text = "ERROR"
            End If

            Marshal.FreeHGlobal(NumberData)
End Sub 


This works, but I barely understand why.

-------------------------------------------------

Here is second function, similar to first (abbreviated):


GetOtherNumber (bool c_flag, char *Number1, BYTE *Number2, int * length);  '<==returns int


I change it to VB.net:

GetOtherNumber Lib "Foo.dll" (ByVal DataFlag As Boolean, ByVal DataString As String, ByVal DataData As IntPtr, ByRef Length As Int32) As Int32 
'(intent is to return data into locations as pointed to by DataData and Length pointers which are passed in; DataFlag and DataString are passed in as supporting data; Int32 is return value = error code)


Here is VB.net code:

Sub Bar
        Dim Result As Int32
        Dim DataLength As Int32
        Dim DataData As IntPtr = Marshal.AllocHGlobal(512)
        Dim TempData(512) As Byte
              
            Result = GetOtherNumber(1, "1234567812345678", DataData, DataLength)  '<==Note: Both the "1" for bool DataFlag, and "1234567812345678", are same values as used by legacy calling function, which of course works

            If Result = SuccessFlag Then            
                Marshal.Copy(DataData, TempData, 0, DataLength)
                lblOtherNumber.Text = System.Text.ASCIIEncoding.ASCII.GetString(TempData)
            Else
                MessageBox.Show("Error reading Other Number.", "Data error", MessageBoxButtons.OK, MessageBoxIcon.Warning)
                lblOtherNumber.Text = "ERROR"
            End If
            
     Marshal.FreeHGlobal(PINData)
End Sub


This does not work! The DLL returns an error indicating that the call has a bad parameter(s). I tried to follow the example of the
first (working) call. Is "Char" vs "Byte" different? How do I make call work with BYTE pointer? Or is it another problem?

Thank you.

Charlie
 
Last edited:
Solved it! I had accidently called the DLL function in the second example using the function name from the first example. Works now.

Charlie
 
Here is the background behind my original post:
I have an external hardware device that provides data upon a specific hardware event. To get the data into the program, you must register the code function, by name, as a callback from the hardware device. To do this properly requires two threads. The first thread handles two buttons, GO and STOP. GO starts the data stream when the external event occurs, STOP stops it. However, the function that actually parses the data must be in a second thread, so that the first thread can handle the STOP button as needed. So here's how it works:

1) GO button sets up callback pointer to the data-parse function (Thread #1)
2) GO button registers the callback function with the external hardware (Thread #1)
3) GO button starts the second thread (Thread #1)
4) The second thread starts the external hardware operation (Thread #2)
5) The external hardware event occurs and produces data (external)
6) The data comes in to the callback function to be parsed (Thread #2)
7) The STOP button is available to end the data stream (Thread #1)

The problem I am having is that the 200+ bytes of data show up in the buffer as only the first byte. I have verified that all 200+ bytes are being sent by the hardware, and that the first (and only) byte is correct.

Here's my code to date:

VB.NET:
    'Class level...

    Private Declare Function EnableData Lib "foo.dll" () As Int32 
    Private Declare Function DisableData Lib "foo.dll" () As Int32  
    Private Declare Function AddDataHandle Lib "foo.dll" (ByValdHandler As MyFunctPtr, ByVal ThisClass As IntPtr) As Int32

    Private Delegate Sub MyFunctPtr(ByVal DataBuffer As Byte(), ByVal DataLength As Integer, ByVal ParamPointer As IntPtr) 

    Dim Callback As System.Threading.Thread


    'Code level...

    Sub GoButton_Click
    'This is in Thread #1

        Dim Result As Int32

        'Define the callback, point to desired second thread
        Callback = New System.Threading.Thread(AddressOf EnableReadData) 

        'Register the callback with the external hardware
        Result = AddDataHandle(AddressOf ParseDataHandler, IntPtr.Zero)

        'Start the second thread
        Callback.Start()

    End Sub

    Sub StopButton_Click
    'This is in Thread #1

        Dim Result As Int32

        'Stop the hardware device
        Result = DisableData()

    End Sub

    Sub EnableReadData()
    'This is in Thread #2


        Dim Result As Int32

        'Start the hardware device
        Result = EnableData()

    End Sub

    Sub ParseDataHandler(DataBuffer As Byte(), DataLength As Integer, ParamPointer As IntPtr)
    'This is in the callback

        Debug.Print(DataBuffer(0))  '<==Why does this buffer only show 1 byte of data???

    End Sub

There are actually two problems here, but the most important at this point is to get the full 200+ bytes of data that SHOULD be in the DataBuffer. Why can the program not see the remaining bytes in the data stream? Where are they being lost? Improper array declaration? Improper pointer operation? Help!

Thanks
Charlie
 
Last edited:
charliee1151 said:
VB.NET:
Debug.Print(DataBuffer(0))  '<==Why does this buffer only show 1 byte of data???
It's not an issue with the buffer. DataBuffer is a Byte array and DataBuffer(0) is the element at index zero in that array, i.e. the first Byte. If you want to see the entire buffer in that spot then try this:
VB.NET:
Debug.Print(String.Join(" ", DataBuffer))
That will join all the elements of DataBuffer together into a single String with a space between each pair of adjacent elements.
 
Success

Success!
Here's the final code snippets:

VB.NET:
Private Declare Function EnableData Lib "Foo.dll" () As Int32 
Private Declare Function DisableData Lib "Foo.dll" () As Int32  
Private Declare Function AddDataHandle Lib "Foo.dll" (ByVal Handler As MyFunctPtr, ByVal ThisClass As IntPtr) As Int32
 
Private Delegate Sub MyFunctPtr(ByVal DataBuffer As IntPtr, ByVal DataLength As Integer, ByVal ParamPointer As IntPtr)

Dim Callback As System.Threading.Thread
Delegate Sub SetTextCallback([text] As String)
 

Private Sub GoButton_Click(sender As Object, e As EventArgs) 
        
        Dim Result As Int32

        'Define the callback, point to desired second thread
        Callback = New System.Threading.Thread(AddressOf Me.EnableReadData)  

        
        
   
        
        'Register the callback with the external hardware
        'NOTE:  THE DLL EXPECTS THE LAST PARAMETER HERE TO BE AN OBJECT, SO IT CAN TURN AROUND AND PASS THAT
        'SAME OBJECT BACK TO US AS THE "PARAMPOINTER" IN THE "MYFUNCTPTR" DELEGATE.  HOWEVER, WE CAN SET IT TO 
        'ZERO SIMPLY BECAUSE WE DON'T CARE ABOUT IT.  WE ALREADY KNOW WE WANT THE DATA TO END UP IN A SPECIFIC
        'CONTROL, SO 'WE'LL INVOKE THAT CONTROL OURSELVES WHEN NEEDED.     SEE "ParseDataHandler"
        Result = AddDataHandle(AddressOf ParseDataHandler, IntPtr.Zero)

        'Start the second thread "EnableReadData"
        Callback.Start()

End Sub
 

  
 
Private Sub EnableReadData()

        Dim Result As Int32
        Dim ErrorData As String  
 

      
        'Start the hardware device
        Result = EnableData()

End sub
   
  


 Private Sub ParseDataHandler(ByVal DataBuffer As IntPtr, ByVal DataLength As Integer, ByVal ParamPointer As IntPtr)
       'HERE IS WHERE WE CAN IGNORE THE LAST PARAMETER, AS IT WAS PASSED IN VIA THE DLL AND IS SUPPOSED TO  
       'REPRESENT THE OBJECT THAT DISPLAYS THE DATA, IN THIS CASE OUR "lblData" LABEL.  SINCE WE ARE 
       'CROSS_THREADING TO SHOW THE DATA ANYWAY, WE ALREADY KNOW WHERE WE ARE GOING TO SEND IT, SO WE JUST DO 
       'THAT; DON'T NEED THE LAST PARAMETER DATA.  SEE "GoButton_Click"
         

 
        Dim Data1 As String
        Dim Data2 As New System.Text.StringBuilder(DataLength * 2)
        Dim TempChar As String
        Dim TempData(DataLength - 1) As Byte
        Dim TempByte As Byte

        'Copy DataBuffer stream into TempData byte array
        System.Runtime.InteropServices.Marshal.Copy(DataBuffer, TempData, 0, DataLength)

        'Convert each byte in the byte array into a two nibble hex stream
        For Each TempByte In TempData
            TempChar = Conversion.Hex(TempByte)
            If TempChar.Length = 1 Then TempChar = "0" & TempChar
            Data2.Append(TempChar)
            Data2.Append(" ")
        Next

        'Convert hex stream to string
        Data1 = Data2.ToString()

        'Call the cross-thread delegate operation
        Me.ShowData([Data1])

        Application.DoEvents()

End Sub
  

 

Private Sub ShowData(ByVal [Data] As String)

        'Is thread that originally created lblData the same thread that wants to use it now?
        If Me.lblData.InvokeRequired Then
            'No, so need to invoke the delegate for it...
            'Define the delegate
            Dim DataDelegate As New SetTextCallback(AddressOf ShowData)

            'Invoke the delegate, passing the text
            Me.Invoke(DataDelegate, New Object() {[Data]})

        Else

            'Yes, so can write directly.  NOTE: THIS SHOULD NEVER HAPPEN, WE ARE NOT CALLING DIRECT FROM ANYPLACE
            Me.lblData.Text = [Data]

        End If

        Application.DoEvents()

End Sub

Private Sub Stop_Click(sender As Object, e As EventArgs) 
        
  

   
        Dim Result As Int32
        Dim ErrorData As String
 

       
         
 
        Result = DisableData()  
   

            
End Sub

I want to thank everyone who took the time to point me in the right direction. I hope this code example helps others in turn.

Charlie
 
charliee1151 said:
VB.NET:
            'Yes, so can write directly.  NOTE: THIS SHOULD NEVER HAPPEN, WE ARE NOT CALLING DIRECT FROM ANYPLACE
            Me.lblData.Text = [Data]
Um, yes it should. What do you think invoking the delegate does? Your delegate refers to that same ShowData method so the Invoke method crosses the thread boundary and invokes the delegate on the UI thread, thus calling the ShowData method a second time. In that second call, InvokeRequired is False because it's on the UI thread, therefore the Else block is what gets executed and that's how the Label Text gets set, which is the whole point. If that did in fact never happen then the Label would never change.

By the way, get rid of that DoEvents call. If you want to force the Label to repaint then call its Refresh method and do it inside the Else block. Like I said, you're executing that method twice so you are currently calling DoEvents twice.
 
==>Your delegate refers...<==
Yes, after studying that, I realized that it actually calls itself. My comment about calling it directly was meant to indicate that I am not setting/changing the text a)from anyplace else, and b)without going through the callback. However, your comment about the double DoEvents statement is valid; I added that early on, and neglected to go back and fix-it-up. And, of course, I will update to the Refresh method as well.

Thanks, you've been a great help.

Charlie
 
Back
Top