Дилемма об использовании указателей на функции-члены

Мне нужно установить указатель функции в библиотечном 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.

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

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


person SkypeMeSM    schedule 20.11.2010    source источник
comment
Спасибо за ваши ответы. Я не могу использовать Boost или любую другую стороннюю библиотеку в этом отношении.   -  person SkypeMeSM    schedule 20.11.2010
comment
Можете ли вы изменить используемую библиотеку?   -  person Stewart    schedule 20.11.2010
comment
Библиотека boost в любом случае предоставляет ограниченную помощь в этом конкретном случае. Ответы, в которых упоминается это, неправильно прочитали ваш вопрос. И части, которые могут каким-то образом помочь, в любом случае являются частью C++ TR1, и ваш компилятор может это поддерживать. Это часть стандарта.   -  person Omnifarious    schedule 21.11.2010
comment
Всем большое спасибо.   -  person SkypeMeSM    schedule 21.11.2010


Ответы (5)


Мой обычный способ решения этой проблемы — создать статическую функцию в моем классе, которая принимает явный указатель 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. Это означает, что количество этих экземпляров будет определено во время компиляции, и это невозможно обойти.

person Omnifarious    schedule 20.11.2010

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

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

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

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

person Stewart    schedule 20.11.2010
comment
Обратите внимание, что я предполагаю, что вы не можете изменить библиотеку. Если вы можете изменить библиотеку, вы должны это сделать. - person Stewart; 20.11.2010
comment
Ваш комментарий предполагает, что вы не можете редактировать ответ. Если вы можете отредактировать ответ, вы должны это сделать. усмехается Конечно, я тоже могу отредактировать ответ, но... - person Omnifarious; 21.11.2010
comment
@Omnifarious - Хороший вопрос, хорошо сделано :). Изменить, чтобы ответить. Спасибо за напоминание. - person Stewart; 21.11.2010

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

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

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

person Jon    schedule 20.11.2010
comment
Как я указал в своем ответе, это будет безопасно только в том случае, если нет абсолютно никакой вероятности того, что API будет вызываться либо повторно, либо несколькими потоками одновременно. Если эти две вещи верны и всегда будут истинными, это хорошее решение. Если это не так, это решение вызовет у вас много проблем. - person Stewart; 20.11.2010
comment
Ничто не мешает вам реализовать любой механизм синхронизации потоков, который вы выберете в вызывающем и сеттере. - person Jon; 21.11.2010

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

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

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

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

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

person Pete Kirkham    schedule 20.11.2010

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

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

person CashCow    schedule 20.11.2010