【问题标题】:Using the memory allocated by a CLI array as storage for an unmanaged class使用 CLI 数组分配的内存作为非托管类的存储空间
【发布时间】:2015-12-14 22:11:28
【问题描述】:

我有一个非托管类,它接受指向内存的指针作为其存储空间。

例如

class MemBlock
{
    void* mpMemoryBlock;

    // Various other variables that manipulate the memory block goes here.
public:
    MemBlock( void* pMemoryBlock ) :
        mpMemoryBlock( pMemoryBlock )
    {
    }

    // A load of code that does operation on the memory block goes here.
};

现在我正在尝试包装这个类以便在 C# 中使用。显然,我希望能够将float[] 之类的东西传递给班级。显而易见的事情是使用包装类中的cli::pin_ptr

public ref class MemBlockWrapper
{
    MemBlock* mpMemBlock;
public:
    MemBlockWrapper( array< float >^ fltArray )
    {
        cli::pin_ptr< float > pFltArray = &fltArray[0];

        // Brilliant we can now pass this pinned array to the C++ code.
        mpMemBlock  = new MemBlock( (void*)pFltArray );

        // Now the pin_ptr goes out of scope ...
    }
}

但是,只要 cli::pin_ptr 在范围内,固定的 ptr 才有效。构造函数退出的那一刻,我不能再保证 C++ 类拥有的内存块是真实的。

有没有办法在类的生命周期内固定一个指针?我做了很多搜索,只找到了一种使用“GCHandle”的方法,该方法似乎纯粹用于托管 C++(即不是 C++/CLI)。有人可以向我指出一种确定性地固定和取消固定指针的方法吗?

【问题讨论】:

标签: c# .net c++-cli mixed-mode pin-ptr


【解决方案1】:

警告:直接回答了这个问题,但在你尝试这个之前,首先阅读Hans' answer并确保你真的了解发生了什么并且仍然想要做就这样吧。

固定的GCHandle 可以完成这项工作,它可以在 C++/CLI 中使用。只要确保句柄是 Pinned 类型,很明显。

这是一个例子:

public ref class MemBlockWrapper
{
    MemBlock* mpMemBlock;
    System::Runtime::InteropServices::GCHandle hFltArray;
public:
    MemBlockWrapper(array< float >^ fltArray)
    {
        hFltArray = System::Runtime::InteropServices::GCHandle::Alloc(
            fltArray,
            System::Runtime::InteropServices::GCHandleType::Pinned);

        mpMemBlock = new MemBlock(hFltArray.AddrOfPinnedObject().ToPointer());
    }

    ~MemBlockWrapper()
    {
        this->!MemBlockWrapper();
    }

    !MemBlockWrapper()
    {
        if (mpMemBlock)
        {
            delete mpMemBlock;
            mpMemBlock = nullptr;
            hFltArray.Free();
        }
    }
};

我添加了一个析构函数和一个终结器,这样即使您忘记处置包装器,您也可以获得Disposable 模式以及安全清理。

【讨论】:

  • gcroot&lt;T&gt;GCHandle 的包装器,但它创建的句柄没有固定。所以这里不是很相关。
  • @BenVoigt 哎呀,你说得对,时间不早了,看来我该睡觉了;)
【解决方案2】:

当然,你不能使用 pin_ptr。 GCHandle 在 VB.NET 中与在 C# 中一样可用,在 C++/CLI 中与在“托管 C++”中一样可用。顺便说一句,后两者之间没有区别,只是语法不同。 GCHandle 是一种普通的 .NET 类型,可用于任何 .NET 兼容语言。

不,您正在考虑的是一个非常糟糕的主意。长时间固定数组是非常邪恶的。其中“长”取决于垃圾收集器的运行速率,通常固定超过几秒钟是非常糟糕的,几分钟就超出了苍白。

固定对象是垃圾收集器在压缩堆时必须不断移动的道路上的一块石头,它会降低堆的使用效率。当它是堆段中唯一剩余的对象时开始出现真正的问题,它不能被回收,并且该数组现在花费你两兆字节。在服务器上添加几只手和脚。

一旦你得到了少数,你接下来考虑只是简单地复制数组。从 array&lt;float&gt;^ 到您使用 new[] 运算符分配的 float[]。没什么大不了的,以每秒 7 GB 的速度运行,无论给予还是索取。

【讨论】:

  • 除非数组大到足以命中大对象堆,在这种情况下,固定它对垃圾收集器的压缩工作完全没有影响。
  • 是的,我应该将此免责声明添加到我的答案中,因为它非常有效 - 固定手柄并不便宜。我认为 OP 希望同时从 C# 和 C++ 操作数组,因此在这种情况下将数据公开为托管数组是有意义的(无需在 C# 端使用unsafe)。但我想你可以只公开额外的访问器函数(甚至是索引器),整个事情都可以这样优雅地解决。
猜你喜欢
  • 2010-12-03
  • 2011-09-21
  • 2013-03-14
  • 1970-01-01
  • 2011-02-08
  • 1970-01-01
  • 1970-01-01
  • 2021-07-03
  • 1970-01-01
相关资源
最近更新 更多