5

我有两个问题;我相信第一个更容易的问题必须在第二个问题之前解决,所以我在这里只坚持那个。

首先,概述:我有一个使用 USB 端口的硬件设备,并且有一个自定义 DLL 可以与之通信。我正在使用 VB.net 从 C++ 升级。自定义 DLL 有很多函数,我已经能够为除一个以外的所有函数编程,使用 IntPtr 和 Marshalling 函数进行更简单的 DLL 调用;最后一个,这将是我的第二个问题/帖子,给我带来了问题。它是一个回调类型操作并使用 TYPEDEF 定义。

所以,第一个问题:我如何转换

typedef void (WINAPI *MyFunctPtr)(unsigned char*,int, LPVOID)

进入VB.net?我理解(我认为)这是定义一个名为 MyFunctPtr 的指针,它接受三个参数,并且是 VOID 的别名,这意味着它不会返回任何内容。这是正确的,我如何在 VB.net 中使用它?

typedef 使用如下:

AddHandler(MyPtrType func,  LPVOID pParam);

其中 AddHandler 是 DLL 调用(这将是我的第二个问题/帖子的主题,以及所需的 DECLARE 语句)。

为了追求这个主题,我查看了许多论坛和 Q/A 类型的讨论,但似乎没有一个专门解决这个问题(至少,在我的无知中,我不能说出来)。我确实在这个论坛中发现了一个似乎非常接近同样问题的线程(“ Using a C-callback function with .NET ”),但我知道的不够多;我什至不明白答案,别管这个问题!

正如我所指出的,这个问题还有第二部分:

1.此代码旨在通过 USB 与外部硬件设备进行通信。我使用 DLL 调用和 INTPTR 编组等许多其他功能成功地做到了这一点。

2.此代码所需的功能有些不同。从本质上讲,这涉及到四方面的努力:

a) 通过执行向外部设备注册 CallBack 函数的 DLL 调用来响应“GoButton”单击(这是一个 DLL 调用,当然,传递对 CallBack 函数的引用。这告诉外部硬件将发送到哪里它是发生适当事件时的数据)并产生第二个线程。

b) 作为新生成的第二个线程,通过执行 DLL 调用来响应,该调用实际上是告诉外部硬件“OK,开始响应事件,并将数据发送到 CallBack”

c) 在第一个/原始线程中,通过执行 DLL 调用来响应“StopBUtton”单击,这实际上是告诉外部硬件,“好的,停止响应事件,并且不向回调发送任何数据"

d) 回调函数本身。

“D”只是一个数据处理程序,我相信它应该与我已经为其他非回调函数编写的数据处理程序没有什么不同。“B”实际上产生了第二个线程来处理回调响应,因为第一个线程必须可用于响应“C”的单击事件。

好的,这是旧版 DLL,按顺序排列:

一个)

    BYTE WINAPI AddHandler(MyPtrType func,  LPVOID pParam); //BYTE is Int32 in VB.net

注意使用“MyPtrType”typedef(定义在此重复),它与CallBack函数具有相同的三个指针

    typedef void (WINAPI *MyPtrType)(unsigned char*, int, LPVOID);

b)

    BYTE WINAPI Enable(); //BYTE is Int32 in VB.net

C)

    BYTE WINAPI Disable();  //BYTE is Int32 in VB.net

以下是调用上述内容的代码函数:

一个)

    GoButton_Click()
    {
        AddHandler(MyCallbackFunction, this);
        BeginThread(SecondThread, this);
        //First thread has spawned second thread, and is now free to continue = exit this function
    }

b)在第二个线程中:

    SecondThread(LPVOID pParam)
    {
       Dialog* pthis = (Dialog*)pParam;
       int ResponseFlag = 0; //int is Int32 in VB.net
       ResponseFlag = Enable();
       //This call will not return until the external device gets the "Stop" command, thus it exists in the second thread
       return 0;
    }

c) 在“停止”按钮事件中:

    StopButton_Click()
    {
        int ResponseFlag = 0; //int is Int32 in VB.net
        ResponseFlag = Disable();
    }

d) 在回调函数中:

    MyCallbackFunction((unsigned char *buf, int rev, LPVOID pParam))
    {
        Dialog* pthis = (Dialog*)pParam;
        CString str;

        for(int i = 0; i < rev; i++)
        {   
            str.Format("%02X ",buf[i]);
            pthis->Data += str;
        }   
    }

我知道 BYTE = Int32 在我的系统中,因为我在其他功能中成功使用了它。

这就是我现在的位置:

    Private Delegate Sub ParsedDataDelegate()
    Private Declare Function EnableData Lib "Foo.dll" () As Int32 'EnableData()
    Private Declare Function DisableData Lib "Foo.dll" () As Int32  'DisableData()
    Private Declare Function AddDataHandle Lib "Foo.dll" (By??? ??? As IntPtr, By??? Parameter As IntPtr) As Int32    'AddDataHandle(MyFunctPtr func,LPVOID pParam)
    'Note: the first parameter to "AddDataHandle" is some kind of reference to "ParseDataHandler" as the callback
    '==>ALSO, SOMETHING GOES HERE TO EQUATE TO "typedef void (WINAPI *MyFunctPtr)(unsigned char*,int, LPVOID)"

    Sub StartButton_Click
    'This is main thread

        Dim Result As Int32
        Dim EnableReadData As New Thread(AddressOf ParseDataHandler)

        'Register callback with external hardware device
        'Result = AddDataHandle(????, ????)  <==Don't yet know what to put here, 
        'until I figure out "typedef void (WINAPI *MyFunctPtr)(unsigned char*,int, LPVOID)"

        'Spawn second thread
        EnableReadData.Start()

    End Sub

    Sub EnableReadData
    'This is spawned thread

        Dim Result As Int32

        'Invoke the callback
        Me.Invoke(New ParseDataDelegate(AddressOf ParseDataHandler))

        'Start the hardware device to get data
        Result = EnableData() 'This DLL call blocks(here in the 2nd thread) until 
        'the StopButton_Click event (in the 1st thread) occurs to call the DisableData DLL

    End Sub

    Private Sub ParseDataHandler()

        'Grab and display data here

    End Sub

    Sub StopButton_Click

        Dim Result As Int32

        'Stop the hardware device
        Result = DisableData()

    End Sub

我不仅不确定 TypeDef 使用什么,我什至不确定我是否正确使用了 CallBack 的概念,这意味着我可能没有正确使用声明本身!

感谢您忍受这么长的帖子。我已经把头撞在墙上将近一个星期了,试图与这三个未知数作斗争,在这三个未知数之间,每个人都可能互相妥协。我完全迷失了,在这一点上,我不能乞求帮助。请帮我。

谢谢查理

==================================================== ==================== 10/28 编辑,更新:

这是我最近的尝试。请注意,尽管列出了错误,但我觉得我们正在取得进展。多亏了这个论坛的支持,我已经能够(我相信)继续前进,因为之前的错误似乎已经得到了显着解决,让我可以进行下一次尝试。请意识到,对我来说,这都是实验......我可能完全朝着错误的方向前进:

    Private Declare Function EnableData Lib "foo.dll" () As Int32 'EnableData()
    Private Declare Function DisableData Lib "foo.dll" () As Int32  'DisableData()
    Private Declare Function AddDataHandle Lib "foo.dll" (ByVal Handler As MyFunctPtr, ByVal ThisClass As IntPtr) As Int32    'AddDataHandle(MyFunctPtr func,LPVOID pParam)

    Private Delegate Sub ParseDataDelegate(DataBuffer As Byte(), DataLength As Integer, ParamPointer As IntPtr)
    Private Delegate Sub MyFunctPtr(DataBuffer As Byte(), DataLength As Integer, ParamPointer As IntPtr) 'typedef void (WINAPI *MyFunctPtr)(unsigned char*,int, LPVOID)



    Sub StartButton_Click
    'This is main thread

        Dim Result As Int32
        Dim Callback As ParseDataDelegate

        Note: Different attempts at same call...

        'Attempt #1 (no parameters) produces this error, repeated 3 times, one for each parameter: 
        'Argument not specified for parameter 'DataBuffer' of 'Private Sub ParseCardDataHandler(DataBuffer() As Byte, DataLength As Integer, ParamPointer As System.IntPtr)'.   
        Dim EnableReadData As New System.Threading.Thread(System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(ParseDataHandler()))

        'Attempt #2 (adding the parameters) produces this error, repeated 3 times, one for each parameter: 
        '1)'DataBuffer' is not declared. It may be inaccessible due to its protection level.
        Dim EnableData As New System.Threading.Thread(System.Runtime.InteropServices.Marshal.GetFunctionPtrForDelegate(ParseDataHandler(DataBuffer(), DataLength, ParamPointer)))


        Callback = AddressOf ParseDataHandler 'Don't let this get collected!  keep it in a class variable for as long as the DLL is using it        

        'I get this error here:
        'Value of type 'System.IntPtr' cannot be converted to 'xxxx.xxxx.MyFunctPtr'.
        Result = AddDataHandle(System.Runtime.InteropServices.Marshal.GetFunctionPtrForDelegate(Callback), IntPtr.Zero)

        EnableReadData.Start()

    End Sub


    Private Sub EnableReadData()

    Dim Result As Int32

        'This produces an error of  "Expression Expected", 3 times, one for each parameter.  What is wanted after the  ":="?  Or is this call wrong altogether?
        Me.Invoke(New ParseDataDelegate(AddressOf ParseDataHandler(DataBuffer:=,DataLength:=, ParamPointer:=)))

        Result = EnableData()

    End Sub

    Private Sub ParseDataHandler(DataBuffer As Byte(), DataLength As Integer, ParamPointer As IntPtr) '(a As Byte(), b As Integer, c As IntPtr)'(ByVal DataBuffer As String, ByVal Length As Integer, SomeParameter As IntPtr)

        Stop

    End Sub

再次,我必须感谢大家的帮助。

查理

==================================================== ================================ 10 月 29 日更新:

取得了一些进展。回调正在工作,但还有一些其他问题。这是迄今为止的代码:

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

    Private Delegate Sub MyFunctPtr(DataBuffer As Byte(), DataLength As Integer, ParamPointer As Object) 'typedef void (WINAPI *MyFunctPtr)(unsigned char*,int, LPVOID)

    Dim Callback As System.Threading.Thread

    'Code level...
    Sub StartButton_Click
    'This is main thread

        Dim Result As Int32

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

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

        'Start the second thread
        Callback.Start()

    End Sub

    Sub StopButton_Click

        Dim Result As Int32

        'Stop the hardware device
        Result = DisableData()

    End Sub

    Sub EnableReadData()
    'This is the secondary thread

        Dim Result As Int32

        'Start the hardware device
        Result = EnableData()

    End Sub

    Sub ParseDataHandler(DataBuffer As Byte(), DataLength As Integer, ParamPointer As Object) '(a As Byte(), b As Integer, c As IntPtr)'(ByVal DataBuffer As String, ByVal Length As Integer, SomeParameter As IntPtr)

        Debug.Print(DataBuffer(0))

    End Sub

在这一点上,我有两个问题和一个问题:

1) ParseDataHandler 例程中的 DataLength 值显示大约 200+ 字节的数据,但 DataBuffer 显示长度为 1。显然,1 是错误的,但 200+ 是否正确?这需要进一步研究。此外,如果 DataLength 是正确的,我不确定如何从 Byte 数组到字符串。

2) 我收到“SEHException 未处理”消息。描述为“外部组件已引发异常”。我假设,由于硬件一直在使用原始代码,它现在仍在工作。此外,术语“外部组件”实际上可能并不意味着系统外部,而是指作为主线程外部的第二线程。这似乎是一个可行的理论?

3) 我用于 AddDataHandle 的定义包括“...ThisClass As IntPtr”。IntPtr 实际上应该是一个对象,但要这样调用它,我必须传入一个对象。现在我正在使用 IntPtr.Zero,因为我认为正确的对象(“我”)给出了错误。我应该使用什么对象?我的基地?我的课?还是完全不同的东西?

继续我的追求,并感谢所有帮助过的人。现在,如果有人能就这最后三个问题给我建议……?:)

再次感谢,查理

====================================================

成功!10 月 30 日

这是最终的代码片段:

 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

我要感谢所有花时间为我指明正确方向的人。我希望这个代码示例反过来帮助其他人。

查理

4

2 回答 2

3

你的电话最终应该是

callback = AddressOf MyHandler ' don't let this get collected!  keep it in a class variable for as long as the DLL is using it
Result = AddDataHandle(Marshal.GetFunctionPtrForDelegate(callback), IntPtr.Zero)

在 C++ 中,第二个参数用于传递对象指针。在 .NET 中,这是行不通的,因为垃圾收集器在内存中移动对象。但这不是必需的,因为GetFunctionPtrForDelegate()函数在生成机器代码时会在内部对对象指针进行编码。

于 2013-10-28T00:19:03.847 回答
1

根据@David Heffernan 的提示,我会试一试:

Delegate Sub MyFunctPtr(a As Byte(), b As Integer, c As Byte())

void表示不返回任何内容。Sub是 VB.NET 中的等价物。这是一个链接来解释如何unsigned char*成为Byte(). 第二个参数也可能是 a Short具体取决于用于C++ 代码的编译器。

我放弃了WINAPI,因为它__stdcallVB一样,只能用那个

最后一个也可以写成c As Any取决于所使用的函数

如何编组 LPVoid 参数的正确答案是特定于函数的。

于 2013-10-27T22:11:37.740 回答