monkey0506 / native-generic-delegates

Generic C# delegates for P/Invoke.
Other
8 stars 0 forks source link

`interceptors` branch does not support use inside a generic class or method #19

Closed monkey0506 closed 9 months ago

monkey0506 commented 9 months ago

Describe the issue

The interceptors branch generates code that uses C# 12 interceptors to produce the required instance for FromAction, FromFunc, and FromFunctionPointer methods. These interceptors will not work if the call-site in user code is nested inside a generic type or method.

private readonly struct FunctionPtr<T>(nint functionPtr) // obviously bad example to demonstrate the issue only
{
    private readonly INativeAction<T> action = INativeAction<T>.FromFunctionPointer // << THIS CALL
    (
        functionPtr,
        null,
        CallingConvention.Winapi
    );

    public void Invoke(T t) => action.Invoke(t);
}

Right now, this call to FromFunctionPointer would cause a NotImplementedException to be thrown at runtime.

Proposed solution

This scenario is already detected during codegen, but the fix is not yet implemented. We cannot intercept this using closed type symbols discovered from the GenericSymbolReferenceTree, because the signatures of the closed types don't match the signature of the open type at the call-site.

Instead, the only approach we can take here is to perform runtime type-checking and dispatch. We do already know the possible types used in this compilation, thanks to the tree. Marshalling behaviors must be parsable at compile-time. Calling conventions that are not parsable at compile-time currently default to CallingConvention.Winapi (the system default calling convention). Ergo, we do have all necessary details to dispatch these calls.

When building the ClassDescriptorCollection, we should check whether the call-site falls into this category. If it does, then we should create a separate collection so we know what classes to generated, and omit producing the interceptor. This separate collection will need to access the generated class name (which may be shared with other call-sites). This collection can use normal equality comparison for the MethodReference (rather than the equality comparer used in building the ClassDescriptorCollection).

Finally, after the codegen for the classes has been completed, we should enumerate this new collection and produce a single interceptor that performs the runtime type-checking.

Additional considerations

This should be considered as a primary use-case. This issue should be treated as high priority.