3

I'm new to C++. I have been told using a "callback" with C++ is the best solution for this. Here is my situation.

I have a DLL written in C++
this DLL has a method to start the service which is run via the C# code (this works fine)
when the service in the DLL runs I want the DLL to pass back text to the C# code, this is just progress code such as "stage one starting " and "stage one completed"


I have looked around and been told that the best way to achieve this is to use callbacks, I don't really have a clue how to implement this. Does anyone have any suggestions or articles out there I can check out? Please include C++ as I have zero experience in C++.


Cheers

Funky
  • 12,890
  • 35
  • 106
  • 161
  • ehi @Funky did you succeed to implement a working solution to this problem? Did you use callbacks? Maybe some of the proposed solutions in this thread? I saw another promising thread with a similar problem: http://stackoverflow.com/questions/38521509/how-to-move-copy-data-from-mfc-to-c-cli-using-events-delegates-callback Thanks in advance, and sorry for the multiple questions :D – alcor Aug 01 '16 at 08:10

4 Answers4

4

There may be cleaner ways, but here are some of the steps I used to make it work.

Define the delegate and the function to pass it to your DLL. The parameters are what will be sent back to the C# delegate:

  public delegate uint CallbackFn( uint param1, uint param2 );

  [DllImport("yourdll.dll",  CallingConvention=CallingConvention.Winapi, EntryPoint="RegisterTheCallback" )]
  private static extern uint RegisterTheCallback( CallbackFn pfn );

Create a variable to store the delegate. Make sure this doesn't go out of scope. In my testing, I found that the GC would reclaim it (it wasn't "aware" that my DLL was still using it):

  CallbackFn mCmdCallback = null;

Then initialize it somewhere:

  mCmdCallback = new CallbackFn( YourCallback );

And then pass it to your DLL:

RegisterTheCallback( mCmdCallback );

And define the actual method that will receive the call:

  private uint YourCallback( uint param1, uint param2 )
  {
    // report progress etc.
  }

The code in the DLL might look like this:

DWORD _declspec( dllexport ) WINAPI RegisterTheCallback
(
   DWORD (WINAPI *lpfnCallback)( DWORD param1, DWORD param2 )
)
{
// Store lpfnCallback somewhere so that it can be called later
...
}

And then the code in your DLL can call it with the appropriate data when it needs:

ret = (lpfnCallback)( 234, 456 );
Mark Wilkins
  • 40,729
  • 5
  • 57
  • 110
  • hi, could you please include any C++? – Funky Feb 17 '11 at 15:56
  • @Funky: Added a bit more to try to clarify the unmanaged end of it. – Mark Wilkins Feb 17 '11 at 16:17
  • the c++ code causes a number of errors, is there anything else that needs to be added to get it working? – Funky Feb 17 '11 at 16:27
  • @Funky: I inadvertently left a type specific to my project in but just now changed it to DWORD (it was UNSIGNED32). What errors do you see? – Mark Wilkins Feb 17 '11 at 16:30
  • @Mark Wilkins : Yes! that worked,...well the C++ has built ok, I haven't done the C# part yet. Can I call this function in my C++ by just using MethodName(data); – Funky Feb 17 '11 at 16:43
  • @Funky: I'm not sure I understand the question completely. But you just call it using the function pointer variable. – Mark Wilkins Feb 17 '11 at 17:27
3

You can simply pass a C# string back to C++ and a C++ string to C#. The requirement is that the string is unicode and the allocation method is SysAllocString and not malloc. Any ASCII string you need to convert to unicode.

const wchar_t* theString = L"hello";
BSTR bstr = SysAllocString(theString);
DoSomething(bstr);
SysFreeString(bstr);

And this to register the C# dll

Assembly asm = Assembly.LoadFile (@"c:\temp\ImageConverter.dll");
RegistrationServices regAsm = new RegistrationServices();
bool bResult = regAsm.RegisterAssembly(asm, AssemblyRegistrationFlags.SetCodeBase);

And this to convert Unicode to ASCII and vice-versa.

inline BSTR Cstring2VBstring(char *szString)
{
    WCHAR* res = NULL;
    BSTR bs;
    DWORD n;
    char *sz = NULL;
    if (*szString && szString)
    {
        sz = strdup(szString);
        n = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, sz, -1, NULL, 0);

        if (n)
        {
            res = (WCHAR*) malloc(n * sizeof(char) );
            MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, sz, -1, res, n);
        }

    }

    bs = SysAllocString( (const OLECHAR*) res);
    free(sz);
    return bs;
}



// C String to BSTR conversion (2)
BSTR Cstringn2VBstring(char *szString, int dwSize)
{
    WCHAR* res = NULL;
    BSTR bs;
    DWORD n = (DWORD) dwSize;
    char *sz = NULL;
    if (*szString)
    {
        sz = (char*) malloc(dwSize);
        memcpy(sz, szString, dwSize);
        n = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, sz, n, NULL, 0);
        if(n)
        {
            res = (WCHAR*) malloc(n * sizeof(char) );
            MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, sz, -1, res, n);
        }
    }
    bs = SysAllocStringLen( (const OLECHAR*) res, n);

    free(sz);
    return bs;
}

And the .NET code:

Namespace TestLibrary2
    ' Interface declaration. '
    Public Interface ICalculator
        Function Add(ByVal Number1 As Integer, ByVal Number2 As Integer) As Integer
        Function Subtract(ByVal Number1 As Long, ByVal Number2 As Long) As Long
        Function ReturnValue() As String
        Function Concat(ByVal Number1 As String, ByVal Number2 As String) As String

        Sub Concat2(ByVal Number1 As String, ByVal Number2 As String)

        Function isTrue(ByVal bInputvalue As Boolean) As Boolean
        Function isTrue2(ByRef bInputvalue As Boolean) As Boolean
    End Interface



    ' Interface implementation. '
    Public Class ManagedClass
        Implements ICalculator


        Public Function Add(ByVal Number1 As Integer, ByVal Number2 As Integer) As Integer Implements ICalculator.Add
            Return Number1 + Number2
        End Function


        Public Function Subtract(ByVal Number1 As Long, ByVal Number2 As Long) As Long Implements ICalculator.Subtract
            Try
                System.IO.File.WriteAllText("c:\temp\subtract.txt", "Subtracted: ")
            Catch ex As Exception
                MsgBox(ex.Message)
            End Try

            Return Number1 - Number2
        End Function


        Public Function Concat(ByVal Number1 As String, ByVal Number2 As String) As String Implements ICalculator.Concat
            Try
                System.IO.File.WriteAllText("c:\temp\Concat.txt", "Nummer1: " + Number1 + vbCrLf + "Nummer2:" + Number2)
            Catch ex As Exception
                MsgBox(ex.Message)
            End Try

            Dim strReturnValue As String = Number1 + Number2
            Return strReturnValue
        End Function


        Public Sub Concat2(ByVal Number1 As String, ByVal Number2 As String) Implements ICalculator.Concat2
            Console.WriteLine("moo")
        End Sub


        Public Function ReturnValue() As String Implements ICalculator.ReturnValue
            Dim x As String = "moooooo"
            Return x
        End Function


        Public Function isTrue(ByVal bInputvalue As Boolean) As Boolean Implements ICalculator.isTrue
            If bInputvalue = True Then
                Return True
            End If
            Return False
        End Function


        Public Function isTrue2(ByRef bInputvalue As Boolean) As Boolean Implements ICalculator.isTrue2
            If bInputvalue = True Then
                Return True
            End If
            Return False
        End Function

    End Class


End Namespace

Edit:
See here for closer information:

http://support.microsoft.com/kb/828736
http://msdn.microsoft.com/en-us/library/ms734686.aspx

Stefan Steiger
  • 78,642
  • 66
  • 377
  • 442
  • This doesn't really help the OP at all. – Marlon Feb 18 '11 at 04:58
  • I think it does. All he wants to do is to call a C#/C++ function from C++/C#. That doesn't even need callbacks. There's a MS support example how to do it, he can find it with google, the above is just the info he needs for strings, because the example only deals with ints. – Stefan Steiger Feb 18 '11 at 07:35
  • I added the C++ code and it built, I added the C# and it built but how do I access the string from C#? – Funky Feb 18 '11 at 14:11
1

It's tricky but here's the code -- took me a while to figure out -- upvotes appreciated. Can you call a C# DLL from a C DLL?

This example is unmanaged c++ to C#.

Community
  • 1
  • 1
patrick
  • 16,091
  • 29
  • 100
  • 164
  • I tried this section in my C++ and got a number of errors: typedef void (__stdcall *Callback)(PCWSTR); static Callback gui; //Assignment of the delegate to the function pointer __declspec(dllexport) void __stdcall CSharp_GuiWriter(void * jarg1) { Callback arg1 = (Callback) 0 ; arg1 = (Callback)jarg1; gui = arg1; } //invocation (*gui)(_T("make C# print this text")); – Funky Feb 17 '11 at 16:02
  • sorry, here are my errors: error C2065: 'PCWSTR' : undeclared identifier error C2165: 'left-side modifier' : cannot modify pointers to data error C2513: 'void *' : no variable declared before '=' error C3861: '_T': identifier not found error C4430: missing type specifier - int assumed. Note: C++ does not support default-int error C2371: 'gui' : redefinition; different basic types – Funky Feb 17 '11 at 16:05
  • @Ben Voigt : Thank you that got rid of half the errors, the only errors I have left are: error C3861: '_T': identifier not found error C4430: missing type specifier - int assumed. Note: C++ does not support default-int error C2371: 'gui' : redefinition; different basic types – Funky Feb 17 '11 at 16:14
  • does this line: (*gui)(_T("make C# print this text")); require anything extra as I get the three errors: error C4430: missing type specifier - int assumed. Note: C++ does not support default-int error C2373: 'gui' : redefinition; different type modifiers error C2440: 'initializing' : cannot convert from 'const char [24]' to 'int *' – Funky Feb 17 '11 at 16:25
  • its defined up top : typedef void (__stdcall *Callback)(PCWSTR); static Callback gui; ... you need to put that invocation in a function of course. – patrick Feb 17 '11 at 16:27
  • I'm not quite sure what you're supposed to do with that code?? – Funky Feb 17 '11 at 16:54
  • 1) Does it compile ? 2) What are you asking? – patrick Feb 17 '11 at 16:57
  • Hi, no it does not compile. Well what I meant was that I do not understand where I should put that code or what I should do with it? – Funky Feb 18 '11 at 09:48
  • I modified the sample putting the (*gui) in a function. See if that compiles. – patrick Feb 18 '11 at 15:20
0

A callback is simply a particular use of a delegate. The normal model looks something like this:

public class MyCaller
{
   public OtherClass otherClassInstance;

   public void CallbackMethod() {...}

   public void UsesTheCallback()
   {
      //The callback method itself is being passed
      otherClassInstance.MethodWithCallback(CallbackMethod);
   }
}

public class OtherClass
{
   public delegate void CallbackDelegate()
   public void MethodWithCallback(CallbackDelegate callback)
   {
      //do some work, then...
      callback(); //invoke the delegate, "calling back" to MyCaller.CallbackMethod()
   }
}

The beauty of this model is that any method with the defined "signature" (parameters and return type) can be used as the callback. It's a form of "loose coupling", where code isn't dependent on knowing what an object IS, only what it DOES, so that object can be swapped out for another object without the code knowing the difference.

The above example is all in C#. When you're using extern functions from a C++ DLL, you'll probably be dealing with an IntPtr type that is a simple pointer to the method. You should be able to "marshal" this pointer as a delegate when defining the C# interface to that extern method, so the method will look like a normal C# method.

KeithS
  • 70,210
  • 21
  • 112
  • 164
  • hi, thanks for your reply, but as I mentioned I have no C++ experience (well....4 days!) do you have a website that shows this with the C++ code? – Funky Feb 17 '11 at 15:49
  • As mentioned in the title, It is for using C++. This does not show any C++. – Funky Feb 18 '11 at 14:12