1

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?

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
Goz
  • 61,365
  • 24
  • 124
  • 204
  • You can't just make it a class-level field? – Ron Beyer Dec 14 '15 at 22:31
  • @RonBeyer nope, it could be moved by the GC, and that can cause an access violation if the native code accesses the pointer later on. – Lucas Trzesniewski Dec 14 '15 at 22:34
  • @RonBeyer: No, `pin_ptr` is "magic" and can only be used for locals. ["Pinning pointers can only be declared as non-static local variables on the stack. "](https://msdn.microsoft.com/en-us/library/1dz8byfh.aspx) – Ben Voigt Dec 14 '15 at 22:54

2 Answers2

2

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.

Community
  • 1
  • 1
Lucas Trzesniewski
  • 50,214
  • 11
  • 107
  • 158
2

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.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Except if the array is large enough to hit the Large Object Heap, in which case pinning it has no effect on the garbage collector compaction efforts at all. – Ben Voigt Dec 14 '15 at 22:53
  • Yup, I should've added this disclaimer to my answer, as it's very valid - pinned handles aren't cheap. I figured OP wanted to manipulate the array from C# and C++ at the same time, so exposing the data as a managed array makes sense in that case (no need to use `unsafe` on the C# side). But I guess you could just expose additional accessor functions (or even an indexer), and the whole thing could be solved elegantly that way. – Lucas Trzesniewski Dec 14 '15 at 23:21