GCHandle、Marshal、托管和非托管内存:钉住还是不钉住?

5

作为Hans Passant 希望,这是我的情况。我有一个混合模式应用程序,其中本机代码完成所有艰苦的工作,同时尊重性能,托管代码仅负责GUI。此外,用户将通过编写专有的C#代码参与其中。我有本机类的C++,GUI和用户代码以及中间包装器类的C++/Cli。在我所有的C++类中,有一个类占据了90%的计算,并且每次都会创建不同的参数。让我们称之为NativeClass。有大约2000个此NativeClass实例,我必须在进行计算之前找到与某些参数相关的正确实例。因此,我设计了一个hash_map,其中参数是哈希码,用于此目的。当我获得参数时,在hash_map中寻找正确的实例,找到它并调用一些方法。
当用户通过编写C#代码参与计算并且该类通过回调执行这些代码时,这很平凡。但有时我需要一些关于用户构建的.NET类的信息。因此,我需要以某种方式将特定的ManagedClass附加到NativeClass。我的第一个解决方案是使用GChandle.Alloc()并传输句柄地址。但是,有一些担忧关于GC它不会正确地完成其工作。Hans建议使用Marshal.AllocCoTaskMem()和Marshal.StructureToPtr()在非托管内存中分配托管对象,但是我认为这对于值类型类或结构是有效的。那么引用类怎么办?我如何传递对NativeClass的引用,同时防止它们被GC收集并使GC同时正常工作?

这里是一些示例代码:

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

    }
};

很抱歉,这篇文章太长了,而且还不够清晰易懂。


1
你应该使用IntPtr而不是int来存储本地指针。否则,你的代码可能会在64位Windows上崩溃。 - Elmue
你不应该在另一个类中释放内存。编写一个析构函数(finalizer)~ NativeClass(),调用FreeHglobal()。 - Elmue
3个回答

3

我花了一段时间与类似的问题作斗争,以下是对我有效的解决方案的概述....

  1. 将托管类的句柄存储为 void *
  2. 在托管类中存储一个指向非托管类的指针(就像你所做的那样)
  3. 使用普通的 newdelete,而不是任何类似于 AllocHGlobal 的东西
  4. void * 和托管对象引用之间转换 GCHandle
  5. 不必担心从 MarshalByRefObject 派生 - 因为您正在进行自己的封送处理,所以您不需要它
  6. 在释放托管类时记得进行防御性编程

我使用了上面的代码并进行了修改,得到了:

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

这应该会为你指明正确的方向。

2
使用此方法的优点是,NativeClass可以位于单独的C++ DLL中,该DLL由C++/CLI包装器使用。这正是我所需要的。 - mcdave
是的,我现在正在使用自己的代码,就像你的一样。Marshal类的东西不起作用。但我问这个问题只是为了确定发生了什么。唯一的区别是你没有在ManagedClass中存储对ManagedClass的句柄,而NativeClass将句柄地址作为指针而不是整数。让我问三个问题。这段代码运行了多长时间?您是否有任何与内存相关的问题?您的应用程序是关于什么的? - ali_bahoo
已经工作了大约3年;除非忘记释放托管句柄,否则不会出现内存问题,否则托管对象永远不会被垃圾收集;该应用程序是一个技术性的应用程序,类似于您的应用程序,需要在 .NET 中拥有 UI 的同时执行本机代码以获得更好的性能。 - mcdave

0

我认为我需要使用/clr编译C++代码。但是我使用了Boost和TBB库。这会对性能产生什么影响? - ali_bahoo

0

嗯。

GCHandle是一个结构体。

在某些情况下,传递未固定的GCHandle引用可能会达到你想要的效果。


我认为没问题。对于GCHandleTypes.Normal,使用GCHandle.Alloc看起来很好。这就是MSDN所说的:“此句柄类型表示不透明句柄,这意味着您无法通过句柄解析固定对象的地址。您可以使用此类型来跟踪对象并防止垃圾回收器对其进行回收。当非托管客户端持有唯一引用(从垃圾回收器无法检测到)到托管对象时,此枚举成员非常有用。”关于GCHandle.Normal。 - ali_bahoo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接