-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Description
Description
This is a repost of my stackoverflow post, that I have found a temporary solution for here
C# function pointers causes access violation upon calling from C++
I have tried debugging, but when the issue occurs. In this case C++ calls the callback, but before it enters the function's scope the exception is thrown, but immediately returns with an error code.
For the sake of debugging, I've tried switching the calling convention from cdecl
to stdcall
to see if it has to do with stack corruption.
My assumptions are that:
- static functions marked with [UnmanagedCallersOnly] have a static address, especially when an entrypoint is defined.
- Thread.BeginThreadAffinity() followed by Thread.EndThreadAffinity() ensures that an unmanaged thread can successfully start executing managed code.
Good to know:
- The function is being invoked by a thread not created in C#, there are many threads calling this function
Reproduction Steps
In the following bit of code, I'm using C# function pointers to omit using a delegate type, for faster performance. The code works fine when I use a delegate type marshalled as a function pointer, no exception no issues. However, when changing it from a delegate to a function pointer it sometimes throws an access violation.
Code is modified to remove namespaces and prefixes not relevant to the question
public static class Engine
{
// Causing issues with sometimes throwing access violation
[DllImport("engine", CallingConvention = CallingConvention.Cdecl)]
public static unsafe extern UInt32 EventScheduler_ScheduleLocalEvent(
int priority, UInt64 delay, delegate* unmanaged[Cdecl]<nint, void> callback, nint obj);
// Works fine every time, but is much slower seems due to delegate instatiations
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void EventScheduler_ScheduleLocalEvent_callbackDelegate(nint obj);
[DllImport("engine", CallingConvention = CallingConvention.Cdecl)]
public static unsafe extern UInt32 EventScheduler_ScheduleLocalEvent(
int priority,
UInt64 delay,
[MarshalAs(UnmanagedType.FunctionPtr)] EventScheduler_ScheduleLocalEvent_callbackDelegate callback,
nint obj);
}
public static class EventScheduler
{
public delegate void EventCallback();
// attribute is only present in the function pointer senario
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl ) }, EntryPoint = "ScheduleLocalEvent_EventCallbackFunction")]
private static void ScheduleLocalEvent_EventCallbackFunction(nint handlePtr)
{
Thread.BeginThreadAffinity();
GCHandle handle = GCHandle.FromIntPtr(handlePtr);
var cb = (EventScheduler.EventCallback)handle.Target!;
cb();
handle.Free();
Thread.EndThreadAffinity();
}
public static UInt32 ScheduleLocalEvent(int priority, SimulationTime delayTime, EventCallback eventCallback)
{
GCHandle handle = GCHandle.Alloc(eventCallback, GCHandleType.Normal);
IntPtr handlePtr = GCHandle.ToIntPtr(handle);
unsafe
{
return Engine.EventScheduler_ScheduleLocalEvent(
priority, delayTime, &ScheduleLocalEvent_EventCallbackFunction, handlePtr);
}
}
}
On the C++ side
#define API extern "C" __declspec(dllexport)
API uint32_t EventScheduler_ScheduleLocalEvent(int priority, uint64_t delay, void(__cdecl* callback)(void* obj), void* obj)
{
ERSAssert(Ers::Core::InsideSubModel());
Core::SyncManagerBase& syncManager = Ers::Core::GetSyncManagerBase();
return syncManager.ScheduleEvent(priority, delay, [callback, obj]() { callback(obj); });
}
Expected behavior
The thread should enter and execute the static function passed using a function pointer
Actual behavior
The thread doesn't enter the user-defined portion of the function. an Access Violation is thrown in a generated call visible in the IL during the native-to-managed-transition
Regression?
No response
Known Workarounds
Change the garbage collection mode by adding ```xml
true
true
### Configuration
Tested on PC's around the office with the same behavior. All were x64, issue occurred on both Windows and Linux. The issue was reproduced on .net8.0 and .net9.0.100-preview
### Other information
The issue doesn't appear always, i think it might be a race condition inside the garbage collector.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status