如何从C#调用C(P / invoke)代码“线程安全”

我有一些使用单个全局变量的简单C代码。 显然这不是线程安全的,所以当我使用P / invoke从C#中的多个线程调用它时,事情搞砸了。

如何为每个线程单独导入此函数,还是使其线程安全?

我尝试声明变量__declspec(thread) ,但这导致程序崩溃。 我也试过制作一个C ++ / CLI类,但它不允许成员函数是__declspec(naked) ,我需要(我使用的是内联汇编) 。 我编写multithreadingC ++代码并不是很有经验,所以我可能会遗漏一些东西。


这是一些示例代码:

C#

 [DllImport("MyDll.dll", CallingConvention = CallingConvention.Cdecl)] public static extern int SomeFunction(int parameter1, int parameter2); 

C ++

 extern "C" { int someGlobalVariable; int __declspec(naked) _someFunction(int parameter1, int parameter2) { __asm { //someGlobalVariable read/written here } } int __declspec(dllexport) SomeFunction(int parameter1, int parameter2) { return _someFunction(parameter1, parameter2); } } 

[编辑]SomeFunction()的结果必须基于someGlobalVariable以某种规定的顺序进行(例如,考虑PRNG, someGlobalVariable作为内部状态) 。 因此,使用互斥锁或其他类型的锁不是一种选择 – 每个线程必须有自己的someGlobalVariable副本。

一种常见的模式是

  • 一个为状态分配内存的函数,
  • 一个没有副作用但改变传入状态的函数,和
  • 释放状态的memoy的函数。

C#端看起来像这样:

用法:

 var state = new ThreadLocal(NativeMethods.CreateSomeState); Parallel.For(0, 100, i => { var result = NativeMethods.SomeFunction(state.Value, i, 42); Console.WriteLine(result); }); 

声明:

 internal static class NativeMethods { [DllImport("MyDll.dll", CallingConvention = CallingConvention.Cdecl)] public static extern SomeSafeHandle CreateSomeState(); [DllImport("MyDll.dll", CallingConvention = CallingConvention.Cdecl)] public static extern int SomeFunction(SomeSafeHandle handle, int parameter1, int parameter2); [DllImport("MyDll.dll", CallingConvention = CallingConvention.Cdecl)] internal static extern int FreeSomeState(IntPtr handle); } 

SafeHandle魔术:

 [SecurityPermission(SecurityAction.InheritanceDemand, UnmanagedCode = true)] [SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)] internal class SomeSafeHandle : SafeHandle { [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] public SomeSafeHandle() : base(IntPtr.Zero, true) { } public override bool IsInvalid { get { return this.handle == IntPtr.Zero; } } [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] protected override bool ReleaseHandle() { return NativeMethods.FreeSomeState(this.handle) == 0; } } 

您可以确保在C#代码中一次只调用_someFunction一次,或者更改C代码以包含对像关键部分这样的同步原语中的全局变量的访问。

我建议更改C#代码而不是C代码,因为C#代码是multithreading的,而不是C代码。

就个人而言如果要在其他地方调用C代码,我会在那里使用互斥锁。 如果这不漂浮你的船,你可以很容易地锁定.Net:

 static object SomeFunctionLock = new Object(); public static int SomeFunction(int parameter1, int parameter2){ lock ( SomeFunctionLock ){ return _SomeFunction( parameter1, parameter2 ); } } [DllImport("MyDll", CallingConvention = CallingConvention.Cdecl)] internal static extern int _SomeFunction(int parameter1, int parameter2); 

[编辑..]

正如所指出的,这序列化了对在这种情况下你不能自己做的function的访问。 您有一些C / C ++代码(错误的IMO)在调用公开函数期间使用全局for状态。

正如您已经观察到__declspec(thread)技巧在这里不起作用那么我会尝试将您的状态/上下文作为不透明指针来回传递,如下所示: –

 extern "C" { int _SomeOtherFunction( void* pctx, int p1, int p2 ) { return stuff; } // publically exposed library function int __declspec(dllexport) SomeFunction(int parameter1, int parameter2) { StateContext ctx; return _SomeOtherFunction( &ctx, parameter1, parameter2 ); } // another publically exposed library function that takes state int __declspec(dllexport) SomeFunctionWithState(StateContext * ctx, int parameter1, int parameter2) { return _SomeOtherFunction( ctx, parameter1, parameter2 ); } // if you wanted to create/preserve/use the state directly StateContext * __declspec(dllexport) GetState(void) { ctx = (StateContext*) calloc( 1 , sizeof(StateContext) ); return ctx; } // tidy up void __declspec(dllexport) FreeState(StateContext * ctx) { free (ctx); } } 

和之前对应的C#包装器一样:

 [DllImport("MyDll", CallingConvention = CallingConvention.Cdecl)] internal static extern int SomeFunction(int parameter1, int parameter2); [DllImport("MyDll", CallingConvention = CallingConvention.Cdecl)] internal static extern int SomeFunctionWithState(IntPtr ctx, int parameter1, int parameter2); [DllImport("MyDll", CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr GetState(); [DllImport("MyDll", CallingConvention = CallingConvention.Cdecl)] internal static extern void FreeState(IntPtr); 

好消息是,您可以创建__declspec(naked)函数作为C ++(非CLI)类的成员:

 class A { int n; public: A() { n = 0; } void f(int n1, int n2); }; __declspec(naked) void A::f(int n1, int n2) { n++; } 

坏消息,你需要COM才能使用这样的课程。 这是正确的:asm用C ++包装,用COM包装,用RCW包装,用CLR包装……