Gooo
Gooo

Reputation: 337

How can I pass a reference object (CLR) through unmanaged code without loosing the reference?

I am creatin a wrapper for an unmanaged third party library. This library allows to register callbacks. I got the callbacks working using delegates but the problem is that when registering a callback I could supply additional user data as void* which will not be touched by the library but passed as parameter to the callback.

In code:

//interface
BOOL register_callback(LPCALLBACKFN pfnCallback, void* lpvUserData);

C++-Cli-side:

bool LibWrapper::RegisterCallback()
{
    resetLastError();

    CallbackDelegate^ callback = gcnew CallbackDelegate(
        this,
        &LibWrapper::test_callback);

    GCHandle callbackHandle = GCHandle::Alloc(callback);
    IntPtr ptrCallback = Marshal::GetFunctionPointerForDelegate(callback);
    LPCALLBACKFN nativeCallbackPtr = \
            static_cast<LPCALLBACKFN>(ptrCallback.ToPointer());

    try
    {
        return register_callback(nativeCallbackPtr,/*void* data*/ nullptr);//!!!
    }
    catch(...)
    {
        setLastError("Unknown error when trying to register a callback.");
        return false;
    }
}

Is there any way I can pass an Object^ instead of just a nullptr as user data? The object itself could really be anything from a simple managed struct to a tree node obviously referencing other nodes, hence I'd like to find a way which does not involve creating a deep unmanaged copy of the whole object.

If it's not possible I might as well use a workaround and save my own list of requested callbacks and append the appropriate userData-Object myself. In that case it would never have to leave the managed world, but it still feels like cheating.

Upvotes: 0

Views: 923

Answers (1)

Hans Passant
Hans Passant

Reputation: 942468

Well, you can, use GCHandle::Alloc() again, get the raw pointer with GCHandle::ToIntPtr() and recover the object reference again in the callback with GCHandle::FromIntPtr().

But this just isn't necessary. The power of a delegate over a function pointer is that it also captures your object reference. The this argument in your CallbackDelegate constructor call. Which ensures that your test_callback() method can be an instance function and access the object members. Even when it is called from native code which knows beans about managed objects. It is called with a valid this reference by the stub that was created when you called Marshal::GetFunctionPointerForDelegate(). So just no need to use the void* argument, simply make the object a field of your class instead.

You ought to fret a bit about cleanup btw, be sure to call GCHandle::Free() when the native code can no longer make callbacks. Not doing so leaks the managed object forever.

Upvotes: 3

Related Questions