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) {
var delegateType = typeof(T);
var method = delegateType.GetMethod("Invoke");
var returnType = method.ReturnType;
var paramTypes =
method
.GetParameters()
.Select((x) => x.ParameterType)
.ToArray();
var nameBuilder = new StringBuilder();
nameBuilder.Append(delegateType.Name);
foreach (var pType in paramTypes)
{
nameBuilder
.Append("`")
.Append(pType.Name);
}
var name = nameBuilder.ToString();
var proxyAssemblyExist =
AppDomain
.CurrentDomain
.GetAssemblies()
.FirstOrDefault((x) => x.GetName().Name == name);
Type proxyType;
if (proxyAssemblyExist == null)
{
var proxyAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(
new AssemblyName(name),
AssemblyBuilderAccess.Run
);
var proxyModule = proxyAssembly.DefineDynamicModule(name);
var proxyTypeBuilder = proxyModule.DefineType(name,
TypeAttributes.AutoClass |
TypeAttributes.AnsiClass |
TypeAttributes.Sealed |
TypeAttributes.Public,
typeof(MulticastDelegate)
);
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);
proxyType = proxyTypeBuilder.CreateType();
}
else
{
proxyType = proxyAssemblyExist.GetType(name);
}
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.
Hey guys, my name is Roy. I was born March 15th 1994 and I am currently a student at the University of Waterloo. 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.