brownpeterg
asked on
Raising events with asynchronous callback from a dll
This is a VB.Net Question
OK, so I've figured (with some help) out how to receive an asynchronous callback from a dll written in C++ using delegates. But I've also just figured out that a) callbacks run on separate threads from the UI and b) I don't -need/want- a separate thread. What I'd like to do is have the callback raise an event that interrupts rather than 'parallel' (a separate thread) the main thread (if possible).
So this is a three part question:
1) Is it possible to for a callback to raise an event in the primary thread?
2) If this isn't possible, is there any way to use events for this purpose or should I simply resign myself to handling a separate thread for each callbacks?
3) Any code examples of how to do either would be great, thanks
pgb
OK, so I've figured (with some help) out how to receive an asynchronous callback from a dll written in C++ using delegates. But I've also just figured out that a) callbacks run on separate threads from the UI and b) I don't -need/want- a separate thread. What I'd like to do is have the callback raise an event that interrupts rather than 'parallel' (a separate thread) the main thread (if possible).
So this is a three part question:
1) Is it possible to for a callback to raise an event in the primary thread?
2) If this isn't possible, is there any way to use events for this purpose or should I simply resign myself to handling a separate thread for each callbacks?
3) Any code examples of how to do either would be great, thanks
pgb
ASKER
AlexFM,
Thanks for responding. Even if I wanted to I couldn't modify the dll. Its a separately provided and while I have documentation re its functions, I have no insite into its code. Also, I don't believe the begininvoke or invoke functions will help me either. In order to receive an asynch callback I must 'register' the address of each of my callback functions (there are many) with the dll by using a separately providing function that gives the dll the address of each callback. Then the dll can use the callback function addresses none, one or more times as it chooses. Hope this clarifies.
pgb
Thanks for responding. Even if I wanted to I couldn't modify the dll. Its a separately provided and while I have documentation re its functions, I have no insite into its code. Also, I don't believe the begininvoke or invoke functions will help me either. In order to receive an asynch callback I must 'register' the address of each of my callback functions (there are many) with the dll by using a separately providing function that gives the dll the address of each callback. Then the dll can use the callback function addresses none, one or more times as it chooses. Hope this clarifies.
pgb
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
AlexFM,
Thanks again. I'm much closer to the solution. Your suggestion above works as long as I have my callback function in the form class.
Complexity and the desire to have all of my callbacks located in other classes, turns "me.beginInvoke" into "form1.begininvoke" with requiste references to the (other) class where the CallbackDelegate delegate and HandleData function reside. Specifically neither of the following work.
Dim F1 as form1
F1.BeginInvoke(New LocalCallBackDelegates.Cal lBackDeleg ate(Addres sOf LocalCallBackDelegates.Han dleData), New Object() {textString})
===The above compiles and runs fine but the output window shows the following:
A first chance exception of type 'System.NullReferenceExcep tion' occurred in VBproject.exe
Or Simply:
form1.BeginInvoke(New LocalCallBackDelegates.Cal lBackDeleg ate(Addres sOf LocalCallBackDelegates.Han dleData), New Object() {textString})
===The above also compiles and runs fine but the output window shows the following:
A first chance exception of type 'System.InvalidOperationEx ception' occurred in System.Windows.Forms.dll
I suppose I should have a better understanding of this, but how do I change statements like the above to reference (the form ) where the main thread is working to allow them to update that (or another) form's controls?
This has been a real struggle for me. Thanks for your help
Pete
Thanks again. I'm much closer to the solution. Your suggestion above works as long as I have my callback function in the form class.
Complexity and the desire to have all of my callbacks located in other classes, turns "me.beginInvoke" into "form1.begininvoke" with requiste references to the (other) class where the CallbackDelegate delegate and HandleData function reside. Specifically neither of the following work.
Dim F1 as form1
F1.BeginInvoke(New LocalCallBackDelegates.Cal
===The above compiles and runs fine but the output window shows the following:
A first chance exception of type 'System.NullReferenceExcep
Or Simply:
form1.BeginInvoke(New LocalCallBackDelegates.Cal
===The above also compiles and runs fine but the output window shows the following:
A first chance exception of type 'System.InvalidOperationEx
I suppose I should have a better understanding of this, but how do I change statements like the above to reference (the form ) where the main thread is working to allow them to update that (or another) form's controls?
This has been a real struggle for me. Thanks for your help
Pete
AddressOf LocalCallBackDelegates.Han dleData
This is incorrect. You need to pass address of form method, something like this:
form1.BeginInvoke(New LocalCallBackDelegates.Cal lBackDeleg ate(Addres sOf form1.HandleData), New Object() {textString})
HandleData should be Form1 method.
This is incorrect. You need to pass address of form method, something like this:
form1.BeginInvoke(New LocalCallBackDelegates.Cal
HandleData should be Form1 method.
ASKER
AlexFM,
Thanks once again! I tried your latest suggestion in the context of the class set I have created and it wouldn’t work. In fact I got the same error as before. While I was able to use your previous suggestions to get the asynchronous callback to work and display its results in the primary/UI thread; I can only make this work when all of the delegate, callback and UI update code are all in the single form (ie the 'me' representation works)
This would seem to be fine for some smaller apps that do not have but a few callbacks and a relatively limited set of controls on a single form. But my app will have numerous callbacks and many controls on a multi-tabbed form.
Again, I could make this work, but I would much prefer not to have all that code in the form class. (What is absolutely necessary?) So then, how should I organize my code so that I can put all of the dll interface code is in a separate project in the solution. The idea is to make the dll interface code readily transportable to other applications. Obviously, it will be necessary to put some code in my own application, for example the unique code that performs work as a result of an asynchronous callback. Ideally here, I like the asynchronous callbacks to act as if they were system events, that interrupt the main thread, execute and then return control back to the Main (UI) thread.
To explain further what I’m doing, let me articulate the requirements I have and then I’ll show you the architecture for my code that I’m attempting to meet the requirements with.
1) I have a DLL that I need to use in my program that interfaces to an IP address out on the web. For the most part I look at this dll as local server to my app and a client to the remote IP address.
2) The dll requires that I enable many different asynchronous callbacks in my code
3) I must register my callbacks with the dll by give the dll the address of each callback subroutine using a function call to the dll. I do this of course, using delegates.
4) Once registered the dll can use the callback subroutines as frequently or as seldom as it choses. Eg{0..N}
5) The primary user interface consists of a single form that has numerous tabs, many controls and many underlying functions (before I even began to create an interface with the dll).
6) So…I need to segregate the dll interface code (delegates, declarations of new delegate objects and callback functions in classes separate from the mainform (if possible?). I need to do this for two reasons
a. The form code would become unwieldy and difficult to manage
b. Primarily, to all the dll interface code to become reuseable. Here specifically, I need to keep my app code and my dll interface code as ‘loosely coupled’ as possible.
So I suppose my question is now how do I do this (specifically #6)?
Here, following; is the basic structure (its not functional code) of what I’ve described earlier and hopefully with some more clarity. Hopefully this makes sense. Again, I've researched this and tried to work it out but without success. I did find some good information at the MSDN site that lists all of Mr Pattison's articles.
Solution: VB_and_CPP_DLL_Merge
Project1: DLL interface Code
Class/File1: DLL Function “Protoypes”--- Pertinent Code follows:
Public Class APIFunctions
‘the following function simply “registers” the callback function
<DllImport(APIFileSpec, CharSet:=CharSet.Ansi)> Public Shared Function SetHostAddress
(<MarshalAs(UnmanagedType. VBByRefStr )> ByRef strHostIPAddress As String,
<MarshalAs(UnmanagedType.V BByRefStr) > ByRef strHostIPSocket As String)
As Integer
End Function
Class/File2: DLL Constants--- Pertinent Code follows:
' Constants for callback types.
Public Const HostLinkStateChange As Integer = 1
Public Const PriceLinkStateChange As Integer = 2
Public Const LogonStatus As Integer = 3
Class/File3: Delegates— Pertinent Code follows:
Public Class Delegates
Public Delegate Sub myLinkStateChange(ByRef LSS As LinkStateStruc)
Class/File4: Structure Definitions— Pertinent Code follows:
Public Class StructureDefs
<StructLayout(LayoutKind.S equential, CharSet:=CharSet.Ansi)> Public Structure LinkStateStruc
<MarshalAs(UnmanagedType.U 1)> Public OldState As Byte
<MarshalAs(UnmanagedType.U 1)> Public NewState As Byte
End Structure
Project2: VBProject
Class/File1: vbFrm---Pertinent Code Follows:
Public Delegate Sub MyUpdateDelegate(ByVal updateText As String)
Public Class vbfrm
‘the function below simply registers the callback address
Private Sub butRegisterHostLinkStateCa llback_Cli ck(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles butRegisterHostLinkStateCa llback.Cli ck
Dim functionResult As Integer
functionResult = RegisterLinkStateCallback( HostLinkSt ateChange, hostLinkStateCallBackDeleg ate)
End Sub
Public Sub updateStatusInfo(ByVal stbxExtraInfoText As String)
If stbxStatusInfo.InvokeRequi red Then
Dim mad As New MyUpdateDelegate(AddressOf updateStatusInfo)
Me.Invoke(mad, stbxExtraInfoText)
Else
stbxStatusInfo.AppendText( stbxExtraI nfoText & vbCrLf)
stbxStatusInfo.Invalidate( )
End If
End Sub
Class/File2: DeclarationsForRemoteDeleg ates---Per tinent Code Follows:
Partial Public Class vbfrm
Public Shared hostLinkStateCallBackDeleg ate = New myLinkStateChange(AddressO f
Event_HostLinkStateChange)
Class/File3: vbfrm_Callbacks---Pertinen t Code Follows:
Partial Public Class vbfrm
Public Shared Sub HandleData(ByVal returnString As String)
vbfrm.stbxStatusInfo.Appen dText(retu rnString)
vbfrm.stbxStatusInfo.Appen dText(vbCr Lf)
End Sub
Public Shared Sub Event_HostLinkStateChange( ByRef Data As LinkStateStruc)
Dim textString As String
textString = "HostLinkStateChange Variables Received: OldState=" &
getLinkStateCodeInfo(Data. OldState) & " NewState= " &
getLinkStateCodeInfo(Data. NewState) & vbCrLf
vbfrm.BeginInvoke(New CallBackDelegate(AddressOf vbfrm.HandleData), New Object() {textString})
End Sub
Thanks once again! I tried your latest suggestion in the context of the class set I have created and it wouldn’t work. In fact I got the same error as before. While I was able to use your previous suggestions to get the asynchronous callback to work and display its results in the primary/UI thread; I can only make this work when all of the delegate, callback and UI update code are all in the single form (ie the 'me' representation works)
This would seem to be fine for some smaller apps that do not have but a few callbacks and a relatively limited set of controls on a single form. But my app will have numerous callbacks and many controls on a multi-tabbed form.
Again, I could make this work, but I would much prefer not to have all that code in the form class. (What is absolutely necessary?) So then, how should I organize my code so that I can put all of the dll interface code is in a separate project in the solution. The idea is to make the dll interface code readily transportable to other applications. Obviously, it will be necessary to put some code in my own application, for example the unique code that performs work as a result of an asynchronous callback. Ideally here, I like the asynchronous callbacks to act as if they were system events, that interrupt the main thread, execute and then return control back to the Main (UI) thread.
To explain further what I’m doing, let me articulate the requirements I have and then I’ll show you the architecture for my code that I’m attempting to meet the requirements with.
1) I have a DLL that I need to use in my program that interfaces to an IP address out on the web. For the most part I look at this dll as local server to my app and a client to the remote IP address.
2) The dll requires that I enable many different asynchronous callbacks in my code
3) I must register my callbacks with the dll by give the dll the address of each callback subroutine using a function call to the dll. I do this of course, using delegates.
4) Once registered the dll can use the callback subroutines as frequently or as seldom as it choses. Eg{0..N}
5) The primary user interface consists of a single form that has numerous tabs, many controls and many underlying functions (before I even began to create an interface with the dll).
6) So…I need to segregate the dll interface code (delegates, declarations of new delegate objects and callback functions in classes separate from the mainform (if possible?). I need to do this for two reasons
a. The form code would become unwieldy and difficult to manage
b. Primarily, to all the dll interface code to become reuseable. Here specifically, I need to keep my app code and my dll interface code as ‘loosely coupled’ as possible.
So I suppose my question is now how do I do this (specifically #6)?
Here, following; is the basic structure (its not functional code) of what I’ve described earlier and hopefully with some more clarity. Hopefully this makes sense. Again, I've researched this and tried to work it out but without success. I did find some good information at the MSDN site that lists all of Mr Pattison's articles.
Solution: VB_and_CPP_DLL_Merge
Project1: DLL interface Code
Class/File1: DLL Function “Protoypes”--- Pertinent Code follows:
Public Class APIFunctions
‘the following function simply “registers” the callback function
<DllImport(APIFileSpec, CharSet:=CharSet.Ansi)> Public Shared Function SetHostAddress
(<MarshalAs(UnmanagedType.
<MarshalAs(UnmanagedType.V
As Integer
End Function
Class/File2: DLL Constants--- Pertinent Code follows:
' Constants for callback types.
Public Const HostLinkStateChange As Integer = 1
Public Const PriceLinkStateChange As Integer = 2
Public Const LogonStatus As Integer = 3
Class/File3: Delegates— Pertinent Code follows:
Public Class Delegates
Public Delegate Sub myLinkStateChange(ByRef LSS As LinkStateStruc)
Class/File4: Structure Definitions— Pertinent Code follows:
Public Class StructureDefs
<StructLayout(LayoutKind.S
<MarshalAs(UnmanagedType.U
<MarshalAs(UnmanagedType.U
End Structure
Project2: VBProject
Class/File1: vbFrm---Pertinent Code Follows:
Public Delegate Sub MyUpdateDelegate(ByVal updateText As String)
Public Class vbfrm
‘the function below simply registers the callback address
Private Sub butRegisterHostLinkStateCa
System.EventArgs) Handles butRegisterHostLinkStateCa
Dim functionResult As Integer
functionResult = RegisterLinkStateCallback(
End Sub
Public Sub updateStatusInfo(ByVal stbxExtraInfoText As String)
If stbxStatusInfo.InvokeRequi
Dim mad As New MyUpdateDelegate(AddressOf
Me.Invoke(mad, stbxExtraInfoText)
Else
stbxStatusInfo.AppendText(
stbxStatusInfo.Invalidate(
End If
End Sub
Class/File2: DeclarationsForRemoteDeleg
Partial Public Class vbfrm
Public Shared hostLinkStateCallBackDeleg
Event_HostLinkStateChange)
Class/File3: vbfrm_Callbacks---Pertinen
Partial Public Class vbfrm
Public Shared Sub HandleData(ByVal returnString As String)
vbfrm.stbxStatusInfo.Appen
vbfrm.stbxStatusInfo.Appen
End Sub
Public Shared Sub Event_HostLinkStateChange(
Dim textString As String
textString = "HostLinkStateChange Variables Received: OldState=" &
getLinkStateCodeInfo(Data.
getLinkStateCodeInfo(Data.
vbfrm.BeginInvoke(New CallBackDelegate(AddressOf
End Sub
ASKER
This has turned out to be harder than I initially thought.
ASKER
The first answer that AlexFM helped a lot, but I was able to come up with the actual answer that I was looking for. By using the using partial classes I am able to separate much of the functionality into separate files. This helps code managment a good bit for me. However, I am distressed that the only way that AlexFMs answer will accept the asynchronous callback in feed it into the UI is the the form that originated the callback. I keep thinking there must be some better way.....
((What I'd like to do is give AlexFM 300 points and myself 200 points. But I can't see how to do this accepting "Multiple Answers" only allows me to see all of AlexFM's responses and non of my own...If an adminstrator looks at this, you help me out?))
((What I'd like to do is give AlexFM 300 points and myself 200 points. But I can't see how to do this accepting "Multiple Answers" only allows me to see all of AlexFM's responses and non of my own...If an adminstrator looks at this, you help me out?))
However, there is easy way to marshal this call to main application thread - use Control.Invoke or Control.BeginInvoke methods. I don't think that C++ Dll is appropriate place to do this, it is much better to call Invoke or BeginInvoke from VB client.