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

712 views Asked by At

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?

2

There are 2 answers

2
Lucas Trzesniewski On BEST ANSWER

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.

2
Hans Passant On

Sure, you cannot use pin_ptr<>. GCHandle is as usable in VB.NET as in C# as in C++/CLI as in "managed C++". There is no distinction between the latter two btw, just different syntax. GCHandle is a plain .NET type, usable in any .NET compatible language.

And no, what you are contemplating is a pretty bad idea. Pinning arrays for a long period of time is pretty evil. Where "long" depends on the rate at which the garbage collector runs, in general pinning for more than a handful of seconds is pretty bad, doing it for minutes is beyond the pale.

A pinned object is a rock in the road that the garbage collector has to constantly maneuver around when it compacts the heap, it makes heap usage less efficient. Starts to get a real problem when it is the only remaining object in the heap segment, it cannot be recycled and that array now costs you two handful of megabytes. Add several hands and feet on a server.

Once you get the handful, you next consider just plain copying the array. From the array<float>^ to a float[] that you allocate with the new[] operator. Nothing much to it, runs at 7 handfuls of gigabytes per second, give or take.