1

I've got problems passing a member function of a C++ CLI class to a native C callback from a library.

To be precise its the Teamspeak 3 SDK.

You can pass a non member function using the following code without problem:

struct ClientUIFunctions funcs;

/* Initialize all callbacks with NULL */
memset(&funcs, 0, sizeof(struct ClientUIFunctions));
funcs.onConnectStatusChangeEvent        = onConnectStatusChangeEvent;

But I need to pass a pointer to a member function, for example:

    funcs.onConnectStatusChangeEvent        = &MyClass::onConnectStatusChangeEvent;

Any other idea how to use the event within a non static member function is welcome to.

Thanks in advance!

user1616552
  • 13
  • 1
  • 4
  • 2
    This can only be done via a static class function because C doesn't know anything about the vtable or what object the function is part of... – DipSwitch Aug 22 '12 at 10:04
  • Is there any way how I can invoke a member function from the static class function later on? I need to have access to some of the dynamic parts of the class within the events. – user1616552 Aug 22 '12 at 10:07
  • Take a look at [this other TS question](http://stackoverflow.com/questions/9475163/c-how-do-i-revive-a-thread-with-boost). The folk there use a container indexed by client ID, and index into it to get an object instance to act upon. Alternatively, you can use a singleton object and a static method. Given that the teamspeak API doesn't provide you with an opaque "context" argument, I don't see any other sensible way around the issue. – Rook Aug 22 '12 at 10:10
  • Thanks for the link, regarding your Singleton Object idea. I already created a singleton object, but was not able to call the function then. `Namespace::MyClass::getInstance()->FireEvent();` It results in a null reference exception... `static MyClass^ instance; static MyClass^ getInstance() { if (!instance) instance = gcnew MyClass(); return instance; } ` – user1616552 Aug 22 '12 at 10:12
  • 1
    You have to create a `static` wrapper, like: `static MyClass::wrapper(MyClass *thisone, Argtype args) { thisone->memfunc(args); }` and find a way to make the callback invocation pass a `MyClass*` ptr in addition to the normal args for the member. Also note that arguments at that point should be trivial (no references, and non-pointer data types only if they're primitives, i.e. no `struct`/`class`/`union`...) so that C/C++ definitely agree on them. – FrankH. Aug 22 '12 at 10:18
  • 1
    Use Marshal::GetFunctionPointerForDelegate(). No need for a static method. But do make sure to store the delegate object so it won't be garbage collected. – Hans Passant Aug 22 '12 at 12:04
  • possible duplicate of [c++/cli pass (managed) delegate to unmanaged code](http://stackoverflow.com/questions/2972452/c-cli-pass-managed-delegate-to-unmanaged-code) – Hans Passant Aug 22 '12 at 12:05

1 Answers1

1

This can only be done via a static class function because C doesn't know anything about the vtable or what object the function is part of. See below for a C++ and Managed C++ example

This could however be a work around, build a wrapper class which handles all the callbacks you need.

#include <string.h>

struct ClientUIFunctions
{
     void (*onConnectStatusChangeEvent)(void);
};

class CCallback
{
public:
     CCallback()
      {
           struct ClientUIFunctions funcs;

           // register callbacks
           my_instance = this;

           /* Initialize all callbacks with NULL */
           memset(&funcs, 0, sizeof(struct ClientUIFunctions));
           funcs.onConnectStatusChangeEvent        = sOnConnectStatusChangeEvent;

      }

     ~CCallback()
      {
           // unregister callbacks
           my_instance = NULL;
      }

     static void sOnConnectStatusChangeEvent(void)
      {
           if (my_instance)
            my_instance->OnConnectStatusChangeEvent();
      }

private:
     static CCallback *my_instance;

     void OnConnectStatusChangeEvent(void)
      {
           // real callback handler in the object
      }
};

CCallback *CCallback::my_instance = NULL;

int main(int argc, char **argv)
{
    CCallback *obj = new CCallback();

    while (1)
    {
         // do other stuff
    }

    return 0;
}

Another possibility would be if the callback supports and void *args like void (*onConnectStatusChangeEvent)(void *args); which you can set from the plugin. You could set the object in this args space so in de sOnConnectStatusChangeEvent you would have something like this:

static void sOnConnectStatusChangeEvent(void *args)
    {
           if (args)
              args->OnConnectStatusChangeEvent();
    }

For managed C++ it should be something like this, however I can't get it to compile because it doesn't like the template brackets..

wrapper.h:

using namespace std;
using namespace System;
using namespace System::Runtime::InteropServices;
using namespace System::Text;

namespace Test
{
    struct ClientUIFunctions
    {
        void (*onConnectStatusChangeEvent)(void);
    };

    public delegate void ConnectStatusChangeEvent(void);

    public ref class ManagedObject
    {
    public:
        // constructors
        ManagedObject();

        // destructor
        ~ManagedObject();

        //finalizer
        !ManagedObject();

        event ConnectStatusChangeEvent^ OnConnectStatusChangeEvent {
            void add(ConnectStatusChangeEvent^ callback) {
                m_connectStatusChanged = static_cast<ConnectStatusChangeEvent^> (Delegate::Combine(m_connectStatusChanged, callback));
            }

            void remove(ConnectStatusChangeEvent^ callback) {
                m_connectStatusChanged = static_cast<ConnectStatusChangeEvent^> (Delegate::Remove(m_connectStatusChanged, callback));
            }

            void raise(void) {
                if (m_connectStatusChanged != nullptr) {
                    m_connectStatusChanged->Invoke();
                }
            }
        }

    private:
        ConnectStatusChangeEvent^ m_connectStatusChanged;   
    };

    class CCallback
    {
    public:
        static void Initialize(ManagedObject^ obj);
        static void DeInitialize(void);

    private:
        static void sOnConnectStatusChangeEvent(void);

        static gcroot<ManagedObject^> m_objManagedObject;
    };
}

wrapper.cpp:

#include <string.h>
#include "wrapper.h"

using namespace System;
using namespace Test;

void CCallback::Initialize(ManagedObject^ obj)
{
    struct ClientUIFunctions funcs;

    // register callbacks
    m_objManagedObject = obj;

    /* Initialize all callbacks with NULL */
    memset(&funcs, 0, sizeof(struct ClientUIFunctions));
    funcs.onConnectStatusChangeEvent        = sOnConnectStatusChangeEvent;

}

void CCallback::DeInitialize(void)
{
    // unregister callbacks
    m_objManagedObject = nullptr;
}

void CCallback::sOnConnectStatusChangeEvent(void)
{
    if (m_objManagedObject != nullptr)
        m_objManagedObject->OnConnectStatusChangeEvent();
}


// constructors
ManagedObject::ManagedObject()
{
    // you can't place the constructor in the header but just for the idea..
    // create wrapper
    CCallback::Initialize(this);          
}

// destructor
ManagedObject::~ManagedObject()
{
    this->!ManagedObject();
}

//finalizer
ManagedObject::!ManagedObject()
{
    CCallback::DeInitialize();        
}

gcroot<ManagedObject^> CCallback::m_objManagedObject = nullptr;

int main(array<System::String ^> ^args)
{
     ManagedObject^ bla = gcnew ManagedObject();

     while (1)
     {
      // do stuff
     }

     return 0;
}
DipSwitch
  • 5,470
  • 2
  • 20
  • 24
  • Ok that sounds good, I added it to my code, but there is one problem: `MyClass::initTeamspeak() { CCallback *obj = new CCallback(); //Now comes the problem, the initClientLib needs the funcs pointer to proceed. if((error = ts3client_initClientLib(&funcs, NULL, LogType_USERLOGGING, NULL, "")) != ERROR_ok) { char* errormsg; [...] }` So the only way would be if the CCallback wraps around each and every function of the SDK? In my C++ CLI Code i could then use the CCallback like so? `CCallback^ obj = new CCallback(); obj->OnConnectStatusChangeEvent += gcnew EventHandler(...); ` – user1616552 Aug 22 '12 at 10:51
  • I'm rewriting it for managed C++ but my managed C++ is a bit rusty, only done this once hehe... – DipSwitch Aug 22 '12 at 11:09
  • @user1616552 I edited the post with an managed C++ example... however when I try to compile it it fails on the `static gcroot m_objManagedObject;` declaration, maybe I'm overseeing something, because this should work and works in another project which I copied it from... =) – DipSwitch Aug 22 '12 at 12:11
  • I think there is the `#include ` missing. Still some errors, but I have a look if I can manage to solve them. But you can try on your system if the include helps, too. Thanks for your committed help so far! – user1616552 Aug 22 '12 at 12:32
  • I was able to get the errors solved, but one problem still persists: `struct ClientUIFunctions funcs; m_objManagedObject = obj; memset(&funcs, 0, sizeof(struct ClientUIFunctions)); funcs.onConnectStatusChangeEvent = sOnConnectStatusChangeEvent; if((error = ts3client_initClientLib(funz, NULL, LogType_USERLOGGING, NULL, "")) != ERROR_ok) { [...] ` Error C2664 'ts3client_initClientLib': Cannot convert paramter 1 from 'const Test::ClientUIFunctions *' to 'const ClientUIFunctions *'` This one is defined in the clientlib.h of the TeamspeakSDK Ok, I think the redefiniton is not needed – user1616552 Aug 22 '12 at 13:18
  • No this one must be removed indeed, this was just so that the source was more complete :) – DipSwitch Aug 22 '12 at 13:36