使用 CLI 数组分配的内存作为非托管 class 的存储

Using the memory allocated by a CLI array as storage for an unmanaged class

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

例如

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

现在我正在尝试包装这个 class 以便在 C# 中使用。显然,我希望能够将类似 float[] 的内容传递给 class。显而易见的事情是使用包装器 class 中的 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 在范围内时才有效。构造函数退出的那一刻,我不能再保证 C++ class 拥有的内存块是真实的。

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

Warning: This directly answers the question, but before you try this, first read and make sure you really understand what's going on and still want to do it this way.

固定的 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 模式以及安全清理。

当然,您不能使用 pin_ptr<>。 GCHandle 在 VB.NET 中、在 C# 中、在 C++/CLI 中以及在 "managed C++" 中一样可用。顺便说一句,后两者之间没有区别,只是语法不同。 GCHandle 是一种普通的 .NET 类型,可用于任何 .NET 兼容语言。

不,你正在考虑的是一个非常糟糕的主意。长时间固定数组是非常邪恶的。其中 "long" 取决于垃圾收集器的运行速度,通常固定超过几秒钟是非常糟糕的,持续几分钟就太过分了。

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

一旦你得到了少数,接下来你会考虑简单地复制数组。从 array<float>^ 到您使用 new[] 运算符分配的 float[]。没什么大不了的,以每秒 7 GB 的速度运行,给或取。