Link to home
Start Free TrialLog in
Avatar of brownpeterg
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
Avatar of AlexFM
AlexFM

Callback function is always called in the context of calling thread. If C++ code is executed in a worker thread, callback is called in this thread.
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.
Avatar of brownpeterg

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
ASKER CERTIFIED SOLUTION
Avatar of AlexFM
AlexFM

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
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.CallBackDelegate(AddressOf LocalCallBackDelegates.HandleData), New Object() {textString})
===The above compiles and runs fine but the output window shows the following:
A first chance exception of type 'System.NullReferenceException' occurred in VBproject.exe

Or Simply:
      form1.BeginInvoke(New LocalCallBackDelegates.CallBackDelegate(AddressOf LocalCallBackDelegates.HandleData), New Object() {textString})
===The above also compiles and runs fine but the output window shows the following:
A first chance exception of type 'System.InvalidOperationException' 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  

AddressOf LocalCallBackDelegates.HandleData

This is incorrect. You need to pass address of form method, something like this:

form1.BeginInvoke(New LocalCallBackDelegates.CallBackDelegate(AddressOf form1.HandleData), New Object() {textString})

HandleData should be Form1 method.
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.VBByRefStr)> 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.Sequential, CharSet:=CharSet.Ansi)> Public Structure LinkStateStruc
                  <MarshalAs(UnmanagedType.U1)> Public OldState As Byte
                  <MarshalAs(UnmanagedType.U1)> 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 butRegisterHostLinkStateCallback_Click(ByVal sender As System.Object, ByVal e As
         System.EventArgs) Handles butRegisterHostLinkStateCallback.Click
        Dim functionResult As Integer
        functionResult = RegisterLinkStateCallback(HostLinkStateChange, hostLinkStateCallBackDelegate)
    End Sub

    Public Sub updateStatusInfo(ByVal stbxExtraInfoText As String)
        If stbxStatusInfo.InvokeRequired Then
            Dim mad As New MyUpdateDelegate(AddressOf updateStatusInfo)
            Me.Invoke(mad, stbxExtraInfoText)
        Else
            stbxStatusInfo.AppendText(stbxExtraInfoText & vbCrLf)
            stbxStatusInfo.Invalidate()
        End If
     End Sub

Class/File2:  DeclarationsForRemoteDelegates---Pertinent Code Follows:

Partial Public Class vbfrm
    Public Shared hostLinkStateCallBackDelegate = New myLinkStateChange(AddressOf
           Event_HostLinkStateChange)

Class/File3:  vbfrm_Callbacks---Pertinent Code Follows:

Partial Public Class vbfrm
    Public Shared Sub HandleData(ByVal returnString As String)
        vbfrm.stbxStatusInfo.AppendText(returnString)
        vbfrm.stbxStatusInfo.AppendText(vbCrLf)
    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
This has turned out to be harder than I initially thought.
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?))