Доброго времени суток!
Было дело — делал я интерфейс для работы с модулями для USB от FTDI. Пришлось изрядно повозиться с подключением DLL-интерфейса. Разочаровавшись в возможностях автоматической линковки Microsoft Visual Studio 2008 (UPD: потом я разобрался с этой темой), я решил это делать вручную. По ходу делазадолбался очень надоело вручную подключать несколько десятков функций. И тогда я обратился к Google, C++ и шаблонам. И если подключение DLL в стиле C++ вопросов не вызвало, то удобный вызов подключенных функций в стиле «Error = FT_Open (Num, &_Handler)», где FT_Open- объект, удался не сразу. Итог (для таких вот функций) — под катом. Если вкратце — я сделал обертку вокруг указателя на функцию.
Сразу оговорюсь — я работаю в Windows XP Prof, Visual Studio. Это принципиально для получения адреса функции. Впрочем, при работе с указателями на функции это не важно.
Ну так вот, для тех, кто не в теме, вот последовательность для нахождения той самой функции FT_Open из FTD2XX.dll средствами WinAPI:
Это не беда, когда функция у вас одна, но в этой самой библиотеке я насчитал 51 функцию. И для каждой мне нужно сделать следующее:
Особенно раздражает необходимость генерить кучу typedef. Да, я знаю, можно писать и без typedef, но это выглядеть будет ОМЕРЗИТЕЛЬНО!
Посему хочется как-то упростить себе жизнь:
В ходе экспериментов и кипения мозгов я получил такой вот шаблон класса:
Думаю, тут все элементарно, но все-таки для непосвященных объясню.
Создается шаблон Funct2, которому первым параметром задается тип Ret, возвращаемый функцией. Следующими двумя параметрами — Arg1 и Arg2 — задаются типы аргументов функции. С целью универсиализации обработки ошибок задается тип исключения Except и его значение Value (параметры по умолчанию задаются #define FunctPtrExceptionType и #define FunctPtrExceptionDefValue).
В теле шаблона класса задается тип tfPtr «указатель на функцию с двумя параметрами» и сам указатель fPtr.
Конструктор по умолчанию задает нулевой указатель или конкретный адрес, если он задан. Также адрес может быть задан через перегруженный operator= (void *Ptr). Почему void * — потому что GetProcAddress () возвращает именно void *. Нет нужды перегружать его сигнатурой operator= (tfPtr Ptr) — компилятор и так понимает, о чем речь.
Ну и, наконец, перегружая operator (), мы добиваемся использования класса как функтора, а для пользователя класса — так и вообще простого вызова функции.
Удобно? Очень! Смотрите:
Дизассемблер в режиме Release показал, что накладные расходы при вызове такой функции — проверка 0-го значение и в связи с этим еще один call. Я думаю, для современных PC это не беда.
Для совершенства тут можно как-то доработать тему исключений — было бы хорошо туда передавать текстовые строки, свои произвольные классы ошибок и т. п. Нолень я недостаточно хорошо знаю шаблоны, чтобы это реализовать.
Ну и, понятное дело, надо наклепать разных вариантов Funct для 3х, 4х и т. д. аргументов. Хорошо бы придумать какой-то макрос, который бы их генерил…
Ну и, еще более понятное дело, надо все это вынести в отдельный .H-файл.
Я надеюсь, кому-то сэкономил время. Буду благодарен за конструктивные комментарии!
P. S. По ходу эксплуатации вскрылась такая неприятная вещь, как соглашение о вызовах. Похоже, надо делать Funct0Stdcall, Funct0Cdecl; Funct1Stdcall, Funct1Cdecl…
Было дело — делал я интерфейс для работы с модулями для USB от FTDI. Пришлось изрядно повозиться с подключением DLL-интерфейса. Разочаровавшись в возможностях автоматической линковки Microsoft Visual Studio 2008 (UPD: потом я разобрался с этой темой), я решил это делать вручную. По ходу дела
Постановка задачи
Сразу оговорюсь — я работаю в Windows XP Prof, Visual Studio. Это принципиально для получения адреса функции. Впрочем, при работе с указателями на функции это не важно.
Ну так вот, для тех, кто не в теме, вот последовательность для нахождения той самой функции FT_Open из FTD2XX.dll средствами WinAPI:
#include "FTD2XX.h" // библиотека от FTDI typedef FT_STATUS (*pFT_Open) (int, FT_HANDLE *); // тип данных "функция FT_OPEN" // ... HMODULE hMod = LoadLibrary ("FTD2XX.dll"); // загрузка библиотеки - д. б. не ноль pFT_Open pOpen = GetProcAddress (hMod, "FT_Open"); // получили адрес функции - также д. б. не ноль // ... FT_STATUS st = pOpen (0, &hDev); // вызываем функцию // ... FreeLibrary (hMod); // закрыли библиотеку
Это не беда, когда функция у вас одна, но в этой самой библиотеке я насчитал 51 функцию. И для каждой мне нужно сделать следующее:
typedef FT_STATUS (*pFT_Open) (int, FT_HANDLE *); // тип данных "указатель на функцию" pFT_Open pOpen; // переменная "указатель на функцию" pFT_Open pOpen = GetProcAddress (hMod, "FT_Open"); // получение адреса функции "FT_Open"
Особенно раздражает необходимость генерить кучу typedef. Да, я знаю, можно писать и без typedef, но это выглядеть будет ОМЕРЗИТЕЛЬНО!
Посему хочется как-то упростить себе жизнь:
Funct2<FT_STATUS, int, FT_HANDLE *> Open; // тип данных "функция 2х аргументов" Open = GetProcAddress (hMod, "FT_Open"); // получение адреса функции "FT_Open" // ... FT_STATUS st = Open (0, &hDev); // вызов функции
Решение
В ходе экспериментов и кипения мозгов я получил такой вот шаблон класса:
template <typename Ret, typename Arg1, typename Arg2, typename Except = FunctPtrExceptionType, Except Value = FunctPtrExceptionDefValue> class Funct2 { public: typedef Ret (*tfPtr) (Arg1, Arg2); tfPtr fPtr; public: Funct2 (tfPtr Ptr = 0): fPtr (Ptr) {} Funct2 &operator= (void *Ptr) { fPtr = reinterpret_cast<tfPtr> (Ptr); return *this; } Ret operator () (Arg1 A1, Arg2 A2) throw (Except) { if (!fPtr) throw Except (Value); return fPtr (A1, A2); } }; // class Funct2
Думаю, тут все элементарно, но все-таки для непосвященных объясню.
Создается шаблон Funct2, которому первым параметром задается тип Ret, возвращаемый функцией. Следующими двумя параметрами — Arg1 и Arg2 — задаются типы аргументов функции. С целью универсиализации обработки ошибок задается тип исключения Except и его значение Value (параметры по умолчанию задаются #define FunctPtrExceptionType и #define FunctPtrExceptionDefValue).
В теле шаблона класса задается тип tfPtr «указатель на функцию с двумя параметрами» и сам указатель fPtr.
Конструктор по умолчанию задает нулевой указатель или конкретный адрес, если он задан. Также адрес может быть задан через перегруженный operator= (void *Ptr). Почему void * — потому что GetProcAddress () возвращает именно void *. Нет нужды перегружать его сигнатурой operator= (tfPtr Ptr) — компилятор и так понимает, о чем речь.
Ну и, наконец, перегружая operator (), мы добиваемся использования класса как функтора, а для пользователя класса — так и вообще простого вызова функции.
Удобно? Очень! Смотрите:
Результат
#include < Windows.h > // для GetProcAdress #include < stdio.h > // для printf // ** // ** Настройка исключений по умолчанию // ** #define FunctPtrExceptionType int // тип данных для исключения по умолчанию #define FunctPtrExceptionDefValue 0 // значение исключения по умолчанию // ** // ** Указатель на функцию без аргументов // ** template < typename Ret = void, typename Except = FunctPtrExceptionType, Except Value = FunctPtrExceptionDefValue > class Funct0 { public: typedef Ret (*tfPtr) (void); tfPtr fPtr; public: Funct0 (tfPtr Ptr = 0): fPtr (Ptr) {} Funct0 &operator= (tfPtr Ptr) { fPtr = Ptr; return this; } Ret operator () (void) throw (Except) { if (!fPtr) throw Except (Value); return fPtr (); } }; // ** // ** Указатель на функцию с 1 аргументом // ** template < typename Ret, typename Arg1, typename Except = FunctPtrExceptionType, Except Value = FunctPtrExceptionDefValue > class Funct1 { public: typedef Ret (*tfPtr) (Arg1); tfPtr fPtr; public: Funct1 (tfPtr Ptr = 0): fPtr (Ptr) {} Funct1 &operator= (void *Ptr) { fPtr = reinterpret_cast<tfPtr> (Ptr); return *this; } Ret operator () (Arg1 A1) throw (Except) { if (!fPtr) throw Except (Value); return fPtr (A1); } }; // ** // ** Указатель на функцию с 2 аргументами // ** template < typename Ret, typename Arg1, typename Arg2, typename Except = FunctPtrExceptionType, Except Value = FunctPtrExceptionDefValue > class Funct2 { public: typedef Ret (*tfPtr) (Arg1, Arg2); tfPtr fPtr; public: Funct2 (tfPtr Ptr = 0): fPtr (Ptr) {} Funct2 &operator= (void *Ptr) { fPtr = reinterpret_cast<tfPtr> (Ptr); return *this; } Ret operator () (Arg1 A1, Arg2 A2) throw (Except) { if (!fPtr) throw Except (Value); return fPtr (A1, A2); } }; // ** // ** Примеры вызова функций // ** int add (const int A, const int *B) { int C; C = A + *B; printf (" int add (const int %d, const int %d) = %d\n", A, *B, C); return C; } void prn (void) { printf (" void prn (void)\n"); } // ** // ** Точка входа // ** void main (void) { int i, i2; double d; long l; Funct0<> prner (prn); Funct1< double, long *, int, 2 > longer; Funct2< int, const int, const int * > adder; adder = add; longer = GetProcAddress (0, "Longer"); try { prner (); i2 = 6; i = adder (5, &i2); d = longer (&l); } catch (int val) { switch (val) { case 2: printf (" *** не удалось определить адрес функции!\n"); break; default: printf (" *** ошибка вызова функции!\n"); break; } }
Итог
Дизассемблер в режиме Release показал, что накладные расходы при вызове такой функции — проверка 0-го значение и в связи с этим еще один call. Я думаю, для современных PC это не беда.
Для совершенства тут можно как-то доработать тему исключений — было бы хорошо туда передавать текстовые строки, свои произвольные классы ошибок и т. п. Но
Ну и, понятное дело, надо наклепать разных вариантов Funct для 3х, 4х и т. д. аргументов. Хорошо бы придумать какой-то макрос, который бы их генерил…
Ну и, еще более понятное дело, надо все это вынести в отдельный .H-файл.
Я надеюсь, кому-то сэкономил время. Буду благодарен за конструктивные комментарии!
P. S. По ходу эксплуатации вскрылась такая неприятная вещь, как соглашение о вызовах. Похоже, надо делать Funct0Stdcall, Funct0Cdecl; Funct1Stdcall, Funct1Cdecl…
