Дилема относно използването на указатели към членски функции

Трябва да задам указател на функция в 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
Библиотеката за повишаване предоставя само ограничена помощ в този конкретен случай. Отговорите, които го споменават, тълкуват погрешно въпроса ви. И частите, които могат да помогнат по някакъв начин, така или иначе са част от 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 не знае как да се справи с този указател. Ако не можете да промените библиотеката, някак сте блокирани.

Ако API предоставя метод за предаване на непрозрачен указател на „контекст“ през библиотеката (както се прави с неща като CreateThread в Windows, където параметърът се третира от операционната система просто като номер с размер на указател, който се предава), тогава трябва да използвайте статична функция член и използвайте този контекстен параметър, за да прехвърлите този указател. В статичната членска функция преобразувайте контекстния параметър обратно към указател и извикайте членската функция през него. Ако библиотеката може да бъде променена от вас или можете да накарате някой да я промени и тя не предлага тази функционалност, трябва да я добавите, защото това е най-добрият начин за свързване на обекти и C.

За съжаление вашият API изглежда не предоставя начин да направите това. Ако приложението ви е с една нишка и можете да гарантирате, че няма повторно влизане или множество потребители на библиотеката едновременно, тогава може да успеете да се измъкнете със съхраняването на този указател в глобален или клас статичен член, тогава можете да направите това и отново използвайте статична член-функция като обратно извикване и извикване чрез параметъра.

Трети подход и абсолютна последна мярка може да бъде създаването на "thunk" обекти, които са малки обекти, които всъщност изграждат изпълнимия код за конфигуриране на този указател и прескачане към правилната членска функция. В съвременните системи с 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

Най-простият подход би бил да се изработят няколко функции ("извикващ" и "задаващ"), които задават стойността на глобална променлива. Бихте използвали сетера, за да зададете стойността на глобала в такъв момент от вашата програма, така че обектът, на който искате да извикате членски функции, да е конструиран. Извикващият ще бъде предаден на setSendFunctor и при извикване ще пренасочи извикването към стойността на глобалната променлива.

Умишлено не съм говорил за типа на глобалното по-горе; това може да бъде каквото искате да бъде (обикновен стар указател на функция, указател на членска функция, функтор от Boost или друга библиотека, или нещо друго, което може да е удобно).

За съжаление не мисля, че има начин да направите това без глобален, тъй като C API е доста ограничаващ.

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

Ако всичко останало се провали и имате случай, в който вашата библиотека има само способността да вземе функционален указател без дефинирани от клиента данни и искате да получите обратни извиквания на множество обекти и жизнените цикли на обратното извикване се припокриват, тогава можете да използвате трамплин .

Начинът, по който обикновено правя това (всъщност ми се е налагало да го правя само няколко пъти, така че не е достатъчно обичайно да го правя „нормално“, тъй като повечето библиотеки са достатъчно нормални, за да предадат някои празни* потребителски данни когато зададете обратно извикване) е да компилирате функция с твърдо кодиран указател в нея:

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

След това можете да проверите машинния код за тази функция (в Windows, като влезете в нея и получите стойността на указателя на функцията, след което проверите паметта на това място). Обикновено след това имате няколко дузини байта, в които местоположението на твърдо кодирания указател е очевидно. Променете стойността на показалеца и ги разграничете, ако не е. Така че извикването на функция член на даден обект ще има същия машинен код, но байтовете за твърдо кодирания указател ще бъдат заменени с байтовете за указателя към обекта приемник.

За да създадете батути по време на изпълнение с указател към thisclass обект, вземете малко записваема изпълнима памет от операционната система (обикновено получавате това на парчета, така че създайте мениджърски обект за трамплините, така че да не използвате 4K за всяка 20-байтова функция ), копирайте тези байтове в него, като замените байтовете за твърдо кодирания указател с указателя на обекта. Вече имате указател към безплатна функция, която можете да извикате, която ще извика членска функция на конкретен обект.

person Pete Kirkham    schedule 20.11.2010

Не знам точно за какво се използват char* и int. Ако те са генерирани от C API, тогава няма какво да направите, тъй като няма начин да предадете каквато и да е "контекстна" информация.

Ако едно от тях се отнася до нещо, което сте предали, и се извиква обратно към вас, тогава можете да ги картографирате обратно към вашия оригинален „this“ или структура, съдържаща „this“ плюс някакъв друг char* или int.

person CashCow    schedule 20.11.2010