Skip to content

Extensible Calling Conventions for native callee functions #51156

@AaronRobinsonMSFT

Description

@AaronRobinsonMSFT

Background and Motivation

The UnmanagedCallersOnlyAttribute provides an extensible mechanism to define native calling conventions on a .NET method. This enables users to author functions that are callable in a native environment. A symmetrical need arises for functions that are defined in native code but declared to be called in a .NET environment. These native function declarations are done using DllImportAttribute.

This approach complements UnmanagedCallersOnlyAttribute and aligns with the reuse of the CallConv* types under the System.Runtime.CompilerServices namespace.

Proposed API

If the user specifies the CallingConvention.Winapi (the default) on the DllImportAttribute the following attribute will be queried.

namespace System.Runtime.InteropServices
{
+    /// <summary>
+    /// Provides an equivalent to <see cref="UnmanagedCallersOnlyAttribute"/> for native
+    /// functions declared in .NET.
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Method)]
+    public class UnmanagedCalleeAttribute : Attribute
+    {
+        public UnmanagedCalleeAttribute() { }
+
+        /// <summary>
+        /// Types indicating calling conventions for the unmanaged callee.
+        /// </summary>
+        /// <remarks>
+        /// If <c>null</c>, the semantics are identical to <c>CallingConvention.Winapi</c>.
+        /// </remarks>
+        public Type[]? CallConvs;
+    }
}

Usage Examples

P/Invoke

Using the defaults, all 3 of the following declarations are the same.

[DllImport("nativelib")]
static extern int Func(IntPtr a, nint val);

[DllImport("nativelib", CallingConvention = CallingConvention.Winapi)]
static extern int Func(IntPtr a, nint val);

[UnmanagedCallee]
[DllImport("nativelib")]
static extern int Func(IntPtr a, nint val);

Some calling conventions can be declared in multiple ways.

[DllImport("nativelib", CallingConvention = CallingConvention.Cdecl)]
static extern int Func(IntPtr a, nint val);

[UnmanagedCallee(CallConvs = new[] { typeof(CallConvCdecl) })]
[DllImport("nativelib")]
static extern int Func(IntPtr a, nint val);

[UnmanagedCallee(CallConvs = new[] { typeof(CallConvCdecl) })]
[DllImport("nativelib", CallingConvention = CallingConvention.Winapi)]
static extern int Func(IntPtr a, nint val);

Future calling conventions would be enabled without updating the CallingConvention enum. Examples in C# and VB.NET.

[UnmanagedCallee(CallConvs = new[] { typeof(CallConvVectorCall) })]
[DllImport("nativelib")]
static extern int Func(IntPtr a, nint val);
<UnmanagedCallee(CallConvs := New Type() { GetType(CallConvVectorCall) })>
<DllImport("nativelib")>
Public Shared Function Func(a As IntPtr, val As IntPtr) As Integer
End Function

This proposal provides an alternative mechanism to support Native Varargs in #48796.

COM style interface - Not proposed but example of how the attribute could evolve.

To enable this scenario the attribute would be updated with the following: [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]

Using the defaults, all 3 of the following declarations are the same on Windows.

[ComVisible(true)]
[Guid("C509A1C1-A6C9-4229-89CE-10B53CF8271B")]
interface IFoo
{
    int Call(ref SomeType valuetype);
}

[ComVisible(true)]
[Guid("C509A1C1-A6C9-4229-89CE-10B53CF8271B")]
[UnmanagedCallee]
interface IFoo
{
    int Call(ref SomeType valuetype);
}

[ComVisible(true)]
[Guid("C509A1C1-A6C9-4229-89CE-10B53CF8271B")]
[UnmanagedCallee(CallConvs = new[] {  typeof(CallConvStdCall), typeof(CallConvMemberInstance) })]
interface IFoo
{
    int Call(ref SomeType valuetype);
}

Alternative Designs

Provide an enum to trigger off for reading the new attribute.

namespace System.Runtime.InteropServices
{
    public enum CallingConvention
    {
+        /// <summary>
+        /// Instructs the runtime to consult the <see cref="UnmanagedCalleeAttribute"/>.
+        /// </summary>
+        UnmanagedCallee = 7
    }
}

Alternative name proposed could be CallingConvention.UnmanagedCallConv.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions