11

単一のグローバル変数を使用する単純な C コードがあります。明らかにこれはスレッドセーフではないため、C# で P/invoke を使用して複数のスレッドから呼び出すと、問題が発生します。

この関数をスレッドごとに個別にインポートするか、スレッドセーフにするにはどうすればよいですか?

変数を宣言しようとしました__declspec(thread)が、プログラムがクラッシュしました。__declspec(naked)また、C++/CLI クラスを作成しようとしましたが、必要なメンバー関数を にすることはできません(インライン アセンブリを使用しています)。私はマルチスレッドの 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);
    }
}

[編集] : must go の結果は、(内部状態としてのPRNG などを考えSomeFunction()てください)に基づいて所定の順序で移動します。そのため、ミューテックスやその他の種類のロックを使用することはオプションではありません。各スレッドには、独自の のコピーが必要です。someGlobalVariable someGlobalVariablesomeGlobalVariable

4

4 に答える 4

8

共通のパターンは、

  • 状態にメモリを割り当てる関数
  • 副作用はないが、渡された状態を変更する関数、および
  • 状態のメモリを解放する関数。

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;
    }
}
于 2012-04-30T19:06:43.960 に答える
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);

[編集..]

指摘したように、これは、この場合自分ではできない関数へのアクセスをシリアル化します。公開された関数の呼び出し中に状態にグローバルを使用する (誤って 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);
于 2012-04-30T18:09:04.583 に答える
2

C# コードで _someFunction を一度に 1 回だけ呼び出すことを確認するか、C コードを変更してクリティカル セクションのような同期プリミティブでグローバル変数へのアクセスをラップすることができます。

C# コードは C コードではなくマルチスレッドであるため、C コードではなく C# コードを変更することをお勧めします。

于 2012-04-30T18:10:52.127 に答える
1

C++ (非 CLI) クラスのメンバーとして関数を作成できます。__declspec(naked)

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 でラップされた...

于 2012-04-30T19:29:01.737 に答える