Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 23 additions & 17 deletions docs/design/coreclr/botr/corelib.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ The pointers to common unmanaged EE structures should be wrapped into handle typ

Passing object references in and out of QCalls is done by wrapping a pointer to a local variable in a handle. It is intentionally cumbersome and should be avoided if reasonably possible. See the `StringHandleOnStack` in the example below. Returning objects, especially strings, from QCalls is the only common pattern where passing the raw objects is widely acceptable. (For reasoning on why this set of restrictions helps make QCalls less prone to GC holes, read the ["GC Holes, FCall, and QCall"](#gcholes) section below.)

QCalls should be implemented with a C-style method signature. This makes it easier for AOT tooling in the future to connect a QCall on the managed side to the implementation on the native side.

### QCall example - managed

Do not replicate the comments into your actual QCall implementation. This is for illustrative purposes.
Expand All @@ -82,7 +84,7 @@ Do not replicate the comments into your actual QCall implementation. This is for
class Foo
{
// All QCalls should have the following DllImport attribute
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it sounds like CharSet and EntryPoint are mandatory. would it be more accurate to say:

- // All QCalls should have the following DllImport attribute
+ // All QCalls should have the following DllImport attribute (with or without `EntryPoint`)
or simply:
+ // An example of QCall DllImport

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CharSet and EntryPoint aren't mandatory, but the majority of cases will require at least one of them. We can update the docs/examples if necessary, but I think the guidance is good as a starting place.

[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
[DllImport(RuntimeHelpers.QCall, EntryPoint = "Foo_BarInternal", CharSet = CharSet.Unicode)]

// QCalls should always be static extern.
private static extern bool BarInternal(int flags, string inString, StringHandleOnStack retString);
Expand Down Expand Up @@ -110,20 +112,13 @@ class Foo

Do not replicate the comments into your actual QCall implementation.

The QCall entrypoint has to be registered in tables in [vm\ecalllist.h][ecalllist] using `QCFuncEntry` macro. See ["Registering your QCall or FCall Method"](#register) below.
The QCall entrypoint has to be registered in tables in [vm\qcallentrypoints.cpp][qcall-entrypoints] using the `DllImportEntry` macro. See ["Registering your QCall or FCall Method"](#register) below.

[ecalllist]: https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/ecalllist.h
[qcall-entrypoints]: https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/qcallentrypoints.cpp

```C++
class FooNative
{
public:
// All QCalls should be static and tagged with QCALLTYPE
static
BOOL QCALLTYPE BarInternal(int flags, LPCWSTR wszString, QCall::StringHandleOnStack retString);
};

BOOL QCALLTYPE FooNative::BarInternal(int flags, LPCWSTR wszString, QCall::StringHandleOnStack retString)
// All QCalls should be free functions and tagged with QCALLTYPE and extern "C"
extern "C" BOOL QCALLTYPE Foo_BarInternal(int flags, LPCWSTR wszString, QCall::StringHandleOnStack retString)
{
// All QCalls should have QCALL_CONTRACT.
// It is alias for THROWS; GC_TRIGGERS; MODE_PREEMPTIVE.
Expand Down Expand Up @@ -221,6 +216,8 @@ public partial sealed class String

The FCall entrypoint has to be registered in tables in [vm\ecalllist.h][ecalllist] using `FCFuncEntry` macro. See ["Registering your QCall or FCall Method"](#register).

[ecalllist]: https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/ecalllist.h

This method is an instance method in managed code, with the "this" parameter passed as the first argument. We use `StringObject*` as the argument type, then copy it into a `STRINGREF` so we get some error checking when we use it.

```C++
Expand Down Expand Up @@ -250,7 +247,7 @@ FCIMPLEND

## <a name="register"></a> Registering your QCall or FCall method

The CLR must know the name of your QCall and FCall methods, both in terms of the managed class and method names, as well as which native methods to call. That is done in [ecalllist.h][ecalllist], with two arrays. The first array maps namespace and class names to an array of function elements. That array of function elements then maps individual method names and signatures to function pointers.
The CLR must know the name of your QCall and FCall methods, both in terms of the managed class and method names, as well as which native methods to call. For FCalls, registration is done in [ecalllist.h][ecalllist], with two arrays. The first array maps namespace and class names to an array of function elements. That array of function elements then maps individual method names and signatures to function pointers.

Say we defined an FCall method for `String.IsInterned()`, in the example above. First, we need to ensure that we have an array of function elements for the String class.

Expand All @@ -271,7 +268,16 @@ FCFuncStart(gStringFuncs)
FCFuncEnd()
```

There is a parallel `QCFuncElement` macro.
QCalls are registered in [qcallentrypoints.cpp][qcall-entrypoints] in the `s_QCall` array with the `DllImportEntry` macro as follows:

```C++
static const Entry s_QCall[] =
{
...
DllImportEntry(MyQCall),
...
};
```

## Naming convention

Expand All @@ -285,9 +291,9 @@ Certain managed types must have a representation available in both managed and n

The CLR provides a binder for this purpose. After you define your managed and native classes, you should provide some clues to the binder to help ensure that the field offsets remain the same to quickly spot when someone accidentally adds a field to only one definition of a type.

In [mscorlib.h][mscorlib.h], use macros ending in "_U" to describe a type, the name of fields in managed code, and the name of fields in a corresponding native data structure. Additionally, you can specify a list of methods, and reference them by name when you attempt to call them later.
In [corelib.h][corelib.h], use macros ending in "_U" to describe a type, the name of fields in managed code, and the name of fields in a corresponding native data structure. Additionally, you can specify a list of methods, and reference them by name when you attempt to call them later.

[mscorlib.h]: https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/mscorlib.h
[corelib.h]: https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/corelib.h

``` C++
DEFINE_CLASS_U(SAFE_HANDLE, Interop, SafeHandle, SafeHandle)
Expand Down Expand Up @@ -345,7 +351,7 @@ When the CLR starts up, CoreLib is loaded by a method called `SystemDomain::Load

For FCalls, look in [fcall.h][fcall] for infrastructure, and [ecalllist.h][ecalllist] to properly inform the runtime about your FCall method.

For QCalls, look in [qcall.h][qcall] for associated infrastructure, and [ecalllist.h][ecalllist] to properly inform the runtime about your QCall method.
For QCalls, look in [qcall.h][qcall] for associated infrastructure, and [qcallentrypoints.cpp][qcall-entrypoints] to properly inform the runtime about your QCall method.

More general infrastructure and some native type definitions can be found in [object.h][object.h]. The binder uses `mscorlib.h` to associate managed and native classes.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ internal static unsafe void _ZeroMemory(ref byte b, nuint byteLength)
}
}

[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
[DllImport(RuntimeHelpers.QCall, EntryPoint = "Buffer_Clear")]
private static extern unsafe void __ZeroMemory(void* b, nuint byteLength);

// The maximum block size to for __BulkMoveWithWriteBarrier FCall. This is required to avoid GC starvation.
Expand Down Expand Up @@ -77,7 +77,7 @@ private static void _BulkMoveWithWriteBarrier(ref byte destination, ref byte sou
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void __BulkMoveWithWriteBarrier(ref byte destination, ref byte source, nuint byteCount);

[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
[DllImport(RuntimeHelpers.QCall, EntryPoint = "Buffer_MemMove")]
private static extern unsafe void __Memmove(byte* dest, byte* src, nuint len);

// Used by ilmarshalers.cpp
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/System.Private.CoreLib/src/System/CLRConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ internal static bool GetBoolValueWithFallbacks(string switchName, string environ
return defaultValue;
}

[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
[DllImport(RuntimeHelpers.QCall, EntryPoint = "ClrConfig_GetConfigBoolValue", CharSet = CharSet.Unicode)]
private static extern bool GetConfigBoolValue(string configSwitchName, out bool exist);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public static void NotifyOfCrossThreadDependency()
}
}

[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
[DllImport(RuntimeHelpers.QCall, EntryPoint="DebugDebugger_Launch")]
private static extern bool LaunchInternal();

// Returns whether or not a debugger is attached to the process.
Expand All @@ -77,7 +77,7 @@ public static extern bool IsAttached
// report the message depending on its settings.
public static void Log(int level, string? category, string? message) => LogInternal(level, category, message);

[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
[DllImport(RuntimeHelpers.QCall, EntryPoint="DebugDebugger_Log", CharSet = CharSet.Unicode)]
private static extern void LogInternal(int level, string? category, string? message);

// Checks to see if an attached debugger has logging enabled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,48 +14,48 @@ internal static partial class EventPipeInternal
//
// These PInvokes are used by the configuration APIs to interact with EventPipe.
//
[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
[DllImport(RuntimeHelpers.QCall, EntryPoint = "EventPipeInternal_Enable")]
private static unsafe extern ulong Enable(
char* outputFile,
EventPipeSerializationFormat format,
uint circularBufferSizeInMB,
EventPipeProviderConfigurationNative* providers,
uint numProviders);

[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
[DllImport(RuntimeHelpers.QCall, EntryPoint = "EventPipeInternal_Disable")]
internal static extern void Disable(ulong sessionID);

//
// These PInvokes are used by EventSource to interact with the EventPipe.
//
[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
[DllImport(RuntimeHelpers.QCall, EntryPoint = "EventPipeInternal_CreateProvider", CharSet = CharSet.Unicode)]
internal static extern IntPtr CreateProvider(string providerName, Interop.Advapi32.EtwEnableCallback callbackFunc);

[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
[DllImport(RuntimeHelpers.QCall, EntryPoint = "EventPipeInternal_DefineEvent")]
internal static extern unsafe IntPtr DefineEvent(IntPtr provHandle, uint eventID, long keywords, uint eventVersion, uint level, void *pMetadata, uint metadataLength);

[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
[DllImport(RuntimeHelpers.QCall, EntryPoint = "EventPipeInternal_GetProvider", CharSet = CharSet.Unicode)]
internal static extern IntPtr GetProvider(string providerName);

[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
[DllImport(RuntimeHelpers.QCall, EntryPoint = "EventPipeInternal_DeleteProvider")]
internal static extern void DeleteProvider(IntPtr provHandle);

[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
[DllImport(RuntimeHelpers.QCall, EntryPoint = "EventPipeInternal_EventActivityIdControl")]
internal static extern int EventActivityIdControl(uint controlCode, ref Guid activityId);

[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
[DllImport(RuntimeHelpers.QCall, EntryPoint = "EventPipeInternal_WriteEventData")]
internal static extern unsafe void WriteEventData(IntPtr eventHandle, EventProvider.EventData* pEventData, uint dataCount, Guid* activityId, Guid* relatedActivityId);

//
// These PInvokes are used as part of the EventPipeEventDispatcher.
//
[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
[DllImport(RuntimeHelpers.QCall, EntryPoint = "EventPipeInternal_GetSessionInfo")]
internal static extern unsafe bool GetSessionInfo(ulong sessionID, EventPipeSessionInfo* pSessionInfo);

[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
[DllImport(RuntimeHelpers.QCall, EntryPoint = "EventPipeInternal_GetNextEvent")]
internal static extern unsafe bool GetNextEvent(ulong sessionID, EventPipeEventInstanceData* pInstance);

[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
[DllImport(RuntimeHelpers.QCall, EntryPoint = "EventPipeInternal_GetWaitHandle")]
internal static extern unsafe IntPtr GetWaitHandle(ulong sessionID);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace System
{
public abstract partial class Enum
{
[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
[DllImport(RuntimeHelpers.QCall, EntryPoint = "Enum_GetValuesAndNames")]
private static extern void GetEnumValuesAndNames(QCallTypeHandle enumType, ObjectHandleOnStack values, ObjectHandleOnStack names, Interop.BOOL getNames);

[MethodImpl(MethodImplOptions.InternalCall)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public static extern int CurrentManagedThreadId
}

// Terminates this process with the given exit code.
[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
[DllImport(RuntimeHelpers.QCall, EntryPoint = "Environment_Exit")]
[DoesNotReturn]
private static extern void _Exit(int exitCode);

Expand Down Expand Up @@ -84,7 +84,7 @@ public static string[] GetCommandLineArgs()
GetCommandLineArgsNative();
}

[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
[DllImport(RuntimeHelpers.QCall, EntryPoint = "Environment_GetProcessorCount")]
private static extern int GetProcessorCount();

// Used by VM
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ internal static string GetMessageFromNativeResources(ExceptionMessageKind kind)
return retMesg!;
}

[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
[DllImport(RuntimeHelpers.QCall, EntryPoint = "ExceptionNative_GetMessageFromNativeResources")]
private static extern void GetMessageFromNativeResources(ExceptionMessageKind kind, StringHandleOnStack retMesg);

internal readonly struct DispatchState
Expand Down
18 changes: 9 additions & 9 deletions src/coreclr/System.Private.CoreLib/src/System/GC.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,10 @@ public static GCMemoryInfo GetGCMemoryInfo(GCKind kind)
return new GCMemoryInfo(data);
}

[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
[DllImport(RuntimeHelpers.QCall, EntryPoint = "GCInterface_StartNoGCRegion")]
internal static extern int _StartNoGCRegion(long totalSize, bool lohSizeKnown, long lohSize, bool disallowFullBlockingGC);

[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
[DllImport(RuntimeHelpers.QCall, EntryPoint = "GCInterface_EndNoGCRegion")]
internal static extern int _EndNoGCRegion();

// keep in sync with GC_ALLOC_FLAGS in gcinterface.h
Expand All @@ -98,10 +98,10 @@ internal enum GC_ALLOC_FLAGS
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern int GetGenerationWR(IntPtr handle);

[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
[DllImport(RuntimeHelpers.QCall, EntryPoint = "GCInterface_GetTotalMemory")]
private static extern long GetTotalMemory();

[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
[DllImport(RuntimeHelpers.QCall, EntryPoint = "GCInterface_Collect")]
private static extern void _Collect(int generation, int mode);

[MethodImpl(MethodImplOptions.InternalCall)]
Expand All @@ -119,10 +119,10 @@ internal enum GC_ALLOC_FLAGS
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern ulong GetGenerationSize(int gen);

[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
[DllImport(RuntimeHelpers.QCall, EntryPoint = "GCInterface_AddMemoryPressure")]
private static extern void _AddMemoryPressure(ulong bytesAllocated);

[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
[DllImport(RuntimeHelpers.QCall, EntryPoint = "GCInterface_RemoveMemoryPressure")]
private static extern void _RemoveMemoryPressure(ulong bytesAllocated);

public static void AddMemoryPressure(long bytesAllocated)
Expand Down Expand Up @@ -289,7 +289,7 @@ public static int GetGeneration(WeakReference wo)
//
public static int MaxGeneration => GetMaxGeneration();

[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
[DllImport(RuntimeHelpers.QCall, EntryPoint = "GCInterface_WaitForPendingFinalizers")]
private static extern void _WaitForPendingFinalizers();

public static void WaitForPendingFinalizers()
Expand Down Expand Up @@ -352,10 +352,10 @@ public static long GetTotalMemory(bool forceFullCollection)
return newSize;
}

[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
[DllImport(RuntimeHelpers.QCall, EntryPoint = "GCInterface_RegisterFrozenSegment")]
private static extern IntPtr _RegisterFrozenSegment(IntPtr sectionAddress, nint sectionSize);

[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
[DllImport(RuntimeHelpers.QCall, EntryPoint = "GCInterface_UnregisterFrozenSegment")]
private static extern void _UnregisterFrozenSegment(IntPtr segmentHandle);

[MethodImpl(MethodImplOptions.InternalCall)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ internal static string FormatFileLoadExceptionMessage(string? fileName, int hRes
return string.Format(format!, fileName, message);
}

[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
[DllImport(RuntimeHelpers.QCall)]
private static extern void GetFileLoadExceptionMessage(int hResult, StringHandleOnStack retString);

[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
[DllImport(RuntimeHelpers.QCall, EntryPoint = "FileLoadException_GetMessageForHR")]
private static extern void GetMessageForHR(int hresult, StringHandleOnStack retString);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public static Assembly Load(AssemblyName assemblyRef)
return RuntimeAssembly.InternalLoad(assemblyRef, ref stackMark, AssemblyLoadContext.CurrentContextualReflectionContext);
}

[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
[DllImport(RuntimeHelpers.QCall, EntryPoint = "AssemblyNative_GetExecutingAssembly")]
private static extern void GetExecutingAssemblyNative(StackCrawlMarkHandle stackMark, ObjectHandleOnStack retAssembly);

internal static RuntimeAssembly GetExecutingAssembly(ref StackCrawlMark stackMark)
Expand Down Expand Up @@ -82,7 +82,7 @@ public static Assembly GetCallingAssembly()
return GetExecutingAssembly(ref stackMark);
}

[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
[DllImport(RuntimeHelpers.QCall, EntryPoint = "AssemblyNative_GetEntryAssembly")]
private static extern void GetEntryAssemblyNative(ObjectHandleOnStack retAssembly);

private static Assembly? GetEntryAssemblyInternal()
Expand All @@ -92,7 +92,8 @@ public static Assembly GetCallingAssembly()
return entryAssembly;
}

[MethodImpl(MethodImplOptions.InternalCall)]
[DllImport(RuntimeHelpers.QCall, EntryPoint = "AssemblyNative_GetAssemblyCount")]
[SuppressGCTransition]
internal static extern uint GetAssemblyCount();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ public static AssemblyBuilder DefineDynamicAssembly(
assemblyAttributes);
}

[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
[DllImport(RuntimeHelpers.QCall, EntryPoint = "AppDomain_CreateDynamicAssembly")]
private static extern void CreateDynamicAssembly(ObjectHandleOnStack name,
StackCrawlMarkHandle stackMark,
int access,
Expand Down
Loading