Оболочка класса C ++ / CLI для библиотеки c - обратные вызовы - PullRequest
7 голосов
/ 13 февраля 2011

Я упаковываю библиотеку C, используя C ++ / CLI. Библиотека C была разработана для использования из неуправляемого класса C ++. Это означает, что библиотечные функции принимают указатель на объект C ++ и затем возвращают этот указатель в обратных вызовах. Это позволяет коду обратного вызова перенаправлять запросы к соответствующей функции события в вызывающем объекте C ++.

Фактические функции довольно сложны, поэтому я упростил проблемное пространство до нескольких основных элементов:

// C library function signature
void CLibFunc(CLIBCALLBACK *callback, void *caller);

// C callback signature
// Second parameter is meant to point to the calling C++ object
typedef int (__stdcall CLIBCALLBACK) (int param1, void *caller);

// C callback implementation
int CallBackImpl(int param1, void* caller)
{
    // Need to call the ManagedCaller's EventFunction from here
    // ???
}

// C++/CLI caller class
public ref class ManagedCaller
{
    public:
        void CallerFunction(void)
        {
            // Call the C library function
            // Need to pass some kind of this class pointer that refers to this object
            CLibFunc(CallBackImpl, ????);
        }

        void EventFunction(param1)
        {
        }
}

Теперь функции библиотеки C необходимо вызывать из управляемого класса C ++. В C ++ / CLI сборщик мусора перемещает объекты в памяти, поэтому передача простого фиксированного указателя на класс больше не работает. Я могу решить проблему, закрепив объект, но это не рекомендуется, поскольку это приводит к фрагментации памяти. Кажется, что другим вариантом было бы использовать указатели auto_gcroot, но я довольно плохо знаком с управляемым C ++ и не уверен, как заставить это работать.

Кто-нибудь знает, как заставить это работать? Какой указатель должен быть передан в функцию C? Как реализация обратного вызова должна перенаправить функцию события вызывающего объекта?

1 Ответ

3 голосов
/ 13 февраля 2011

Похоже, это похоже на то, над чем я сейчас работаю.

Вот запись в блоге о предоставлении собственных обратных вызовов с использованием классов C ++: http://blogs.microsoft.co.il/blogs/alon/archive/2007/05/29/Native-Callback.aspx

Я не знаком с вызовом функций-членов C ++ из C, но я создал интерфейс (абстрактный базовый класс) для другого класса C ++ для обратных вызовов (аналогично статье) Вот основной пример того, для чего я предоставляю мост:

// Interface (abstract base) class providing the callback
class IProvider {
public:
    virtual ~IProvider() {}
    virtual void Callback() = 0;
};

// User class of the callback
class CUser {   
    IProvider * m_pProvider;
public:
    CUser(IProvider * pProvider) {
        m_pProvider = pProvider;
    }
    void DoSomething() {
        m_pProvider->Callback();
    }
};

// Implementation of the interface class
class CHelloWorldProvider : public IProvider {
    void Callback() {
        printf("Hello World!");
    }
};

// Usage of the callback provider in a pure native setting
void PureNativeUsage() {
    CHelloWorldProvider oProvider;
    CUser oUser(&oProvider);
    oUser.DoSomething();
}

Теперь, чтобы сделать это доступным для управляемых реализаций провайдера, нам нужно создать серию классов, обеспечивающих мост.

// Where gcroot is defined
#include <vcclr.h>

// Managed provider interface class 
public interface class IManagedProvider {
    void Callback();
};

// Native bridge class that can be passed to the user
class CProviderBridge : public IProvider {
    // Give the managed class full access
    friend ref class ManagedProviderBase;

    // Store a reference to the managed object for callback redirects
    gcroot<IManagedProvider ^> m_rManaged;

public:
    void Callback(){
        m_rManaged->Callback();
    }
};

// Managed provider base class, this provides a managed base class for extending
public ref class ManagedProviderBase abstract : public IManagedProvider {
    // Pointer to the native bridge object
    CProviderBridge * m_pNative;

protected:
    ManagedProviderBase() {
        // Create the native bridge object and set the managed reference
        m_pNative = new CProviderBridge();
        m_pNative->m_rManaged = this;
    }

public:
    ~ManagedProviderBase() {
        delete m_pNative;
    }

    // Returns a pointer to the native provider object
    IProvider * GetProvider() {
        return m_pNative;
    }

    // Makes the deriving class implement the function
    virtual void Callback() = 0;
};

// Pure managed provider implementation (this could also be declared in another library and/or in C#/VB.net)
public ref class ManagedHelloWorldProvider : public ManagedProviderBase {
public:
    virtual void Callback() override {
        Console::Write("Hello World");
    }
};

// Usage of the managed provider from the native user
void MixedUsage() {
    ManagedHelloWorldProvider ^ rManagedProvider = gcnew ManagedHelloWorldProvider;
    CUser oUser(rManagedProvider->GetProvider());
    oUser.DoSomething();
}

Редактировать: Добавлен код, показывающий без используемого примера класса управляемого интерфейса.

Вот модифицированная версия моего примера, которую можно использовать, учитывая ваш CLibFunc выше. Это предполагает, что функция C выполняет обратный вызов, является точной.

Также это может быть немного уменьшено в зависимости от того, насколько вовлечены ваши классы обратного вызова и сколько свободы для расширения вам нужно.

// Where gcroot is defined
#include <vcclr.h>

// C callback signature
// Second parameter is meant to point to the calling C++ object
typedef int (__stdcall CLIBCALLBACK) (int param1, void *caller);

// C library function
void CLibFunc(CLIBCALLBACK *callback, void *caller) {
    // Do some work
    (*callback)(1234, caller);
    // Do more work
}

// Managed caller interface class 
public interface class IManagedCaller {
    void EventFunction(int param1);
};

// C++ native bridge struct
struct CCallerBridge {
    // Give the managed class full access
    friend ref class ManagedCaller;

    // Store a reference to the managed object for callback redirects
    gcroot<IManagedCaller ^> m_rManaged;

public:
    // Cast the caller to the native bridge and call managed event function
    // Note: This must be __stdcall to prevent function call stack corruption
    static int __stdcall CallBackImpl(int param1, void * caller) {
        CCallerBridge * pCaller = (CCallerBridge *) caller;
        pCaller->m_rManaged->EventFunction(param1);
        return 0;
    }
};

// C++/CLI caller class
public ref class ManagedCaller : public IManagedCaller {
    // Pointer to the native bridge object
    CCallerBridge * m_pNative;

public:
    ManagedCaller() {
        // Create the native bridge object and set the managed reference
        m_pNative = new CCallerBridge();
        m_pNative->m_rManaged = this;
    }
    ~ManagedCaller() {
        delete m_pNative;
    }

    // Calls the C library function
    void CallerFunction() {
        CLibFunc(CCallerBridge::CallBackImpl, m_pNative);
    }

    // Managed callback function
    virtual void EventFunction(int param1) {
        Console::WriteLine(param1);
    }
};

// Usage
int main(array<System::String ^> ^args) {
    ManagedCaller ^ oCaller = gcnew ManagedCaller();
    oCaller->CallerFunction();
    return 0;
}
...