如何让C(P /调用)的代码从C#叫做“线程安全”(How to make C (P/invoke)

2019-07-29 05:01发布

我有一些简单的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

Answer 1:

一个常见的模式是有

  • 对于状态分配内存的功能,
  • 有没有副作用,但突变传入的状态和功能
  • 释放的状态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;
    }
}


Answer 2:

您可以确保你只有一次在同一时间在你的C#代码中调用_someFunction或更改C代码来包装原始的像一个关键部分的同步访问全局变量。

我会建议改变C#代码,而不是C代码,如C#代码是多线程的,不C代码。



Answer 3:

个人如果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);


Answer 4:

好消息,你可以创建一个__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 ...



文章来源: How to make C (P/invoke) code called from C# “Thread-safe”