//
you're reading...
Delegates

Delegates as Callbacks Part 1

1. Introduction

1.1 A concept frequently used in real-world software development is that of a callback.

1.2 A callback, as we know, is a function which is asynchronously called, often when some event of significance occurs.

1.3 This series of blogs examines how this concept is realized between managed and unmanaged code. In this part 1, we shall examine how an unmanaged function may be invoked in managed code.

1.4 In part 2, we shall examine how a pointer to a managed function may be passed to unmanaged code in order to be called by unmanaged code at runtime.

2. Sample Code 1 – Unmanaged Callback.

2.1 Let’s say we want a C++ function like the following :

int __stdcall CPPCallBackFunction(int iParam)
{
  printf ("%d\r\n", iParam);
  return 0;
}

to be invoked by managed code.

2.2 The first thing we need to do is to pass a pointer to this function to managed code. For this, we need to create an API (called by managed code) that returns such a pointer.

2.3 One way we can do this is to define a C++ function as follows :

typedef int (__stdcall *PCPPCallBackFunction)(int iParam);
PCPPCallBackFunction __stdcall SetupCallBack()
{
  return CPPCallBackFunction;
}

where PCPPCallBackFunction is a function pointer declaration.

2.4 On the managed side, we need to declare the delegate to represent the callback function. The following is an example in C# :

public delegate int CPPCallBackFunction(int iParam);

A delegate is akin to a function pointer in managed code.

2.5 We also need to declare the C++ API which we will call to receive the callback function pointer :

[DllImport("TestDLL.dll", CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr SetupCallBack();

Note that the return value of SetupCallBack() is an IntPtr. This will be transformed into a delegate as we shall soon see.

2.6 We then use the Marshal.GetDelegateForFunctionPointer() method to transform the returned IntPtr into a delegate :

IntPtr pDelegateFunction = SetupCallBack();
CPPCallBackFunction pCPPCallBackFunction =
  (CPPCallBackFunction)(Marshal.GetDelegateForFunctionPointer(pDelegateFunction,typeof(CPPCallBackFunction)))
  as CPPCallBackFunction;

We may then invoke the callback function anytime using the delegate instance.

2.7 Listed below is a full C/C++ example code :

#include <stdio.h>
typedef int (__stdcall *PCPPCallBackFunction)(int iParam); 

int __stdcall CPPCallBackFunction(int iParam)
{
  printf ("%d\r\n", iParam);
  return 0;
} 

PCPPCallBackFunction __stdcall SetupCallBack()
{
  return CPPCallBackFunction;
}

2.8 Listed below is a full C# example client code :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices; 

namespace CSConsoleClient01
{
  class Program
  {
    // Declare a delegate type to represent the unmanaged callback function.
    public delegate int CPPCallBackFunction(int iParam);
    // Declare the unmanaged function that will return the callback function pointer.
    [DllImport("TestDLL.dll", CallingConvention = CallingConvention.StdCall)]
    public static extern IntPtr SetupCallBack(); 

    static void Main(string[] args)
    {
      // Obtain the unmanaged callback function pointer using the SetupCallBack() API.
      IntPtr pDelegateFunction = SetupCallBack();
      // Transform the returned unmanaged function pointer into a managed delegate.
      CPPCallBackFunction pCPPCallBackFunction =
        (CPPCallBackFunction) ( Marshal.GetDelegateForFunctionPointer(pDelegateFunction,typeof(CPPCallBackFunction)) )
        as CPPCallBackFunction;
      // Loop over and call the callback 5 times,
      // passing along a different integer value
      // each time.
      for (int i = 0; i < 5; i++)
      {
        // This is how we call the unmanaged function :
        // via a delegate.
        int iRet = pCPPCallBackFunction(i);
      } 

      Console.ReadKey();
    }
  }
}

3. Using MarshalAsAttributes to Specify Delegate Parameters.
3.1 Note that if it is necessary, it is also possible to use the MarshalAsAttribute to specify how delegate parameters are to be marshaled to the unmanaged function.

3.2 For example, if the callback function takes a constant NULL-terminated string as parameter :

typedef int (__stdcall *PCPPCallBackFunction2)(/*[in]*/ LPCTSTR lpszStringParam);

It would then be necessary to specify this information in the declaration of the corresponding delegate :

public delegate int CPPCallBackFunction2([In] [MarshalAs(UnmanagedType.LPStr)] string strStringParam);

The [MarshalAs(UnmanagedType.LPStr)] attribute indicates that the string parameter strStringParam is to be transformed into a NULL-terminated ANSI character array. The [In] attribute indicates that the NULL-terminated character array parameter is to be marshaled to the callback function one way. The managed side owns the memory. No changes to this array is expected from the unmanaged side and after the unmanaged function returns, the character array is to be destroyed on the managed side (by the interop marshaler).

3.3 Listed below is a sample C++ code to demonstrate this :

typedef int (__stdcall *PCPPCallBackFunction2)(/*[in]*/ LPCTSTR lpszStringParam); 

int __stdcall CPPCallBackFunction2(/*[in]*/ LPCTSTR lpszStringParam)
{
  // The lpszStringParam parameter is an "in" parameter.
  // It is owned by the caller and this function cannot
  // modify it.
  printf ("%s\r\n", lpszStringParam);
  return 0;
} 

PCPPCallBackFunction2 __stdcall SetupCallBack2()
{
  return CPPCallBackFunction2;
}

3.4 Listed below is a sample C# client code :

public delegate int CPPCallBackFunction2([In] [MarshalAs(UnmanagedType.LPStr)] string strStringParam); 

[DllImport("TestDLL.dll", CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr SetupCallBack2(); 

static void InvokeCallBackFunction2()
{
  // Obtain the unmanaged callback function pointer using the SetupCallBack2() API.
  IntPtr pDelegateFunction2 = SetupCallBack2();
  // Transform the returned unmanaged function pointer into a managed delegate.
  CPPCallBackFunction2 pCPPCallBackFunction2 =
    (CPPCallBackFunction2) ( Marshal.GetDelegateForFunctionPointer(pDelegateFunction2,typeof(CPPCallBackFunction2)) )
    as CPPCallBackFunction2;
  // Loop over and call the callback 5 times,
  // passing along a different string value
  // each time.
  for (int i = 0; i < 5; i++)
  {
    string strParam = string.Format("String {0:D}", i);
    // This is how we call the unmanaged function :
    // via a delegate.
    // Note also that strParam will be destroyed
    // on the managed side (by the interop marshaler).
    int iRet = pCPPCallBackFunction2(strParam);
  }
}

4. Receiving an Allocated String from the Callback.
4.1 It is also possible to receive a string from the unmanaged function as an “out” parameter. This time, the C++ unmanaged side must allocate the memory for the C-style string to be returned as an “out” parameter. The managed side will own this memory. When the callback returns, the Interop Marshaler will use the C-style string to construct a managed string and then destroy the C-style string.

4.2 Listed below is a sample C++ code that demonstrates this :

typedef int (__stdcall *PCPPCallBackFunction3)(/*[out]*/ LPTSTR* ppszStringParamReceiver); 

int __stdcall CPPCallBackFunction3(/*[out]*/ LPTSTR* ppszStringParamReceiver)
{
  // The ppszStringParamReceiver parameter is an "out" parameter.
  // A string will be allocated on the unmanaged side and will be
  // returned to the managed caller.
  // It will be owned by the caller and the onus is on the caller
  // to free this memory.
  char szSampleString[] = "Hello World";
  size_t stStringSize = sizeof(szSampleString);

  *ppszStringParamReceiver = (LPTSTR)::CoTaskMemAlloc((ULONG)stStringSize);

  strcpy(*ppszStringParamReceiver, szSampleString);

  return 0;
} 

PCPPCallBackFunction3 __stdcall SetupCallBack3()
{
  return CPPCallBackFunction3;
}

4.3 Listed below is the C# client code :

public delegate int CPPCallBackFunction3([Out] [MarshalAs(UnmanagedType.LPStr)] out string strStringReceiver); 

[DllImport("TestDLL.dll", CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr SetupCallBack3(); 

static void InvokeCallBackFunction3()
{
  // Obtain the unmanaged callback function pointer using the SetupCallBack3() API.
  IntPtr pDelegateFunction3 = SetupCallBack3();
  // Transform the returned unmanaged function pointer into a managed delegate.
  CPPCallBackFunction3 pCPPCallBackFunction3
    = (CPPCallBackFunction3) ( Marshal.GetDelegateForFunctionPointer(pDelegateFunction3,typeof(CPPCallBackFunction3)) )
    as CPPCallBackFunction3;
  // Loop over and call the callback 5 times.
  for (int i = 0; i < 5; i++)
  {
    string strParam = null;
    // This is how we call the unmanaged function :
    // via a delegate.
    int iRet = pCPPCallBackFunction3(out strParam);
    // The returned NULL-terminated ANSI character array
    // will be freed by the Interop Marshaler.
    // strParam will be Garbage Collected.
    Console.WriteLine("{0:S}", strParam);
  }
}

4.4 Note that the protocol for returning an unmanaged string to managed code is that the string must be allocated using the ::CoTaskMemAlloc() API (if the string is to be marshaled as “UnmanagedType.LPStr”) or ::SysAllocString() (if the string is to be marshaled as “UnmanagedType.BStr”). On the managed side, after receiving the unmanaged string and using it to construct a equivalent managed string, the interop marshaler will use the appropriate function to free the unmanaged string (i.e. Marshal.FreeCoTaskMem() or Marshal.FreeBSTR()).

4.5 The newly created managed string itself will be subject to garbage collection as per normal.

Advertisements

About Lim Bio Liong

I've been in software development for nearly 20 years specializing in C , COM and C#. It's truly an exicting time we live in, with so much resources at our disposal to gain and share knowledge. I hope my blog will serve a small part in this global knowledge sharing network. For many years now I've been deeply involved with C development work. However since circa 2010, my current work has required me to use more and more on C# with a particular focus on COM interop. I've also written several articles for CodeProject. However, in recent years I've concentrated my time more on helping others in the MSDN forums. Please feel free to leave a comment whenever you have any constructive criticism over any of my blog posts.

Discussion

9 thoughts on “Delegates as Callbacks Part 1

  1. Hi Mr. Liong
    Thank you very much for this post. This is what I’ve been looking for. I just miss one small thing:
    In your example you use:
    [DllImport(“TestDLL.dll”, CallingConvention = CallingConvention.StdCall)]
    public static extern IntPtr SetupCallBack();
    in order to get hand on the pointer to the callback function.
    I have do to it in another way. I’ve started the .NET app using the guidelines in:
    http://www.codeproject.com/Articles/12673/Calling-Managed-NET-C-COM-Objects-from-Unmanaged-C
    On the .NET side I’ve created a function (RegisterCppCallback) to register the callback function pointer. This function (RegisterCppCallback) is called from the CPP side with a pointer to the CPP callback function. How can I do this ?
    Thank you very much in advance.
    Regards,
    Evan H. Hansen

    Posted by Evan Haahr Hansen | September 23, 2013, 12:44 pm
    • Hello Evan,

      1. In order to have a managed method (e.g. RegisterCppCallback()) callable from an unmanaged application (e.g. a C++ program), your managed class (which contains the RegisterCppCallback() method) must be a public COM-visible class.

      2. The example class (MyDotNetClass) in the CodeProject article is certainly a good sample to start with. Simply replace the ShowCOMDialog() function with RegisterCppCallback(), e.g. :

      public void RegisterCppCallback(IntPtr pCPPCallback);

      3. This would generate a COM method like the following :

      HRESULT RegisterCppCallback([in] long pCPPCallback);

      4. Just follow the sample C++ application from the CodeProject article and call this method from it.

      5. The following is a short example :

      using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Text;
      using System.Runtime.InteropServices;

      namespace DelegateCallingClassLib
      {
      [ComVisible(true)]
      [ClassInterface(ClassInterfaceType.AutoDual)]
      [Guid(“CF67EBB0-17B2-4a4f-AD16-47032F5C32D5”)]
      [ProgId(“DelegateCallingClassLib.DelegateCallingClass”)]
      public class DelegateCallingClass
      {
      public void RegisterCppCallback(IntPtr pCPPCallback)
      {
      pDelegateFunction = pCPPCallback;
      }

      private IntPtr pDelegateFunction = IntPtr.Zero;
      }
      }

      – Bio.

      Posted by Lim Bio Liong | September 23, 2013, 3:38 pm
      • Hi Lim

        Thank you very much for your quick reply.
        You are right that the CodeProject article gives a good example and it works just fine for me – no problem.
        My problem is to establish the callback function. In your reply you use an IntPtr as argument to the RegisterCppCallback function. How can I call this function from the C++ application – how can I make the callback funktion in the C++ application known to the .NET application ?

        Regards,
        Evan

        Posted by Evan Haahr Hansen | September 23, 2013, 6:28 pm
  2. Hello Evan,

    Call the RegisterCppCallback() function in your C++ program with the name of your callback function cast into a long, e.g. :

    #include “stdafx.h”
    #import “DelegateCallingClassLib.tlb” raw_interfaces_only
    using namespace DelegateCallingClassLib;

    typedef int (__stdcall *PCPPCallBackFunction)(int iParam);

    int __stdcall CPPCallBackFunction(int iParam)
    {
    printf (“%d\r\n”, iParam);
    return 0;
    }

    int _tmain(int argc, _TCHAR* argv[])
    {
    ::CoInitialize(NULL);

    if (1)
    {
    _DelegateCallingClassPtr sp_DelegateCallingClass = NULL;

    sp_DelegateCallingClass.CreateInstance(__uuidof(DelegateCallingClass));

    sp_DelegateCallingClass -> RegisterCppCallback((long)CPPCallBackFunction);



    }

    ::CoUninitialize();

    return 0;
    }

    Posted by Lim Bio Liong | September 24, 2013, 11:10 am

Trackbacks/Pingbacks

  1. Pingback: Delegates As Callbacks Part 2 « limbioliong - July 10, 2011

  2. Pingback: Delegates As Callbacks Part 2 | 福利网 - August 29, 2013

  3. Pingback: Delegates as Callbacks Part 1 | 福利网 - August 29, 2013

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: