Future API changes #21
Replies: 4 comments 1 reply
-
Runtime fallbacks - Partially Implemented by #27The current Being able to parse these parameters as compile-time constant expressions is the optimal case, but previous work on the This would remove all of the diagnostics that the generator currently produces as well, which is also ideal. Update: The |
Beta Was this translation helpful? Give feedback.
-
Marshal map - Implemented by #25Although the marshalling behavior can be greatly simplified in the managed to unmanaged case with This map would fall under the same stringent static analysis rules currently applied to marshalling behaviors. Something such as (see [Proposal]: Dictionary expressions #7822): private static readonly MarshalMap map =
[
typeof(string): new MarshalAsAttribute(UnmanagedType.LPUTF8Str), // marshal all `string` arguments as UTF-8
typeof(bool): new MarshalAsAttribute(UnmanagedType.I4) // marshal all `bool` arguments as 32-bit
]; This map could then be applied such that every The ordinal marshalling descriptor would be given the highest precedence, followed by the |
Beta Was this translation helpful? Give feedback.
-
MarshallingDescriptor generic type argument - Implemented by #28The current This approach worked as a proof-of-concept, but it strongly restricted the usage, even when it was discovered that these parameters could be stored as a static readonly MarshalAsAttribute marshalReturnAs = new MarshalAsAttribute(UnmanagedType.LPUTF8Str);
// ...
var func = INativeFunc<string>.FromFunctionPointer(pNative, CallingConvention.Cdecl, marshalReturnAs); // OK, param is `static readonly` field
// ...
INativeFunc<string> GetMethod(nint ptr, CallingConvention callingConvention, MarshalAsAttribute? marshalling)
{
return INativeFunc<string>.FromFunctionPointer(ptr, callingConvention, marshalling); // ERROR - marshalling can't be parsed
} In the case of Instead of this approach, it is possible to pass an additional generic type argument that can carry this information. This can be implemented through an interface: internal interface MarshallingDescriptor<TDescriptor> where TDescriptor : MarshallingDescriptor<TDescriptor>
{
protected static abstract MarshalMap? MarshalMap => get;
protected static abstract MarshalAsAttribute?[]? MarshalParamsAs => get;
protected static abstract MarshalAsAttribute? MarshalReturnAs => get;
} Then an additional generic type parameter can be added to the interfaces to carry this information through to the call-site: internal sealed class MarshallingDescriptor : MarshallingDescriptor<MarshallingDescriptor> // this naming is fine
{
private static readonly MarshalAsAttribute marshalReturnAs = new MarshalAsAttribute(UnmanagedType.LPUTF8Str);
static MarshalMap? MarshallingDescriptor<MarshallingDescriptor>.MarshalMap => null;
static MarshalAsAttribute?[]? MarshallingDescriptor<MarshallingDescriptor>.MarshalParamsAs => null;
static MarshalAsAttribute? MarshallingDescriptor<MarshallingDescriptor>.MarshalReturnAs => marshalReturnAs;
}
// ...
INativeFunc<string, TMarshalling> GetMethod<TMarshallingDescriptor>(nint ptr, CallingConvention callingConvention)
where TMarshallingDescriptor : MarshallingDescriptor<TMarshallingDescriptor>
{
return INativeFunc<string, TMarshallingDescriptor>.FromFunctionPointer(ptr, callingConvention);
}
// ...
var func = GetMethod<MarshallingDescriptor>(pNative, CallingConvention.Cdecl);
var str = func.Invoke(); // get result from native This proposal is already lengthy, but the calling convention can also be included in the descriptor to avoid runtime handling if a compile-time constant expression can be supplied there. For example, |
Beta Was this translation helpful? Give feedback.
-
Facilitate
|
Beta Was this translation helpful? Give feedback.
-
FromDelegate
Currently, this project does not support any
ref
-qualified parameters. In large part, this was becauseSystem.Action
andSystem.Func
cannot support them. However, C# 10 added natural function types, which allows passing in a lambda or method name to a parameter of typeSystem.Delegate
(under certain rules).We are already restricting the parameters describing marshalling behaviors to values that can be statically analyzed for source generation. Restricting the first parameter of a
FromDelegate
method under these same rules would not seem strange from a development perspective. We have to be able to analyze the delegate that we are receiving in order for the source generator to produce the necessary code.The first practical upshot of
FromDelegate
would be that it would allowref
,in
, andout
parameters to be defined! However, this does come with some challenges. In order to support IntelliSense (and avoid false errors being reported), the definition ofInvoke
must be static. This can be overcome using aref struct
for each parameter. When usingFromDelegate
, theref
kind ofInvoke
's arguments can be inferred. However, when usingFromFunctionPointer
, we would not have a managed method to infer this information from, andref struct
s cannot be used as type parameters. A more robust solution would be required to support both scenarios (managed to unmanaged and unmanaged to managed).Another benefit of
FromDelegate
is that the marshalling behaviors can also be directly inferred from the lambda or method passed as the first argument. This means that we could completely remove themarshalAs
arguments currently in place forFromAction
andFromFunc
. Discovering theIMethodSymbol
for the delegate argument might look something like:From that point, we can inspect the
ref
kind, thescoped
kind, and the attributes of each parameter. TheMarshalAsAttribute
attributes are ready for use in the generated source code using only theToString()
method, no additional parsing is needed.When will it arrive?
I have conducted proof-of-concept tests on these ideas, but I am working on this project in my very limited free time. I cannot make any promises about when these feature changes might get worked on, but I can say that I'm excited about the potential of seeing them implemented. If anyone sees this, thank you for reading!
Beta Was this translation helpful? Give feedback.
All reactions