Search code examples
c#.netc++-climixed-modepin-ptr

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


I have an unmanaged class that accepts a pointer to memory as its storage space.

e.g.

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

Now I'm trying to wrap this class for use from C#. Obviously I'd like to be able to pass something like a float[] to the class. The obvious thing to do would be to use cli::pin_ptr from the wrapper class.

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

However the pinned ptr is only valid so long as the cli::pin_ptr is in scope. The moment the constructor exits I can no longer guarantee that the memory block that the C++ class has is genuine.

Is there a way to pin a pointer for the lifetime of a class? I've done a lot of searching around and only found a method using "GCHandle" that appears to be purely for managed C++ (ie not C++/CLI). Can someone point me towards a way of pinning and unpinning a pointer deterministically?


Solution

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

    A pinned GCHandle will do the job, it's usable from C++/CLI. Just make sure the handle is of the Pinned type, obviously.

    Here's an example:

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

    I've added a destructor and a finalizer, this way you get the Disposable pattern along with safe cleanup even if you forget to dispose your wrapper.