Дилемма об использовании указателей на функции-члены - PullRequest
1 голос
/ 20 ноября 2010

Мне нужно установить указатель на функцию в API библиотеки, чтобы она вызывала эту функцию всякий раз, когда ей нужно выполнить определенное действие.

int (*send_processor)(char*,int);
int setSendFunctor(int (*process_func)(char*,int))
    {
        send_processor = process_func;
    }

В моем основном источнике эта функция определена как функция-член класса,

int thisclass::thisfunction(char* buf,int a){//code}

эта функция зависит от других функций в этом классе. Поэтому я не могу отделить его от класса.

Я не могу установить указатель на функцию-член в setSendFunctor, поскольку внутри API он назначается не указателю функции-члена, а обычному указателю функции send_processor.

Поскольку этот класс не является производным классом, я не могу использовать виртуальные функции.

Каков наилучший способ справиться с этим?

Ответы [ 5 ]

3 голосов
/ 20 ноября 2010

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

Если у API нет средства для предоставления ему void *, который затем будет возвращен вашему обратному вызову, этот API сломан и требует исправления. Если вы не можете это исправить, есть варианты, включающие глобальные (или их двоюродный брат static) переменные. Но они безобразны.

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

Решение, основанное на этой идее без использования шаблонов, будет выглядеть примерно так:

class InstanceBinder1 {
  public:
   static initialize(thisClass *instance, int (thisClass::*processor)(char *buf, int a)) {
      instance_ = instance;
      processor_ = processor;
   }

   static int processor(char *instance, int a) {
      instance_->*processor_(buf, a);
   }

  private:
   static thisClass *instance_;
   static int (thisClass::*processor_)(char *buf, int a);
};

Тогда вы могли бы передать &InstanceBinder1::processor в библиотеку C.

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

2 голосов
/ 20 ноября 2010

К сожалению, нет способа передать указатель на член в C API, как этот, потому что C api не знает, как обращаться с этим указателем.Если вы не можете изменить библиотеку, вы немного застряли.

Если API предоставляет метод для передачи непрозрачного «контекстного» указателя через библиотеку (как это делается с такими вещами, как CreateThread в Windows,где параметр обрабатывается ОС как просто число размером с указатель, которое передается), тогда вы должны использовать статическую функцию-член и использовать этот параметр контекста для передачи вашего указателя this.В статической функции-члене приведите параметр контекста обратно к указателю и вызовите функцию-член через него.Если библиотека может быть изменена вами, или вы можете заставить кого-то изменить ее, и она не предлагает эту функциональность, вам следует добавить ее, потому что это лучший способ связать объекты с C.

К сожалениюваш API не предоставляет такой возможности.Если ваше приложение является однопоточным, и вы можете гарантировать, что не будет ни повторного входа, ни нескольких пользователей библиотеки в одно и то же время, вы можете избежать сохранения указателя this в глобальном или статическом члене класса, тогда вы можете сделать это.и снова использовать статическую функцию-член в качестве обратного вызова и вызова через параметр.

Третий подход и абсолютное последнее средство может заключаться в создании «thunk» объектов, которые представляют собой небольшие объекты, которые фактически создают исполняемый файлкод для настройки указателя this и перехода к правильной функции-члену.В современных системах с DEP и прочим это сложный подход, который, скорее всего, не стоит хлопот.Библиотека ATL делает это в Windows, но это сложно и вызывает много проблем с безопасностью и обновлениями на протяжении многих лет.

1 голос
/ 21 ноября 2010

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

Способ, которым я обычно делаю это (на самом деле, мне когда-либо приходилось делать это всего пару раз, поэтому это не достаточно распространено, чтобы «нормально» это делать, так как большинство библиотек достаточно нормальны, чтобы передать какую-то пустоту). * пользовательские данные при установке обратного вызова) должны компилировать функцию с жестко заданным указателем на ней:

int send_processor_trampoline ( char* buf, int a )
{
    return ( ( thisclass* ) 0x12345678 ) -> thisfunction ( buf, a );
}

Затем вы можете проверить машинный код этой функции (в Windows зайдя в нее и получив значение указателя функции, затем проверив память в этом месте). Обычно у вас есть пара дюжин байтов, в которых местоположение жестко закодированного указателя очевидно. Измените значение указателя и измените их, если это не так. Поэтому вызов функции-члена для данного объекта будет иметь тот же машинный код, но байты для жестко закодированного указателя будут заменены байтами для указателя на объект-получатель.

Чтобы создать батуты во время выполнения с указателем на объект thisclass, извлеките некоторую доступную для записи исполняемую память из ОС (обычно это происходит кусками, поэтому создайте объект-менеджер для батутов, чтобы не использовать 4K). для каждой 20-байтовой функции) скопируйте эти байты в нее, заменив байты для жестко закодированного указателя указателем объекта. Теперь у вас есть указатель на свободную функцию, которую вы можете вызвать, которая вызовет функцию-член для определенного объекта.

1 голос
/ 20 ноября 2010

Самый простой подход - создать пару функций («вызывающий» и «установочный»), которые устанавливают значение глобальной переменной.Вы бы использовали метод set для установки значения global в такой точке вашей программы, чтобы был создан объект, для которого вы хотите вызывать функции-члены.Вызывающая сторона будет передана на setSendFunctor, и при вызове перенаправит вызов на значение глобальной переменной.

Я специально не говорил о типе глобальной выше;это может быть то, что вы хотите (простой указатель на функцию-функцию, указатель на функцию-член, функтор из Boost или другой библиотеки или что-то еще, что может быть удобным)Я думаю, что есть способ сделать это без глобального, поскольку C API довольно ограничен.

0 голосов
/ 20 ноября 2010

Я не знаю точно, для чего используются char * и int. Если они сгенерированы API C, то вы ничего не можете сделать, так как у вас нет возможности передавать какую-либо «контекстную» информацию.

Если один из них относится к чему-то, что вы передали, и вам перезванивают, то вы можете сопоставить их с вашим исходным «this» или структурой, содержащей «this» плюс некоторые другие символы * или int.

...