Click here to Skip to main content
Click here to Skip to main content

Tagged as

A look at marshalling delegates in .NET

, 19 Nov 2012 CPOL
Rate this:
Please Sign up or sign in to vote.
Discusses marshalling delegates in .NET and critques what .NET libraries have to offer.

Introduction

This tip/article originally started out in my mind as kind of a rant. I don't feel that .NET provides modern methods for marshalling delegates in the .NET world. This article will discuss why I feel this way and how I feel the implementation should have been done.

Marshal.GetDelegateForFunctionPointer()

I find this method to be very obsolete, I mean we're talking like .NET 1.0 days. The method takes a Type and the associated IntPtr which is of course the function pointer. Since the .NET 2.0 era where generics were first introduced, a method defined similar to the following should have been provided:

public static T GetDelegateForFunctionPointer<T>(IntPtr address, CallingConvention conv)

It's incredibly tedious that a method like this isn't provided as we end up writing code similar to the following which is seemingly less efficient than Microsoft could make it:

var invoker = Marshal.GetDelegateForFunctionPointer(addr, typeof(InvokerDelegate)) as InvokerDelegate;

If that wasn't bad enough, you find out the Type cannot be generic! This becomes extremely inconvenient when you have to start defining delegates for every type of function pointer your application needs to play with. Here's how I ideally imagined things:

var ptr = GetProcAddress(LoadLibraryA("user32.dll"), "MessageBoxA");
var MessageBoxA = 
   GetDelegateForFunctionPointer<Func<IntPtr, string, string, 
   int, int>>(ptr, CallingConvention.StdCall);
MessageBoxA(IntPtr.Zero, "Hello world", "Test", 0);

Marshal.GetFunctionPointerForDelegate()

Unlike its counterpart, there isn't much to complain about this function. The one down side similar to its counter part is that the type of the delegate being passed in cannot be generic which once again can be extremely difficult living in the modern .NET world.

Another annoying thing about this method is that there's a chance that the garbage-collector may collect the delegate-instance you obtained the function pointer for. If this occurs, your function pointer is no longer valid so you need to keep the delegate instance alive for as long as you need a valid function pointer.

Replacements

So, I decided to see what would come about if I decided to bring to life my ideas. The following implementation follows the guidelines above and tries to overcome the limitations of the functions provided by Marshal:

public static T GetDelegateForFunctionPointer<T>(IntPtr ptr, CallingConvention conv)
        where T : class
{
    var delegateType = typeof(T);
    var method = delegateType.GetMethod("Invoke");
    var returnType = method.ReturnType;
    var paramTypes =
        method
        .GetParameters()
        .Select((x) => x.ParameterType)
        .ToArray();
    var invoke = new DynamicMethod("Invoke", returnType, paramTypes, typeof(Delegate));
    var il = invoke.GetILGenerator();
    for (int i = 0; i < paramTypes.Length; i++)
        il.Emit(OpCodes.Ldarg, i);
    if (IntPtr.Size == sizeof(int))
        il.Emit(OpCodes.Ldc_I4, ptr.ToInt32());
    else
        il.Emit(OpCodes.Ldc_I8, ptr.ToInt64());
    il.EmitCalli(OpCodes.Calli, conv, returnType, paramTypes);
    il.Emit(OpCodes.Ret);
    return invoke.CreateDelegate(delegateType) as T;
}

I finally got around to implementing my own version of GetFunctionPointerForDelegate() which supported generic delegates. There are some things I dislike about it though, such as:

  • A new assembly has to be created for each generic type marshaled
  • It's a wrapper around Marshal.GetFunctionPointerForDelegate() which results in...
  • You must keep alive the delegate object (binder) to ensure the pointer does not become invalid.
public static IntPtr GetFunctionPointerForDelegate<T>(T delegateCallback, out object binder)
    where T : class
{
    var del = delegateCallback as Delegate;
    IntPtr result;

    try
    {
        result =  Marshal.GetFunctionPointerForDelegate(del);
        binder = del;
    }
    catch (ArgumentException) // generic type delegate
    {
        var delegateType = typeof(T);
        var method = delegateType.GetMethod("Invoke");
        var returnType = method.ReturnType;
        var paramTypes =
            method
            .GetParameters()
            .Select((x) => x.ParameterType)
            .ToArray();

        // builder a friendly name for our assembly, module, and proxy type
        var nameBuilder = new StringBuilder();
        nameBuilder.Append(delegateType.Name);
        foreach (var pType in paramTypes)
        {
            nameBuilder
                .Append("`")
                .Append(pType.Name);
        }
        var name = nameBuilder.ToString();

        // check if we've previously proxied this type before
        var proxyAssemblyExist =
            AppDomain
            .CurrentDomain
            .GetAssemblies()
            .FirstOrDefault((x) => x.GetName().Name == name);

        Type proxyType;
        if (proxyAssemblyExist == null)
        {
            /// create a proxy assembly
            var proxyAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(
                new AssemblyName(name),
                AssemblyBuilderAccess.Run
            );
            var proxyModule = proxyAssembly.DefineDynamicModule(name);
            // begin creating the proxy type
            var proxyTypeBuilder = proxyModule.DefineType(name,
                TypeAttributes.AutoClass | 
                TypeAttributes.AnsiClass | 
                TypeAttributes.Sealed    | 
                TypeAttributes.Public,
                typeof(MulticastDelegate)
            );
            // implement the basic methods of a delegate as the compiler does
            var methodAttributes = 
                MethodAttributes.Public | 
                MethodAttributes.HideBySig | 
                MethodAttributes.NewSlot | 
                MethodAttributes.Virtual;
            proxyTypeBuilder
                .DefineConstructor(
                    MethodAttributes.FamANDAssem | 
                    MethodAttributes.Family | 
                    MethodAttributes.HideBySig | 
                    MethodAttributes.RTSpecialName, 
                    CallingConventions.Standard, 
                    new Type[] { typeof(object), typeof(IntPtr) })
                .SetImplementationFlags(
                    MethodImplAttributes.Runtime | 
                    MethodImplAttributes.Managed
                );
            proxyTypeBuilder
                .DefineMethod(
                    "BeginInvoke", 
                    methodAttributes, 
                    typeof(IAsyncResult), 
                    paramTypes)
                .SetImplementationFlags(
                    MethodImplAttributes.Runtime | 
                    MethodImplAttributes.Managed);
            proxyTypeBuilder
                .DefineMethod(
                    "EndInvoke", 
                    methodAttributes, 
                    null, 
                    new Type[] 
                    { typeof(IAsyncResult) })
                .SetImplementationFlags(
                    MethodImplAttributes.Runtime |
                    MethodImplAttributes.Managed);
            proxyTypeBuilder
                .DefineMethod(
                    "Invoke", 
                    methodAttributes, 
                    returnType, 
                    paramTypes)
                .SetImplementationFlags(
                    MethodImplAttributes.Runtime | 
                    MethodImplAttributes.Managed);
            // create & wrap an instance of the proxy type
            proxyType = proxyTypeBuilder.CreateType();
        }
        else
        {
            // pull the type from an existing proxy assembly
            proxyType = proxyAssemblyExist.GetType(name);
        }
        // marshal and bind the proxy so the pointer doesn't become invalid
        var repProxy = Delegate.CreateDelegate(proxyType, del.Target, del.Method);
        result = Marshal.GetFunctionPointerForDelegate(repProxy);
        binder = Tuple.Create(del, repProxy);
    }
    return result;
}

Special thanks to Martin Bæhrenz Bjerregaard for his contributions to this article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

roylawliet
Student
Canada Canada
I was born March 15th 1994 and I have been programming for about 5 years now and working with C# for about 3 years. I am experienced in a variety of programming languages such as C/C++, Delphi, VB.NET, F#, and Python. I absolutely love learning new things about computer science, so I'm always doing research and often reinventing the wheel to get a better grasp on concepts.

Comments and Discussions

 
QuestionReally? Generic delegates seem to be working for me Pinmemberkornman0017-Aug-12 21:03 
AnswerRe: Really? Generic delegates seem to be working for me Pinmemberroylawliet18-Aug-12 4:27 
GeneralRe: Really? Generic delegates seem to be working for me Pinmemberkornman0018-Aug-12 6:41 
GeneralRe: Really? Generic delegates seem to be working for me Pinmemberroylawliet18-Aug-12 8:11 
QuestionRe: Really? Generic delegates seem to be working for me Pinmemberkornman0018-Aug-12 8:21 
AnswerRe: Really? Generic delegates seem to be working for me Pinmemberroylawliet18-Aug-12 9:43 
GeneralRe: Really? Generic delegates seem to be working for me Pinmemberroylawliet23-Aug-12 7:28 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.141029.1 | Last Updated 19 Nov 2012
Article Copyright 2012 by roylawliet
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid