Implement unmanaged interface APIs #30
Merged
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
As originally described in #21, this adds additional interfaces
IUnmanagedActionandIUnmanagedFuncthat derive fromINativeActionorINativeFunc, respectively. These new interfaces explicitly require theunsafekeyword, so the compilation must use the/unsafecompiler switch and define a constantUNSAFEto use the new interfaces.The new interfaces have twice as many generic type arguments as the base interfaces, with each type parameter
Tnhaving a matching type parameterUn. EachU-prefixed type argument represents a native function pointer argument type, and has anunmanagedgeneric type constraint.To represent a method that in managed code takes a single
stringparameter, you might then use this interface:ninthere 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
UnmanagedCallersOnlyAttributecan be implicitly converted to a native function pointer using the&(address-of) operator, but this works with other native function pointers as well.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:
Because we are invoking the function pointer directly, the argument has the type
nint. Here, we use theMarshalclass 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, andAsThisCallPtr. These properties do not do any runtime checks, except when the factory method was called withCallingConvention.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/INativeFuncinterface 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 theINativeAction/INativeFuncinterface factory methods will not implement theIUnmanagedAction/IUnmanagedFuncinterface.