我有两个问题;我相信第一个更容易的问题必须在第二个问题之前解决,所以我在这里只坚持那个。
首先,概述:我有一个使用 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
我要感谢所有花时间为我指明正确方向的人。我希望这个代码示例反过来帮助其他人。
查理