Non-Generic Delegates Treated as Generic Types: Can't pass callback function pointers to native code

IDE: Visual Studio
Version: RemObjects 9.2.101.218
Language: Silver
Target .NET
Description: It appears that all Swift custom block types on .NET, with the single exception of () -> (), are treated as generic types even if they do not contain any generic parameters. As a result, they cannot be passed as delegates to unmanaged code.

Expected Behavior: Given this simple delegate (callback) function of type () -> ()

func myDelegate() {
    print("Managed delegate executed")
}

we can get a native (unmanaged) pointer to it as follows:

typealias ManagedDelegate = () -> ()
var managedDelegate = ManagedDelegate(myDelegate)
var nativeDelegate: IntPtr = Marshal.GetFunctionPointerForDelegate(managedDelegate)

This native pointer can be passed to a native function in a native DLL, and the native function can use it to invoke myDelegate(). In this case, everything works as expected.

Actual Behavior:

However, if you add a return type or any method parameter to myDelegate(), for example:

func myDelegate(_ i: Int32) {
    print("Managed delegate executed")
}

typealias ManagedDelegate = (Int32) -> ()
var managedDelegate = ManagedDelegate(myDelegate)
var nativeDelegate: IntPtr = Marshal.GetFunctionPointerForDelegate(managedDelegate)

the result is a runtime exception:

Unhandled Exception: System.ArgumentException: The specified Type must not be a generic type definition.
Parameter name: delegate
   at System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegateInternal(Delegate d)
   at System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(Delegate d)
   at System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate[TDelegate](TDelegate d)

Apparently, the system is treating the type (Int32) -> () as a generic type, even though it isn’t generic. The result is that pointers to delegates of any type other than () -> () cannot be passed to native code.

This appears to be a bug, unless there is some other subtlety that I am missing.

Well it is a generic type. They map to Action<T*> and Func<T*>

Thank you Carlo. That would explain the error message.

I tried inspecting the type as follows:

print(managedDelegate.GetType().ToString())

Prints --> System.Action`1[System.Int32]

Here you can see that the generic parameter in Action<T*> has already been resolved to Int32 before the call to Marshal.GetFunctionPointerForDelegate(). But it must somehow still be tagged as generic, preventing the Marshaler from handling it.

With Microsoft’s C# the following code works as expected:

    static void myDelegate(Int32 i) {
        Console.WriteLine("Managed delegate executed");
    }

    delegate void ManagedDelegate(Int32 i);
    ManagedDelegate managedDelegate = new ManagedDelegate(myDelegate);
    IntPtr nativeDelegate = Marshal.GetFunctionPointerForDelegate(managedDelegate);

I inspected the type again in this case, and observed the following:

Console.WriteLine(managedDelegate.GetType().ToString());

Prints --> MyNamespace.MyClass+ManagedDelegate

It appears that function references created with the delegate keyword in C# are more strictly typed than function references in Silver, so that they can be marshaled successfully for passing to native code.

Is there any way to get a more strictly typed function reference in Silver so that it can be marshaled for native code? Several Win32 API functions rely on callbacks, so something like this will be necessary in order to handle them directly using Silver.

The problem is mostly a syntax one. I have a bug open to support “proper” delegates, If you have full elements, easiest is to define a type in C# or Oxygene mode and use it from Swift mode.

Understood. C# and Oxygene wrappers should certainly work. I have also used a C++/CLI wrapper to accomplish the same thing.

In the meantime, while you look for a solution to the syntax problem, I have found a way to create real .NET delegates using only Silver. In case it is useful to anyone, here’s the code for a Silver class called CustomDelegate that wraps a .NET delegate:

import System
import System.Reflection
import System.Linq.Expressions
import System.Runtime.InteropServices

public class CustomDelegate {
    let delegate: Delegate
    var nativeFunctionPointer: IntPtr {
        get { return Marshal.GetFunctionPointerForDelegate(delegate) }
    }

    init(methodInfo: MethodInfo, object: Object? = nil) {
        delegate = CreateDelegate(methodInfo: methodInfo, object: object)!
    }

    public static func CreateDelegate(methodInfo: MethodInfo, object: Object? = nil) -> System.Delegate? {
        if methodInfo.MemberType != MemberTypes.Method { return nil }
        if !methodInfo.IsStatic && object == nil { return nil } // Need a object instance for a nonstatic method

        // Convert the method signature into a list of types
        var parameters: ParameterInfo[] = methodInfo.GetParameters()
        var signature: Type[] = Type[](count: parameters.Length + 1)
        for i in 0..<parameters.count {
            signature[i] = parameters[i].ParameterType
        }
        signature[signature.count - 1] = methodInfo.ReturnType
 
	    guard let tDelegateHelpers = FindTypeInAssemblies("System.Linq.Expressions.Compiler.DelegateHelpers") else { return nil }
	    guard let miMNCD = tDelegateHelpers.GetMethod("MakeNewCustomDelegate", BindingFlags.NonPublic | BindingFlags.Static) else { return nil }
        typealias TypeofMakeNewCustomDelegate = Func<Type[], Type>
	    var typeMNCD: Type = TypeofMakeNewCustomDelegate.self
	    guard let MakeNewCustomDelegate = System.Delegate.CreateDelegate(typeMNCD, miMNCD) as? TypeofMakeNewCustomDelegate else { return nil }
        guard let typeDelegate = MakeNewCustomDelegate(signature) else { return nil }
        return methodInfo.CreateDelegate(typeDelegate, object)
    }

    private static func FindTypeInAssemblies(_ sTypeFullName: String) -> Type? {
	    for assembly in AppDomain.CurrentDomain.GetAssemblies() {
		    if let type = assembly.GetType(sTypeFullName, false, true) { return type }
	    }
	    return nil
    }
}

The constructor takes a MethodInfo object for the delegate method obtained using GetMethodInfo(), and an optional object instance if the delegate is a non-static instance method. You can use it as follows, for example to invoke a Win32 API function with a callback:

import System.Runtime.InteropServices
import System.Reflection

@DllImport("User32.dll") static __external func EnumDisplayMonitors(_ hdc: IntPtr, _ lprcClip: IntPtr, _ lpfnEnum: IntPtr, _ dwData: IntPtr) -> Bool

typealias MonitorEnumProc = (IntPtr, IntPtr, IntPtr, UInt32) -> Bool

func monitorEnumProc(_ hMonitor: System.IntPtr, _ hdcMonitor: System.IntPtr, _ lprcMonitor: System.IntPtr, _ dwData: System.UInt32) -> Bool {
    print("Found monitor with handle \(hMonitor).")
    return true
}

func TestWin32APICallback() {
    let method: (IntPtr, IntPtr, IntPtr, UInt32) -> Bool = monitorEnumProc
    let delegate = CustomDelegate(methodInfo: method.GetMethodInfo())
    let bResult = EnumDisplayMonitors(0, 0, delegate.nativeFunctionPointer, 0)
    print("Returned from native function with return value of \(bResult)")
}