5
votes

I need to use an unmanaged API from C++/CLI. This API stores a void pointer to arbitrary user data and a few callbacks. It then eventually calls those callbacks, passing the user data in as void*.

So far I had a native class passing its "this" pointer as the user data, and using that pointer to have the API call back into this class, i.e.:

static void __stdcall Callback(void* userData) {
    ((MyType*)userData)->Method();
}

class MyType {
public:
    MyType() { RegisterWithApi((void*)this, Callback); }
    void Method();
};

I'm trying to translate this using a managed class. I found that the type gcroot can be used to safely store a managed reference in native code, so here's how I'm doing it now:

// This is called by the native API
static void __stdcall Callback(void* userData) {
    // Cast back to gcroot and call into managed code
    (*(gcroot<MyType^>*)userData)->Method();
}

ref class MyType {
    gcroot<MyType^>* m_self;
public:
    MyType() { 
        m_self = new gcroot<MyType^>;
        RegisterWithApi((void*)m_self, Callback);
    }
    ~MyType() { delete m_self; }
    // Method we want called by the native API
    void Method();
}

While this seems fine to the C++/CLI compiler, I am not perfectly re-assured. From what I understand, gcroot somehow keeps track of its managed reference as it is moved by the GC. Will it manage to do this while stored as a void* by unmanaged code? Is this code safe?

Thanks.

2
Do favor Marshal::GetFunctionPointerForDelegate(), an example is hereHans Passant

2 Answers

2
votes

This is what I ended up doing and it works perfectly. The purpose of gcroot is to store a managed reference on the native heap, which is precisely what I'm doing here.

0
votes

No! It's exactly the other way around. gcroot is a native class template. You use it to store a handle to managed memory in a native type which is compiled with clr support. You will typically use it to divert calls to member functions of a native object to a managed object stored in a member of type gcroot.

EDIT: I was on mobile yesterday where typing code examples is a bit awkward... The intended and typical usage of gcroot<T^> is somewhere along these lines:

// ICallback.h
struct ICallback {
    virtual void Invoke() = 0;
    virtual void Release() = 0;
    protected:
        ~ICallback() {}
};

That is what your native apps or libraries see and include. Then, you have a mixed component compiled with CLR support, which implements ICallback and stores a handle to some managed object in a gcroot<ManagedType^>:

// Callback.cpp (this translation unit must be compiled with /clr)
// I did not compile and test, but you get the point...
template<class T^> class Callback : public ICallback {
    gcroot<T^> m_Managed;

    virtual void Invoke()
    {
       m_Managed->Invoke();
    }

    virtual void Release()
    {
        delete this;
    }
public:
    Callback(T^ p_Managed) : m_Managed(p_Managed) {}
};

__declspec( dllexport ) ICallback* CreateCallback()
{
    auto t_Managed = gcnew SomeManagedType();
    return new Callback<System::Action^>(
        gcnew System::Action(t_Managed, &SomeManagedType::Method)); 
}

Your native apps call CreateCallback, recieve an instance of ICallback which when Invoke-d calls a method of managed type, held in gcroot<System::Action^>...