GCHandle, Marshal, managed and unmanaged memory : To pin or Not To Pin

ali_bahoo picture ali_bahoo · Nov 10, 2010 · Viewed 7.5k times · Source

As Hans Passant wishes here is the scenario of mine. I have a mixed mode application in which the native code does all the hard work while respecting the performance and managed code is responsible for only GUI. Also users will be participating by writing their proprietary C# code. I have C++ for native classes, C# for GUI and user code and C++/Cli for wrapper classes in between. Among all of my C++ classes there is one that does %90 of the calculations and is created a different parameter each time. Let's call it NativeClass. There are apprx. 2000 instances of this NativeClass and I have to find the right instance related to some parameter before it does calculation. So I devised a hash_map, with parameters being the hash code, for this purpose. When I get a parameter, I seek for the right instance in hash_map, I find it and call someof its methods.
When users contrubute to calculations by writing C# code and this class execute these codes by callbacks. This is trivial but sometimes I need some information about the .Net classes that users built. So I need to attach that specific ManagedClass to NativeClass somehow. My first solution is using GChandle.Alloc() and transfering the handles address. But there are some concerns about GC that it will not be doing its job properly. Hans recommended the Marshal.AllocCoTaskMem() and Marshal.StructureToPtr() to allocate managed objects in unmanaged memory, however I believe this is valid for value type classes or structs. How about ref classes? How can I pass a reference to NativeClass while preventing them to be GC collected and make GC work properly at the sametime ?

Here is some sample 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);

    }
};

I am sorry that it is toooooo long and still not clear.

Answer

mcdave picture mcdave · Nov 12, 2010

I spent quite a while battling with a similar problem and this is the outline of the solution that worked for me ....

  1. Store the handle to the managed class as a void *
  2. Store a pointer to the unmanaged class in the managed class (as you have done)
  3. Use plain old new and delete rather than anything such as AllocHGlobal
  4. Transform the GCHandle between the void * and managed object reference
  5. Don't worry about deriving from MarshalByRefObject - you don't need it as you are doing your own marshalling
  6. Remember defensive coding when freeing the managed class

I took your above code and hacked at it to get:

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

This should put you on the right track.