2010-11-10 18 views
5

Comme Hans Passantwishes voici le scénario du mien. J'ai une application en mode mixte dans laquelle le code natif fait tout le travail dur tout en respectant les performances et le code managé est responsable uniquement de l'interface graphique. Les utilisateurs participeront également en écrivant leur code C# propriétaire. J'ai C++ pour les classes natives, C# pour l'interface graphique et le code utilisateur et C++/Cli pour les classes wrapper entre. Parmi toutes mes classes C++, il y en a une qui fait% 90 des calculs et est créée un paramètre différent à chaque fois. Appelons-le NativeClass. Il y a apprx. 2000 instances de cette NativeClass et je dois trouver la bonne instance liée à un paramètre avant de faire le calcul. J'ai donc conçu une hash_map, avec les paramètres étant le code de hachage, à cette fin. Quand j'obtiens un paramètre, je cherche la bonne instance dans hash_map, je la trouve et appelle quelques unes de ses méthodes.
Lorsque les utilisateurs effectuent des calculs en écrivant du code C# et que cette classe exécute ces codes par des rappels. C'est trivial mais parfois j'ai besoin d'informations sur les classes .Net que les utilisateurs ont construites. J'ai donc besoin de rattacher cette ManagedClass spécifique à NativeClass d'une manière ou d'une autre. Ma première solution utilise GChandle.Alloc() et transfère l'adresse des handles. Mais il y a quelques concerns sur GC qu'il ne fera pas son travail correctement. Hans a recommandé Marshal.AllocCoTaskMem() et Marshal.StructureToPtr() pour allouer des objets gérés dans la mémoire non managée, mais je crois que cela est valide pour les classes de type de valeur ou les structures. Que diriez-vous des classes de ref? Comment puis-je transmettre une référence à NativeClass tout en empêchant la collecte du GC et le bon fonctionnement du GC en même temps?GCHandle, Maréchal, mémoire gérée et non gérée: Pour épingler ou ne pas épingler

Voici quelques exemples de code:

class NativeClass 
{ 
private: 
    int AddressOfManagedHandle; 
public: 
    static NativeClass * GetNativeClassFromHashMap(int SomeParameter) 
    { 
// return NativeClass associated with SomeParameter from NativeClassHashMap; 
    } 
    NativeClass(int addr, int SomeParameter) : AddressOfManagedHandle(addr) 
    { 

    } 
    int GetAddress(){return AddressOfManagedHandle;} 
void DoCalculation(){ 
// CALCULATIONS 
} 
}; 


public ref class ManagedClass : MarshalByRefObject 
{ 
private: 
    NativeClass* _nc; 
//GCHandle handle; 
    void FreeManagedClass() 
    { 
     Marshal::FreeHGlobal(IntPtr(_nc->GetAddress())); 
//if(handle.IsAllocated) 
//handle.Free(); 
     delete _nc; 
    } 
public: 
    ManagedClass() 
    { 
     IntPtr addr = (Marshal::AllocHGlobal(Marshal::Sizeof(this))); // Error 
     Marshal::StructureToPtr(this,addr,true); 
//handle = GCHandle.Alloc(this); 
//IntPtr addr = handle.ToIntPtr(); 
     _nc = new NativeClass(addr.ToInt32()); 
    } 
    ~ManagedClass() {FreeManagedClass();} 
    !ManagedClass() {FreeManagedClass();} 
    int GetAddress() {return _nc->GetAddress();}; 
    static ManagedClass^ GetManagedClass(int SomeParameter) 
    { 
int addr = NativeClass::GetNativeClassFromHashMap(SomeParameter)->GetAddress(); 
//Object^obj = GCHandle::FromIntPtr(IntPtr(addr)).Target; 
Object^ obj = Marshal::PtrToStructure(IntPtr(addr), ManagedClass::typeid); 
    return dynamic_cast<ManagedClass^>(obj); 

    } 
}; 

Je suis désolé qu'il est toooooo long et toujours pas claire.

+1

Vous devez utiliser IntPtr plutôt qu'int pour stocker les pointeurs natifs. Sinon, votre code peut se bloquer sur Windows 64 bits. – Elmue

+0

Vous ne devriez pas libérer de la mémoire dans une autre classe. Ecrivez un destructeur (finalizer) ~ NativeClass() qui appelle FreeHglobal(). – Elmue

Répondre

3

J'ai passé un bon moment aux prises avec un problème similaire et ce sont les grandes lignes de la solution qui a fonctionné pour moi ....

  1. magasin la poignée à la classe gérée comme une void *
  2. magasin un pointeur vers la classe non gérée dans la classe géré (comme vous l'avez fait)
  3. Utilisez simplement vieux new et delete plutôt que quoi que ce soit, comme AllocHGlobal
  4. Transformer le GCHandle entre le voi d * et objet géré référence
  5. Ne vous inquiétez pas de tirer MarshalByRefObject - vous n'avez pas besoin que vous faites votre propre marshalling
  6. Rappelez-vous le codage défensif lors de la libération de la classe gérée

Je pris votre code ci-dessus et piraté à obtenir:

class NativeClass 
{ 
private: 
    void * managedHandle; 
public: 
    static NativeClass * GetNativeClassFromHashMap(int SomeParameter) 
    { 
     // return NativeClass associated with SomeParameter from NativeClassHashMap; 
    } 
    NativeClass(void *handle, int SomeParameter) 
     : managedHandle(handle) 
    { 
    } 
    void * ManagedHandle() 
    { 
     return managedHandle; 
    } 
    void DoCalculation() 
    { 
     // CALCULATIONS 
    } 
}; 

public ref class ManagedClass 
{ 
private: 
    NativeClass* _nc; 
    void FreeManagedClass() 
    { 
     if (_nc) 
     { 
      // Free the handle to the managed object 
      static_cast<GCHandle>(IntPtr(_nc->ManagedHandle)).Free(); 
      // Delete the native object 
      delete _nc; 
      _nc = 0; 
     } 
    } 
public: 
    ManagedClass() 
    { 
     // Allocate GCHandle of type 'Normal' (see doco for Normal, Weak, Pinned) 
     GCHandle gch = GCHandle::Alloc(this, GCHandleType::Normal); 
     // Convert to void* 
     void *handle = static_cast<IntPtr>(gch).ToPointer(); 
     // Initialise native object, storing handle to native object as void* 
     _nc = new NativeClass(handle); 
    } 
    ~ManagedClass() {FreeManagedClass();} 
    !ManagedClass() {FreeManagedClass();} 
    static ManagedClass^ GetManagedClass(int SomeParameter) 
    { 
     // Native class is retrieved from hash map 
     NativeClass *nc = NativeClass::GetNativeClassFromHashMap(SomeParameter); 
     // Extract GCHandle from handle stored in native class 
     // This is the reverse of the process used in the ManagedClass constructor 
     GCHandle gch = static_cast<GCHandle>(IntPtr(nc->ManagedHandle())); 
     // Cast the target of the GCHandle to the managed object 
     return dynamic_cast<ManagedClass^>(gch.Target); 
    } 
}; 

Cela devrait vous mettre sur la bonne voie.

+2

L'avantage de cette méthode par rapport à l'utilisation de gcroot est que NativeClass peut se trouver dans une DLL C++ distincte qui est consommée par l'encapsuleur C++/CLI. C'était ce dont j'avais besoin. – mcdave

+0

Oui mon propre code, j'utilise en ce moment, est juste comme le vôtre. Les choses avec la classe Marshal ne fonctionne pas. Mais j'ai posé la question juste pour être sûr de ce qui se passe. Seules les différences sont que vous ne stockez pas handle à ManagedClass dans la classe ManagedClass et NativeClass a l'adresse du handle comme un pointeur pas comme un entier. Laissez-moi poser 3 questions. Depuis combien de temps ce code fonctionne-t-il? Avez-vous des problèmes liés à la mémoire? Quelle est votre application? –

+0

Je travaillais depuis ~ 3 ans; Aucun problème de mémoire à moins que vous n'ayez oublié de libérer le handle géré, auquel cas les objets gérés ne sont jamais récupérés; L'application est une application technique qui, comme la vôtre, nécessite l'exécution de code natif tout en ayant l'interface utilisateur dans .NET. – mcdave

0

Hmmm.

GCHandle est une structure.

Dans certains cas, le fait de passer une instruction GCHandle non épinglée fera ce que vous voulez.

+0

Eh bien, je le pensais. GCHandle.Alloc avec GCHandleTypes.Normal me semble ok. C'est ce que dit MSDN 'Ce type de handle représente un handle opaque, ce qui signifie que vous ne pouvez pas résoudre l'adresse de l'objet épinglé via la poignée. Vous pouvez utiliser ce type pour suivre un objet et empêcher sa collecte par le garbage collector. Ce membre d'énumération est utile lorsqu'un client non géré contient la seule référence, qui est indétectable depuis le garbage collector, vers un objet géré. »Sur GCHandle.Normal. –