Как я могу выставить указатели функций C ++ в C? - PullRequest
5 голосов
/ 08 апреля 2020

У меня есть два типа указателей на функции, определенные в моем C ++, которые выглядят так:

typedef void(*CallbackFn)(bool, std::string, py::array_t<uint8_t>&);
typedef std::function<void(std::string)> LogFunction;
Class Core{
...
void myfunc1(LogFunction lg1, CallbackFn callback, int x, std::string y);
};

, и я хочу иметь возможность выставить их в C, но я не могу найти способ сделать это. Моя первая попытка состояла в том, чтобы привести их как void*, а затем вернуть их обратно к их фактическому типу. но это кажется плохой идеей. Так что я не знаю, как go узнать об этом преобразовании.
Также решение, которое мне нужно найти, должно быть выполнимо, по крайней мере, на C ++ 11.

Обновление:

Большое спасибо за ваши ответы. Тем не менее мне нужно добавить немного больше объяснений, как то, что я после. Я знаю о extern "C", и на самом деле функции C++ представлены с использованием этого уже в моем DLL. Однако проблема, с которой я столкнулся, состояла в том, чтобы передать указатели на функции туда и обратно между C и C ++.
Один из способов состоял в том, чтобы определить указатели на функции так, чтобы они могли быть непосредственно использованы C. То есть мне нужно было изменить, например:

typedef void(*CallbackFn)(bool, std::string, py::array_t<uint8_t>&);
typedef std::function<void(std::string)> LogFunction;

на C совместимый:

typedef void(*CCallbackFn)(bool, char*, int, unsigned char, int length);
typedef void(*CLogFunction)(char* string, int length);

и использовать их вместо этого. Тем не менее, недостатком этого является то, что DLL также используется клиентами C ++, и это будет помехой для того, чтобы все C ++ было совместимо с C, и я потерял бы преимущества C ++, делая это.
Вместо этого я решил придумать второй путь. C ++ остается прежним, но для связи C и взаимодействия с другими языками через C API я выполняю преобразование самостоятельно.
То есть они используют стиль C, а затем я преобразую это обратно в C ++ в часть реализации. Чтобы еще больше упростить это, я разработал некоторые настройки по умолчанию для части C ++. То есть, предположим, из-за отсутствия лучшего примера, экземпляру нужна функция обратного вызова, чтобы регистрировать все, что происходит. Я определяю функцию обратного вызова в случае, если она не была предоставлена ​​пользователем, и создаю две функции для C API, в частности что-то примерно похожее на это:

//in core.cpp for example
include "Core.h"
...

extern "C"
{
 Core * core;
 ...

 Core_API void* get_default_log_callback()
 {
   return (void*) core->SomeDefaultCallback();  
 } 

 Core_API void* set_log_callback(void* fn)
 {
    // convert/cast that to the c++ callback type
    // CallbackFn, 
     core->SetCallback(fn_converted);  
 }

, и клиент может, например, использовать get_default_log_callback и использовать его возвращение к set_log_call_back. По сути, идея заключается в том, чтобы использовать уже определенные ресурсы C ++. Я застрял в этом процессе преобразования, как преобразовать такие указатели обратного вызова в C совместимый тип (например, как я показал, было бы очень просто просто привести указатель к void *, например, и написать оболочку C который принимает void * и затем преобразовывает его в правильный тип.

Я хотел бы также знать об этом сценарии и о том, является ли это хорошей практикой или иным образом плохой.

Второй вопрос:

Также я хотел бы знать, возможно ли преобразование, например, из CCallbackFn и CallbackFn?
Предположим, у меня есть функция (мой * 1053). * функция выше, например) в форме CCalbackFn, но в конечном итоге я хочу получить ее в форме CallbackFn (изменить ее и вызвать базовый C ++, который принимает CallbackFn)? это возможно?

Ответы [ 4 ]

6 голосов
/ 08 апреля 2020

C не выполняет / не может обрабатывать искажение имен в C ++ (и в типах C ++, которые не идентичны типам C). Вы не можете использовать не-POD-типы (и простые указатели на функции, включающие типы, которые нельзя использовать в C) во всем, что подвергается C. И вам нужно использовать extern "C" для разоблаченного материала, чтобы отключить искажение имен (или, скорее, использовать любое соглашение об именах / искажение ваших текущих платформ C, которое использует компилятор).

Короче: используйте extern "C" для всего, что должно быть вызвано из C и , убедитесь, что все, что выставлено таким образом, использует только те типы, которые вы можете писать / использовать в C.

4 голосов
/ 08 апреля 2020

Вы можете выставить функцию для C, объявив ее extern "C".

Однако функция должна принимать только те типы аргументов, которые действительны в C.

Из приведенного выше кода вам придется express ваш обратный вызов в больше C -подобных терминов.

0 голосов
/ 15 апреля 2020

В конечном итоге я придумал свое собственное решение, которое я сам называю подходом «делегирование обратных вызовов»! Идея в том, что вместо прямого обратного вызова C вы создаете переадресацию, вы создаете промежуточный обратный вызов, который действует как переводчик между двумя API. Например, предположим, что у моего класса C ++ есть метод, который принимает только обратные вызовы с этой сигнатурой:

typedef void(*CallbackFn)(bool, std::string, py::array_t<uint8_t>&);

И теперь мы хотим выставить это значение C. и это наша C подпись обратного вызова:

typedef void(*CCallbackFn)(bool, const char*, unsigned char*, int rows, int cols);

Теперь, как мы можем go с первого на второй или наоборот? Мы создаем новый обратный вызов в нашем классе C ++ типа CallbackFn и внутри него выполняем обратные вызовы C. Таким образом, используя косвенный вызов, мы можем легко отделить сигнатуры между C и C ++ API и использовать те из них, которые наиболее подходят для каждого.

Чтобы сделать его более конкретным, нам нужно что-то вроде этого:

CORE_API void Core::DelegateCCallback(bool status, std::string id, py::array_t<uint8_t>& img)
{
    //here is used a std::map to store my c-callbacks you can use
    //vector or anything else that you like
    for (auto item: this->callbackMap_c)
    {
        //item.first is our callback, so use it like a function 
        item.first(status, id.c_str(), img.mutable_data(), img.shape(0), img.shape(1));
    }
}

И вы обновите свой список обратных вызовов C следующим образом, используя две открытые функции: Добавить и Удалить, чтобы добавить и удалите все обратные вызовы соответственно:

extern "C"
{
//Core is our C++ class for example
Core* core = nullptr;
...
    CORE_API void AddCallback(CCallbackFn callback)
    {
        core->AddCallback_C(callback);
    }

    CORE_API void RemoveCallback(CCallbackFn callback)
    {
        core->RemoveCallback_C(callback);
    }
}

и обратно в нашем классе C ++, методы AddCallback_C определены как:

CORE_API void Core::AddCallback_C(CCallbackFn callback)
{
    auto x = this->callbackMap_c.emplace(callback, typeid(callback).name());
}

CORE_API void Core::RemoveCallback_C(CCallbackFn callback)
{
    this->callbackMap_c.erase(callback);
}

Просто добавление / удаление обратного вызова в список обратного вызова. Это все. Теперь, когда мы создаем наш код C ++, нам нужно добавить DelegateCCallback в список обратных вызовов, поэтому, когда все обратные вызовы C ++ выполняются, этот тоже выполняется, и с ним он будет l oop через все обратные вызовы C и выполняет их один за другим.

Например, в моем случае обратные вызовы нужно было запускать в модуле Python, поэтому в моем конструкторе я должен был сделать что-то вроде этого:


CORE_API Core::Core(LogFunction logInfo)
{
    //....
    // add our 'Callback delegate' to the list of callbacks
    // that would run.  
    callbackPyList.attr("append")(py::cpp_function([this](bool status, std::string id, py::array_t<uint8_t>& img)
                                                         {
                                                            this->DelegateCCallback(status, id, img);
                                                         }));

//...
}

Вы можете получить фантазию с этим и включите темы, et c, как вы будете sh.

0 голосов
/ 08 апреля 2020

Чтобы представить любые функции C ++ для C, вы должны заключить вызовы C ++ в функции C в простую библиотеку C ++. И экспортируйте из него только функции C. Используйте общий заголовок для C объявлений функций внутри и снаружи библиотеки. Эти функции будут вызываться из любой среды C. Все типы C ++ заключают в класс и передают указатель на этот класс через обертки функций, как дескриптор среды C ++. Указатель на класс должен быть void * или просто long. И только на стороне C ++ вы будете интерпретировать его для собственного класса среды. Обновление 1:

  1. Вы должны держать C и C ++ разделенными. Это означает, что не следует выполнять преобразования между C и C ++. Сохраняйте отдельные C версии и C ++ версии функций XX_log_callback. Например, ваши функции C ++ используют std :: string, py :: array_t &. Вы не можете использовать это C. Нет конвертации, и нет возможности воспользоваться ее преимуществами в C. Вы можете использовать преимущества C ++ только в C ++, поэтому создайте отдельную версию только для C ++ и одну доступную для C разработчиков.

  2. Это кстати. Существует методика передачи интерфейсов C ++ в C и обратно в C ++. Но будьте внимательны, он использует только C совместимые типы возврата и аргумента. Это означает создание структуры с указателем на таблицу указателей функций. В C ++ это интерфейс, но в C это структура. Эта техника используется в COM / OLE2 в Windows. https://www.codeproject.com/Articles/13601/COM-in-plain-C Чтобы использовать такую ​​технику, вы должны очень хорошо понимать, как сделать класс C ++ совместимым со структурой C.

Теперь я просто скопирую / вставить некоторые фрагменты кода из проекта кода с небольшими объяснениями. Основное правило при передаче интерфейсов между C и C ++, использовать только типы, совместимые с C, в качестве аргументов функции и в качестве возвращаемого типа. Первые четыре байта в интерфейсе - это указатель на массив функций, называемый виртуальной таблицей:

typedef struct
{
   IExampleVtbl * lpVtbl;//<-- here is the pointer to virtual table
   DWORD          count;//<-- here the current class data starts
   char           buffer[80];
} IExample;

Здесь вы добавляете указатели на функции в виртуальной таблице. IExampleVtbl - это структура, заполненная указателями, а двоичная - это эквивалент непрерывного массива указателей

static const IExampleVtbl IExample_Vtbl = {SetString, GetString};
IExample * example;

// Allocate the class
example = (IExample *)malloc(sizeof(IExample));

example->lpVtbl = &IExample_Vtbl;//<-- here you pass the pointer to virtual functions
example->count = 1; //<-- initialize class members
example->buffer[0] = 0;

Теперь вот как вы вызываете методы:

char buffer[80];
example->lpVtbl->SetString(example, "Some text");
example->lpVtbl->GetString(example, buffer, sizeof(buffer));

Имейте в виду, все вышеперечисленное C. В приведенном выше примере вы явно ссылаетесь на член виртуальной таблицы, а также явно передаете его в качестве первого параметра в функции. C ++ эквивалент вызова GetString / SetString:

example->SetString("Some text");
example->GetString(buffer, sizeof(buffer));

Вот функции SetString / GetStrinf и структура виртуальной таблицы:

HRESULT STDMETHODCALLTYPE SetString(IExample *this, char * str)
{
   memcpy(this->buffer, str, length);//be attentive, it is almost pseudocode
   return(0);
}
HRESULT STDMETHODCALLTYPE GetString(IExample *this, char *buffer, int buffer_len)
{
   memcpy(str, this->buffer, length);//be attentive, it is almost pseudocode
   return(0);
}
typedef struct {
   SetStringPtr       *SetString;
   GetStringPtr       *GetString;
} IExampleVtbl;

STDMETHODCALLTYPE должен сделать его совместимым с Вызов C ++ классов функций-членов, поэтому вы сможете передавать IExample между C и C ++. Я считаю, что это будет настоящий кошмар для C программистов, но нелегкая задача для коллег на C ++. Чтобы получить доступ к этому, когда интерфейс передается из C, вы объявляете интерфейс следующим образом:

class IExample
{
public:
   virtual HRESULT  SetString(char * str) = 0;//<-- see first parameter gone away in both functions
   virtual HRESULT GetString(char *buffer, int buffer_len) = 0;
};

Если вы реализуете в C ++ для передачи C, то эквивалент кода выше будет:

class IExample
{
    int count = 1; //<-- initialize class members
    char buffer[80] = "";
public:
   virtual HRESULT  SetString(char * str)
   {
      memcpy(this->buffer, str, length);//be attentive, it is almost pseudocode
      return(0);
   }
   virtual HRESULT GetString(char *buffer, int buffer_len)
   {
      memcpy(str, this->buffer, length);//be attentive, it is almost pseudocode
      return(0);
   }
};

Еще одна вещь. Вы не используете объявление C в C ++ и наоборот. Это с помощью подхода COM для решения проблемы. Он может быть не переносимым на разные компиляторы, но имейте в виду, что аналогичный подход используется в старом CORBA. Только ты должен иметь в виду. Вы создаете один интерфейс для C и один для C ++. В части C ++ скрыть интерфейс C, а в C скрыть интерфейс C ++. Обходите только указатели.

...