5

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.

Asik
  • 21,506
  • 6
  • 72
  • 131
  • 1
    Do favor Marshal::GetFunctionPointerForDelegate(), an example [is here](http://stackoverflow.com/questions/2972452/c-cli-pass-managed-delegate-to-unmanaged-code/2973278#2973278) – Hans Passant Mar 08 '13 at 22:52

2 Answers2

2

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.

Asik
  • 21,506
  • 6
  • 72
  • 131
0

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^>...

Paul Michalik
  • 4,331
  • 16
  • 18
  • 1
    So the issue is that the native API isn't compiled with clr support? Because otherwise what you describe is exactly what I'm doing. – Asik Mar 08 '13 at 21:43
  • The native class that uses gcroot must be compiled with clr support. What I described is exactly the opposite of what you've shown in your question: you store a native gcroot pointer in a managed object. – Paul Michalik Mar 08 '13 at 21:54
  • Well, the function where the gcroot is used ("Callback") *is* compiled with CLR support. The gcroot is stored on the unmanaged heap which is apparently allowed. What difference would it make that a managed class keep a pointer to it? – Asik Mar 08 '13 at 22:06
  • Well that is just not the intended usage of `gcroot`. The whole point of having a `gcroot` where `T` is a managed type is to use a safe RAII wrapper for handles to managed memory. Using `gcroot` allocated on the free-store is similar to `std::shared_ptr* p = new std::shared_ptr` - pointless. I'll edit my answer to demonstrate... – Paul Michalik Mar 09 '13 at 10:01
  • 1
    How is it pointless if it solves my problem? I want to pass an instance of a managed class as void* to an API that I have no control over. Creating a gcroot on the native heap and passing it as void* seems to do the trick, since when I get called back with this pointer I can cast back to gcroot and forward into the managed class. I'm not asking what is typical use, I'm asking if this will work correctly. – Asik Mar 09 '13 at 13:25