我有两个问题;我相信第一个更容易的问题必须在第二个之前解决,所以我在这里只坚持那个。
首先,概述:我有一个使用 USB 端口的硬件设备,并且有一个自定义 DLL 可以与它通信。我正在使用 VB.net 从 C++ 升级。自定义 DLL 有许多函数,我已经能够为除一个之外的所有函数进行编程,使用 IntPtr 和 Marshalling 函数进行更简单的 DLL 调用;最后一个,这将是我的第二个问题/帖子,给我带来了问题。它是一种回调类型操作,并使用 TYPEDEF 定义。
所以,第一个问题:我如何转换
typedef void (WINAPI *MyFunctPtr)(unsigned char*,int, LPVOID)
Run Code Online (Sandbox Code Playgroud)
进入VB.net?我理解(我认为)这是定义一个名为 MyFunctPtr 的指针,它接受三个参数,并且是 VOID 的别名,这意味着它不会返回任何内容。这是正确的,我如何在 VB.net 中使用它?
typedef 的用法如下:
AddHandler(MyPtrType func, LPVOID pParam);
Run Code Online (Sandbox Code Playgroud)
其中 AddHandler 是 DLL 调用(这将是我的第二个问题/帖子的主题,以及所需的 DECLARE 语句)。
为了追求这个主题,我查看了许多论坛和问答式讨论,但似乎没有一个专门解决这个问题(至少,我的无知不能说)。我确实在这个论坛中发现了一个与同一问题非常接近的主题(“在 .NET 中使用 C 回调函数”),但我知道的不够多;我什至不明白答案,更别提问题了!
正如我所指出的,这个问题还有第二部分:
1.此代码旨在通过 USB 与外部硬件设备进行通信。我成功地使用了许多其他函数,使用 DLL 调用和带 INTPTR 的封送处理。
2.但是这段代码需要的功能有些不同。从本质上讲,涉及四个方面的努力:
a) 通过执行 DLL 调用来响应“GoButton”点击,该调用将 CallBack 函数注册到外部设备(这是一个 DLL 调用,当然,传递对 CallBack 函数的引用。这会告诉外部硬件向何处发送它是发生适当事件时的数据)并产生第二个线程。
b) 作为新产生的第二个线程,通过执行 DLL 调用来响应,该调用实际上告诉外部硬件“OK,开始响应事件,并将数据发送到 CallBack”
c) 在第一个/原始线程中,通过执行 DLL 调用来响应“StopBUtton”单击,该调用实际上告诉外部硬件,“OK,停止响应事件,并且不向 CallBack 发送任何数据”
d) CallBack 函数本身。
“D”只是一个数据处理程序,我相信它应该与我已经为其他非 CallBack 函数编写的数据处理程序没有什么不同。“B”实际上产生了第二个线程来处理回调响应,因为第一个线程必须可用于响应“C”的点击事件。
好的,这是旧的 DLL,按顺序:
一种)
BYTE WINAPI AddHandler(MyPtrType func, LPVOID pParam); //BYTE is Int32 in VB.net
Run Code Online (Sandbox Code Playgroud)
注意“MyPtrType”类型定义的使用(这里重复定义),它与回调函数具有相同的三个指针
typedef void (WINAPI *MyPtrType)(unsigned char*, int, LPVOID);
Run Code Online (Sandbox Code Playgroud)
b)
BYTE WINAPI Enable(); //BYTE is Int32 in VB.net
Run Code Online (Sandbox Code Playgroud)
C)
BYTE WINAPI Disable(); //BYTE is Int32 in VB.net
Run Code Online (Sandbox Code Playgroud)
下面是调用上面的代码函数:
一种)
GoButton_Click()
{
AddHandler(MyCallbackFunction, this);
BeginThread(SecondThread, this);
//First thread has spawned second thread, and is now free to continue = exit this function
}
Run Code Online (Sandbox Code Playgroud)
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;
}
Run Code Online (Sandbox Code Playgroud)
c) 在“停止”按钮事件中:
StopButton_Click()
{
int ResponseFlag = 0; //int is Int32 in VB.net
ResponseFlag = Disable();
}
Run Code Online (Sandbox Code Playgroud)
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;
}
}
Run Code Online (Sandbox Code Playgroud)
我知道 BYTE = Int32 在我的系统中,因为我在其他功能中成功使用了它。
这就是我现在的位置:
typedef void (WINAPI *MyFunctPtr)(unsigned char*,int, LPVOID)
Run Code Online (Sandbox Code Playgroud)
我不仅不确定要为 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
Run Code Online (Sandbox Code Playgroud)
再次,我必须感谢大家的帮助。
查理
================================================== ============================== 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
Run Code Online (Sandbox Code Playgroud)
在这一点上,我有两个问题和一个疑问:
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
Run Code Online (Sandbox Code Playgroud)
我要感谢所有花时间为我指明正确方向的人。我希望这个代码示例反过来可以帮助其他人。
查理
根据@David Heffernan 的提示,我会尝试一下:
Delegate Sub MyFunctPtr(a As Byte(), b As Integer, c As Byte())
Run Code Online (Sandbox Code Playgroud)
void意味着没有返回任何内容。Sub与 VB.NET 中的等效项。这是一个解释如何unsigned char*成为Byte(). 第二个参数也可以是 a Short,具体取决于您的 C++ 代码使用的编译器。
我放弃了WINAPI,因为它是一样的,__stdcall而且VB只能使用它。
最后一个也可以写为c As Any,具体取决于所使用的函数:
如何编组 LPVoid 参数的正确答案特定于函数。