Skip to content

Conversation

@monkey0506
Copy link
Owner

As originally described in #21, this adds additional interfaces IUnmanagedAction and IUnmanagedFunc that derive from INativeAction or INativeFunc, respectively. These new interfaces explicitly require the unsafe keyword, so the compilation must use the /unsafe compiler switch and define a constant UNSAFE to use the new interfaces.

The new interfaces have twice as many generic type arguments as the base interfaces, with each type parameter Tn having a matching type parameter Un. Each U-prefixed type argument represents a native function pointer argument type, and has an unmanaged generic type constraint.

To represent a method that in managed code takes a single string parameter, you might then use this interface:

var action = IUnmanagedAction<string, nint>.FromFunctionPointer(...);

nint here represents the native method's argument of the matching ordinal position.

The generic type parameters include all of the managed type arguments first, then the unmanaged type arguments.

The benefit behind this new API is that you can directly work with native function pointers. Methods marked with the UnmanagedCallersOnlyAttribute can be implicitly converted to a native function pointer using the & (address-of) operator, but this works with other native function pointers as well.

[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
public static void UnmanagedPrint(nint message)
{
    var str = Marshal.PtrToStringUni(message);
    Console.WriteLine(str);
}

// ...

var action = IUnmanagedAction<string, nint>.FromFunctionPointer(&UnmanagedPrint);

The calling convention is read directly from the function pointer's signature, so here this will be CallingConvention.Cdecl. Overwriting the calling convention for native function pointers is not supported by the API.

Creating the managed object is only half of the battle. Native function pointers are only useful when you invoke them directly:

var str = Marshal.StringToCoTaskMemUni("Hello World!");
action.AsCdeclPtr(str);
Marshal.FreeCoTaskMem(str);

Because we are invoking the function pointer directly, the argument has the type nint. Here, we use the Marshal class to allocate and free memory, but other strategies exist. You lose the benefit of built-in marshalling when invoking the function pointer directly, but the invocation is direct - there is no overhead from the managed runtime, garbage collector, etc.

Properties are exposed for AsCdeclPtr, AsStdCallPtr, and AsThisCallPtr. These properties do not do any runtime checks, except when the factory method was called with CallingConvention.Winapi, which will check the platform default calling convention at runtime (using x86-platform rules for the default; non-x86 platforms don't have an explicit default).

You can fall back to the INativeAction/INativeFunc interface if you don't want to carry the full type parameter list for both the managed and unmanaged type arguments, but casting back will require the full type argument list. Objects created using the INativeAction/INativeFunc interface factory methods will not implement the IUnmanagedAction/IUnmanagedFunc interface.

@monkey0506 monkey0506 added the enhancement New feature or request label Oct 10, 2024
@monkey0506 monkey0506 added this to the v2.0.0 milestone Oct 10, 2024
@monkey0506 monkey0506 self-assigned this Oct 10, 2024
@monkey0506 monkey0506 force-pushed the unmanaged-interfaces branch 3 times, most recently from d66529b to 6ffc085 Compare October 14, 2024 02:29
The base interface implementations do not support these properties as there
is no target type for the pointers.
@monkey0506 monkey0506 force-pushed the unmanaged-interfaces branch from 6ffc085 to a8f3d4b Compare October 14, 2024 03:12
@monkey0506 monkey0506 merged commit 853742b into interceptors Oct 14, 2024
@monkey0506 monkey0506 deleted the unmanaged-interfaces branch October 14, 2024 03:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants