C++/CLI: preventing garbage collection on managed

2020-03-12 03:34发布

问题:

I have a C++ unmanaged class NativeDog that needs to be used from C#, so I've create a wrapper class ManagedDog.

// unmanaged C++ class
class NativeDog
{
    NativeDog(...); // constructor
    ~NativeDog(); // destructor
    ...
}

// C++/CLI wrapper class
ref class ManagedDog
{
    NativeDog* innerObject; // unmanaged, but private, won't be seen from C#
    ManagedDog(...)
    {
        innerObject = new NativeDog(...);
        ...
    }

    ~ManagedDog() // destructor (like Dispose() in C#)
    {
        // free unmanaged resources
        if (innerObject)
            delete innerObject;
    }

    !ManagedDog() // finalizer (like Finalize() in C#, in case
    {             // the user forgets to dispose)
        ~ManagedDog(); // call destructor
    }
}

All is well, and I use the class like this:

// in C++/CLI
// this function is called from C++ code
void MyLibrary::FeedDogNative(NativeDog* nativedog)
{
    ... // (***)
}
// this function is called from C#, passes on the dog to the native function
void MyLibrary::FeedDogManaged(ManagedDog^ dog)
{
    NativeDog* rawdog = dog->innerObject;
    MyLibrary::FeedDogNative(rawdog);
}

// C# client code
void MyFunc()
{
    ManagedDog dog = new ManagedDog(...);
    MyLibrary.FeedDogManaged(dog);
}

See what's wrong? I didn't either at first, until very strange things started happening from time to time. Basically if after calling MyFunc() the program is paused by the GC while it is somewhere in the native function FeedDogNative (marked (***) above), it will think the managed wrapper can be collected because it will no longer be used, neither in the C# MyFunc (it's a local variable and will not be used after the FeedDogManaged call), neither in FeedDogManaged. And so this has actually happened on occasions. The GC calls the Finalizer, which deletes the native dog object, even though FeedDogNative has not finished using it! So my unmanaged code is now using a deleted pointer.

How can I prevent this? I can think of some ways (e.g. a dummy call pretending to use dog at the end of FeedDogManaged) but what would the recommended way be?

回答1:

You need a GC::KeepAlive() call in your FeedDogManaged function. Seems like it is an exact use case for that.



回答2:

In your managed code, add GC.KeepAlive(dog) following the call to FeedDogManaged():

http://msdn.microsoft.com/en-us/library/system.gc.keepalive(VS.71).aspx