我有一些简单的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,与。 someGlobalVariable
作为内部状态)。 因此,使用一个互斥体或其他类型的锁是不是一种选择-每个线程都必须有它自己的拷贝someGlobalVariable
。
一个常见的模式是有
- 对于状态分配内存的功能,
- 有没有副作用,但突变传入的状态和功能
- 释放的状态memoy的功能。
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;
}
}
您可以确保你只有一次在同一时间在你的C#代码中调用_someFunction或更改C代码来包装原始的像一个关键部分的同步访问全局变量。
我会建议改变C#代码,而不是C代码,如C#代码是多线程的,不C代码。
个人如果C代码是在其他地方叫我会用一个互斥体存在。 如果不浮动你的船,你可以在净锁很容易:
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);
[编辑..]
正如指出的那样,这种串行访问,你不能做自己在这种情况下的功能。 您有(错误IMO)的调用暴露功能中采用了全球对状态的一些C / C ++代码。
正如你所观察到__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 ...