我有一些使用单个全局变量的简单C代码。显然这不是线程安全的,所以当我使用P / invoke从C#中的多个线程调用它时,事情搞砸了。
如何为每个线程单独导入此函数,还是使其成为线程安全的?
我尝试声明变量__declspec(thread)
,但这导致程序崩溃。我也试过制作一个C ++ / CLI类,但它不允许成员函数__declspec(naked)
,我需要(我正在使用内联汇编)。我编写多线程C ++代码并不是很有经验,所以可能会有一些我缺少的东西。
以下是一些示例代码:
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,{{1}作为内部状态)。因此,使用互斥锁或其他类型的锁不是一种选择 - 每个线程必须拥有自己的someGlobalVariable
副本。
答案 0 :(得分:7)
常见的模式是
C#方面看起来像这样:
用法:的
var state = new ThreadLocal<SomeSafeHandle>(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;
}
}
答案 1 :(得分:2)
就个人而言如果要在其他地方调用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);
<强> [编辑..] 强>
正如所指出的,这序列化了对在这种情况下你不能自己做的功能的访问。您有一些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);
答案 2 :(得分:2)
您可以确保在C#代码中一次只调用_someFunction一次,或者更改C代码以包含对像关键部分这样的同步原语中的全局变量的访问。
我建议更改C#代码而不是C代码,因为C#代码是多线程的,而不是C代码。
答案 3 :(得分:1)
好消息,你可以创建一个__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才能使用这样的课程。那是对的:用C ++包装的asm,包装在COM中,用RCW包装,用CLR包装......