Comment créer un code C (P / invoke) appelé à partir de C # “Thread-safe”

J’ai un code C simple qui utilise une seule variable globale. Évidemment, ce n’est pas sûr pour les threads, donc quand j’appelle ça depuis plusieurs threads en C # en utilisant P / invoke, les choses se gâchent.

Comment puis-je importer cette fonction séparément pour chaque thread ou la rendre thread-safe?

J’ai essayé de déclarer la variable __declspec(thread) , mais cela a provoqué le blocage du programme. J’ai également essayé de créer une classe C ++ / CLI, mais cela ne permet pas aux fonctions membres d’être __declspec(naked) , ce dont j’ai besoin (j’utilise inline-assembly) . Je ne suis pas très expérimenté en écriture de code C ++ multithread, il se peut donc que quelque chose me manque.


Voici un exemple de code:

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); } } 

[Edit] : Le résultat de SomeFunction() doit être SomeFunction() dans un ordre prescrit basé sur someGlobalVariable (pensez par exemple à un PRNG, avec someGlobalVariable comme état interne) . Donc, l’utilisation d’un mutex ou d’un autre type de verrou n’est pas une option – chaque thread doit avoir sa propre copie de someGlobalVariable .

Un modèle commun est d’avoir

  • une fonction qui alloue de la mémoire pour l’état,
  • une fonction qui n’a pas d’effets secondaires, mais mute de l’état transmis, et
  • une fonction qui libère le memoy pour l’état.

Le côté C # ressemblerait à ceci:

Usage:

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

Déclarations:

 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 magic:

 [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; } } 

Vous pouvez soit vous assurer que vous appelez seulement _someFunction une fois à la fois dans votre code C #, soit modifier le code C pour encapsuler l’access à la variable globale dans une primitive de synchronisation comme une section critique.

Je recommanderais de changer le code C # plutôt que le code C, car le code C # est multi-threadé, pas le code C.

Personnellement, si le code C devait être appelé ailleurs, j’utiliserais un mutex. Si cela ne fait pas flotter votre bateau, vous pouvez verrouiller .Net assez facilement:

 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); 

[Modifier..]

Comme indiqué, cela sérialise l’access à la fonction que vous ne pouvez pas exécuter vous-même dans ce cas. Vous avez du code C / C ++ qui (à tort, IMO) utilise un état global pour lors de l’appel de la fonction exposée.

Comme vous avez constaté que l’astuce __declspec(thread) ne fonctionne pas ici, j’essaierais alors de faire passer votre état / contexte en tant que pointeur opaque comme ceci: –

 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); } } 

Et le wrapper C # correspondant comme avant:

 [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); 

La bonne nouvelle est que vous pouvez créer une fonction __declspec(naked) tant que membre de la classe C ++ (non-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++; } 

Les mauvaises nouvelles, vous aurez besoin de COM pour pouvoir utiliser une telle classe. C’est vrai: asm enveloppé dans C ++, enveloppé dans COM, enveloppé dans RCW, enveloppé dans CLR …