Доброго времени суток!
Было дело — делал я интерфейс для работы с модулями для 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:
#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…