Pull to refresh

Обертка для вызова функций по их адресу

Reading time5 min
Views7.4K
Доброго времени суток!
Было дело — делал я интерфейс для работы с модулями для 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…
Tags:
Hubs:
+5
Comments20

Articles