2016-01-15 1 views
4

En utilisant Microsoft Visual C# 2010, j'ai récemment remarqué que vous pouvez passer des objets par ref au code non managé. Je me suis donc chargé de tenter d'écrire du code non managé qui convertit un char * C++ en une chaîne C# en utilisant un rappel au code managé. J'ai fait deux tentatives.Quel est le niveau de sécurité de ref lorsqu'il est utilisé avec un code dangereux?

Tentative 1: Appelez la fonction non managée qui stocke un paramètre ref. Ensuite, une fois cette fonction renvoyée au code managé, appelez une autre fonction non managée qui appelle une fonction de rappel qui convertit le caractère * en une chaîne managée.

C++ 
typedef void (_stdcall* CallbackFunc)(void* ManagedString, char* UnmanagedString); 

CallbackFunc UnmanagedToManaged = 0; 
void* ManagedString = 0; 

extern "C" __declspec(dllexport) void __stdcall StoreCallback(CallbackFunc X) { 
    UnmanagedToManaged = X; 
} 
extern "C" __declspec(dllexport) void __stdcall StoreManagedStringRef(void* X) { 
    ManagedString = X; 
} 
extern "C" __declspec(dllexport) void __stdcall CallCallback() { 
    UnmanagedToManaged(ManagedString, "This is an unmanaged string produced by unmanaged code"); 
} 

C# 
[DllImport("Name.dll", CallingConvention = CallingConvention.StdCall)] 
public static extern void StoreCallback(CallbackFunc X); 
[DllImport("Name.dll", CallingConvention = CallingConvention.StdCall)] 
public static extern void StoreManagedStringRef(ref string X); 
[DllImport("Name.dll", CallingConvention = CallingConvention.StdCall)] 
public static extern void CallCallback(); 

[UnmanagedFunctionPointer(CallingConvention.StdCall)] 
public delegate void CallbackFunc(ref string Managed, IntPtr Native); 

static void Main(string[] args) { 
    string a = "This string should be replaced"; 

    StoreCallback(UnmanagedToManaged); 
    StoreManagedStringRef(ref a); 
    CallCallback(); 
} 

static void UnmanagedToManaged(ref string Managed, IntPtr Unmanaged) { 
    Managed = Marshal.PtrToStringAnsi(Unmanaged); 
} 

Tentative 2: ref chaîne passe à la fonction non géré qui passe l'arbitre de chaîne à la fonction de rappel géré.

C++ 
typedef void (_stdcall* CallbackFunc)(void* ManagedString, char* UnmanagedString); 

CallbackFunc UnmanagedToManaged = 0; 

extern "C" __declspec(dllexport) void __stdcall StoreCallback(CallbackFunc X) { 
    UnmanagedToManaged = X; 
} 
extern "C" __declspec(dllexport) void __stdcall DoEverything(void* X) { 
    UnmanagedToManaged(X, "This is an unmanaged string produced by unmanaged code"); 
} 

C# 
[DllImport("Name.dll", CallingConvention = CallingConvention.StdCall)] 
public static extern void StoreCallback(CallbackFunc X); 
[DllImport("Name.dll", CallingConvention = CallingConvention.StdCall)] 
public static extern void DoEverything(ref string X); 

[UnmanagedFunctionPointer(CallingConvention.StdCall)] 
public delegate void CallbackFunc(ref string Managed, IntPtr Unmanaged); 

static void Main(string[] args) { 
    string a = "This string should be replaced"; 

    StoreCallback(UnmanagedToManaged); 
    DoEverything(ref a); 
} 

static void UnmanagedToManaged(ref string Managed, IntPtr Unmanaged) { 
    Managed = Marshal.PtrToStringAnsi(Unmanaged); 
} 

La tentative 1 ne fonctionne pas mais la tentative 2 le fait. Dans la tentative 1, il semble que dès que le code non géré revient après avoir stocké le ref, l'arbitre devient invalide. Pourquoi cela arrive-t-il? Compte tenu des résultats de la tentative 1, j'ai des doutes que la tentative 2 fonctionnera de manière fiable. Donc, quelle est la sécurité de ref sur le côté non géré du code lorsqu'il est utilisé avec du code non managé? Ou en d'autres termes, ce qui ne fonctionnera pas dans le code non géré lors de l'utilisation ref?

choses que je voudrais savoir sont sont:

Qu'est-ce qui se passe exactement quand les objets sont passés en utilisant ref au code non managé? Est-ce que cela garantit que les objets resteront à leur position actuelle en mémoire pendant que le ref est utilisé en code non managé?

Quelles sont les limitations de ref (que ne puis-je pas faire avec un ref) en code non managé?

+0

Le code dangereux est appelé "dangereux" pour une raison ... il est dangereux. :) – Almo

Répondre

1

Une discussion complète de la façon dont p/Invoke des œuvres est au-delà de la portée d'un bon débordement de la pile Q & A. Mais brièvement:

Dans aucun de vos exemples vous passez vraiment l'adresse de votre variable gérée à la code non managé. La couche p/invoke inclut une logique de marshaling qui traduit vos données managées en quelque chose d'utilisable par le code non managé, puis se traduit lorsque le code non managé est renvoyé.

Dans les deux exemples, la couche p/invoke doit créer un objet intermédiaire à des fins de marshaling. Dans le premier exemple, cet objet est parti au moment où vous appelez à nouveau le code non managé. Bien sûr, dans le deuxième exemple, ce n'est pas le cas, puisque tout le travail se passe en même temps.

Je crois que votre deuxième exemple devrait être sûr à utiliser. C'est-à-dire que la couche p/invoke est assez intelligente pour gérer correctement ref dans ce cas. Le premier exemple n'est pas fiable parce que p/invoke est mal utilisé, pas à cause d'une limitation fondamentale des paramètres ref.


Quelques points supplémentaires:

  • je ne voudrais pas utiliser le mot "dangereux" ici. Oui, l'appel à un code non géré est à certains égards dangereux, mais en C# "dangereux" a une signification très spécifique, liée à l'utilisation du mot-clé unsafe. Je ne vois rien dans votre exemple de code qui utilise réellement unsafe.
  • Dans les deux exemples, vous avez un bug lié à votre utilisation du délégué passé au code non managé. En particulier, alors que la couche p/invoke peut traduire votre référence de délégué géré en un pointeur de fonction que le code non géré peut utiliser, elle ne connaît rien de l'objet à vie de l'objet délégué. Il gardera l'objet en vie assez longtemps pour que l'appel à la méthode p/invoquée se termine, mais si vous en avez besoin pour vivre plus longtemps que cela (comme ce serait le cas ici), vous devez le faire vous-même. Par exemple, utilisez GC.KeepAlive() sur une variable dans laquelle vous avez stocké la référence. (Vous pouvez probablement reproduire un plantage en insérant un appel à GC.Collect() entre l'appel à StoreCallback() et l'appel ultérieur au code non managé où le pointeur de fonction serait utilisé).
+0

Vous avez raison sur le deuxième point supplémentaire. L'insertion de GC.Collect() a provoqué un blocage. Je suppose que garder une référence statique du délégué fonctionnerait également. –