//Вячеслав
Ермолаев. http://bcdev.narod.ru
//Использование template-классов
при импортировании
функций из dll
void __fastcall TMainForm::OpenTaskActionExecute(TObject *Sender)
{
void (*RunDLL)(TComponent*, char*,char*);
try
{
HINSTANCE DllInstance;
AnsiString DllName = LocalBestPmExe + Data->TaskTree_NAME->AsString;
#pragma warn -pia
if (!(DllInstance = LoadLibrary(DllName.c_str())))
{
throw(Exception("Ошибка
загрузки DLL:" + DllName));
}
if(!(RunDLL = (void (*)(TComponent*, char*,char*))
GetProcAddress(DllInstance,ProcName.c_str())))
{
throw(Exception("Ошибка
получения адреса функции:"+ ProcName.c_str()));
}
RunDLL(this,UserCode.c_str(),LabelCode.c_str());
}
#pragma warn .pia
catch(Exception& excp)
{
ShowErr(mtError,&excp);
}
}
//Сколько
раз Вам приходилось писать такой или
примерно такой код загрузки
//динамической библиотеки и вызова из
нее функции? Если Вы занимались
//программированием достаточно серьезных
приложений, думаю не один раз.
//Вот и я как то в очередной раз
закончив деэлельку, стал
набивать
//в основной программе уже набивший
оскомину код загрузки этой dll и вызова
//функции из него, как перед глазами на
мгновение материализовался невесть
//откуда-то взявшийся и почему-то очень
знакомый тип в клетчатом пиджаке и
//разбитом пенсне и ехидным голосом
произнес "А сапожник то без сапог".
//Тот час же ногам стало холодно и неуютно,
запахло сыростью и серой.
//"Допрограммировался"- подумал я, и еще:
"Вот
так начинается шизофрения.
//Да и пиво вчера надо пить было поменьше. И
причем тут сапоги?".
Но
//поскольку дальнейшего продолжения не
последовало и ботинки вроде бы
//оказались на месте, я решил все же
посмотреть, чем было вызвано появление
//сей нечисти , так красочно описанной еще
Булгаковым.
//Я вгляделся в только что написанные
строчки и ужаснулся: ну
и где же
//здесь
великий и могучий ООП, к поклонникам
которого я себя так гордо отношу.
//Передо мной черным по белому
чернелась абракадабра, которая по большей
//части к конечной цели - вызову функции с
тремя параметрами - прямого
//отношения не имела.
Ну да, здесь конечно есть над чем задуматься.
//Сколько
раз я с умным видом пытался втиснуть
проблемы бухгалтеров,
//менеджеров, экономистов и т.п. в рамки
парадигмы ООП, а на себя
//времени конечно не хватало. Действительно
сапожник. Ну это не беда.
//Чем там у нас отгоняют нечистую силу?
Святым писанием? Быстренько
//хватаю Страуструпа "Язык
программирования C++" и читаю бессмертные
//формулировки правила "правой руки"(стр.16,Москва,"Радио
и связь",1991):
// "a)если вы считаете "это"
отдельным понятием, сделайте его классом;
// "б)если вы считаете "это"
отдельным объектом, сделайте его
// объектом некоторого класса".
//И всего то? Ну в данном вопросе можно
обойтись и без помощи аналитика.
//В распоряжении имеем два понятия - два
класса: динамическую библиотеку
//и импортируемую функцию. Сейчас мы их
быстренько оформим в
//классы-враперы (от Wraper или Adapter, "Приемы
объектно-ориентированного
//проектирования(паттерны проектирования)",Э.Гамма
и др.,Санкт-Петербург,
//"Питер",2001) и будем спать спокойно.
Хотя кажется нет. Спят спокойно,
//когда платят налоги. Итак
class
TDll {
HINSTANCE
m_DLLInstance;
public:
TDll(const char* a_Name) {
#pragma option push -w-pia
if (!(m_DLLInstance = LoadLibrary(a_Name)))
#pragma option pop
throw(Exception("Ошибка
загрузки библиотеки:"+AnsiString(a_Name)));
}
~TDll(){
if (m_DLLInstance) FreeLibrary(m_DLLInstance);
}
operator HINSTANCE() const
{
return m_DLLInstance; }
};
//С
динамической библиотекой я разобрался
достаточно просто. А что делать
//с импортируемой функцией с заранее
неизвестным типом возвращаемого
//значения да еще с параметрами, коих число и
типы тоже могут быть
//совершенно различны. Короче говоря, все
предложенные мною варианты мною
//самим же были отметены. Изящное решение не
никак находилось. К тому же
//меня постоянно преследовало чувство дежа-вю,
в смысле того, что я это
//уже где-то видел и незачем изобретать
велосипед. Ну да, конечно! Старый
//добрый OWL - шедевр объектно-ориентированного
программирования. Кстати,
//проект не совсем умер и поддерживается
международной группой энтузиастов.
//Ознакомиться с состоянием дел по OWL
можно на сайте www.owlnext.org.
//Итак попробуем адаптировать
борландовское решение. Для начала
определим
//базовый класс TDllProc
class
TDllProc {
public:
TDllProc(const TDll& a_Dll, const char* a_Name)
{
#pragma option push -w-pia
if(!(m_Proc = GetProcAddress(HINSTANCE(a_Dll),a_Name)))
#pragma option pop
throw(Exception("Ошибка получения
адреса функции:" + AnsiString(a_Name)));
}
protected:
FARPROC m_Proc;
};
//Далее
все предельно просто. Надо просто
определить ряд template классов,
//являющихся производными от TDllProc.
Прежде всего о соглашениях. Борланд
//предложил использовать следующие
соглашения:
//
// Имя класса для функции, которая не
возвращает результат - TDllProcVX
// Имя класса для функции, которая
возвращает результат - TDllProcX.
// где X - число параметров функции.
//
//Вот этой изюминки - кодирование возврата и
числа параметров - мне не
//хватило, чтобы решить задачу
самостоятельно. Остается только с завистью
//следить за полетом мысли профессионалов.
Итак, определяем класс-врапер
//для инкапсуляции функции типа void (*)()
class TDllProcV0 : public TDllProc {
public:
TDllProcV0(const
TDll& a_Dll, const char* a_Name)
:TDllProc(a_Dll,a_Name) {}
void operator ()()
{
typedef void (*TProc)();
((TProc)m_Proc)();
}
};
//Здесь пока
шаблоны не применяются, незачем - нет ни
параметров, ни
//возвращаемого результата. Но вот для
инкапсуляции вызова функции
//без параметров, но возвращающей результат,
потребуется уже шаблон:
template <class R>
class TDllProc0 : public TDllProc {
public:
TDllProc0(const
TDll& a_Dll, const char* a_Name)
: TDllProc(a_Dll,a_Name) {}
R operator ()()
{
typedef R (* TProc)();
return ((TProc)m_Proc)();
}
};
//Также при
определении классов для все
последующих вариантов функций
//без шаблонов не обойтись. Следующей у нас
будет функция типа без
//возврата результата с одним параметром, по
нашим соглашениям TDllProcV1
template <class P1>
class TDllProcV1 : public TDllProc {
public:
TDllProcV1(const
TDll& a_Dll, const char* a_Name)
: TDllProc(a_Dll,a_Name) {}
void operator ()(P1 p)
{
typedef void (* TProc)(P1);
((TProc)m_Proc)(p1);
}
};
//Для полной
ясности приведем еще объявление класса для
функции с
//одним параметром и с возвратом результата
- TDllProc1
template <class R, class
P1>
class TDllProc1 : public TDllProc {
public:
TDllProc1(const
TDll& a_Dll, const char* a_Name)
: TDllProc(a_Dll,a_Name) {}
R operator ()(P1 p1)
{
typedef R (* TProc)(P1 );
return ((TProc)m_Proc)(p1);
}
};
//Думаю
приведенного кода для понимания идеи
вполне достаточно. Просто
//нужно последовательно аналогичным
порядком определить классы для функций
//с возвратом и без возврата с двумя, тремя и
т.д. числом параметров. Вы
//можете остановиться на любом числе.
Программисты от Борланда выбрали
//число 13. Видно у них тоже были проблемы
с нечистой силой. Не будем
//нарушать заведенный порядок и то же
остановимся на числе 13. Последние
//два класса для функций с 13-ю параметрами
будут выглядеть следующим
//образом
template <class P1,class
P2,class P3,class P4,class P5,class P6,class
P7,
class P8,class
P9,class P10,class P11,class P12,class P13>
class TDllProcV13 : public TDllProc {
public:
TDllProcV13(const
TDll& a_Dll, const char* a_Name)
: TDllProc(a_Dll,a_Name) {}
void operator ()(P1 p1,P2 p2,P3 p3,P4 p4,P5 p5,P6 p6,P7 p7,P8
p8,
P9 p9,P10 p10,P11 p11,P12 p12,P13 p13)
{
typedef void (* TProc)(P1,P2,P3,P4,P5,P6,P7,P8,P9,P10,P11,P12,P13);
((TProc)m_Proc)(p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12,p13);
}
};
template <class R,class P2,class P3,class
P4,class P5,class P6,class P7,
class P8,class
P9,class P10,class P11,class P12,class P13>
class TDllProc13 : public TDllProc {
public:
TDllProc13(const
TDll& a_Dll, const char* a_Name)
: TDllProc(a_Dll,a_Name) {}
R operator ()(P1 p1,P2 p2,P3 p3,P4 p4,P5 p5,P6 p6,P7 p7,P8 p8,
P9 p9,P10 p10,P11 p11,P12 p12,P13 p13)
{
typedef R (* TProc)(P1 );
return ((TProc)m_Proc)(p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12,p13);
}
};
//Ну вот и все.
Теперь вернемся к тому, с чего все началось.
Моя функция
//будет теперь выглядеть следующим
образом
void __fastcall
TMainForm::OpenTaskActionExecute(TObject *Sender)
{
try
{
//TDll создаем динамически, т.к выгрузка dll
происходит по окончании
//работы программы - это определяется
логикой работы приложения
TDll* dll = new TDll(DllName.c_str());
//если нужно выгрузить dll после вызова
функции, объявление
//выглядело бы так: TDll dll(DllName.c_str());
//объявлем объект-функциию
TDllProcV3<TComponent*,char*,char*>
RunDll(*dll,ProcName.c_str());
//ну и наконец сам вызов
RunDll(this,UserCode.c_str(),LabelCode.c_str());
}
catch(Exception& excp)
{
ShowErr(mtError,&excp);
}
}
//Вся процедура
вызова функции из динамической библиотеки
уложилась в три
//строки: два объявления и вызов. На мой
взгляд достаточно лаконично и
//изящно. Теперь вставляем в проект,
компилируем и ... получаем:
// [C++Error]
Main.cpp(96):E2238 Multiple declaration for TDll
// [C++Error] uticls.h(500):E2344 Earlier declaration for TDll
//Вот мистика! Что-то опять запахло серой.
Дрожащей рукой открываю
//файл uticls.h и вижу, что парни из Борланда
тоже не забыли свою
//технологию из OWL, но дали ее в сильно
урезанном виде исключительно,
//по всей видимости, для своих целей. Ниже
весь борландовский кусок:
////////////////////////////////////////////////////
// TDll: Wrapper class encapsulating a DLL
////////////////////////////////////////////////////
class TDll
{
public:
TDll(LPCTSTR name) : m_Hinstance(::LoadLibrary(name)) {}
~TDll()
{
if (m_Hinstance)
::FreeLibrary(m_Hinstance);
}
operator HINSTANCE() const { return m_Hinstance; }
FARPROC GetProcAddress(LPCSTR funcName) const { return ::GetProcAddress(*this, funcName); }
protected:
HINSTANCE m_Hinstance;
private:
TDll(const TDll&);
TDll& operator=(const TDll&);
};
//////////////////////////////////////////////////
// TDllProc: Encapsulates a DLL EntryPoint/FARPROC
//////////////////////////////////////////////////
class TDllProc
{
public:
TDllProc(const TDll& dll, LPCSTR funcName)
{
m_Proc = dll.GetProcAddress(funcName);
}
operator bool() { return m_Proc != 0; }
protected:
FARPROC m_Proc;
};
////////////////////////////////////////////////////////////////
// FARPROC that's CDECL, returns HRESULT and takes one parameter
////////////////////////////////////////////////////////////////
template <class P1>
class TDllProc1 : public TDllProc
{
public:
TDllProc1(const TDll& dll , LPCTSTR funcName) : TDllProc(dll, funcName) {};
HRESULT operator ()(P1 erste)
{
typedef HRESULT (__cdecl* outproc)(P1 erste);
return ((outproc)m_Proc)(erste);
}
};
//////////////////////////////////////////////////////////////////
// FARPROC that's STDCALL, returns HRESULT and take two parameters
//////////////////////////////////////////////////////////////////
template <class P1,
class P2>
class TDllStdProc2 : public TDllProc
{
public:
TDllStdProc2(const TDll& dll, LPCTSTR funcName) : TDllProc(dll, funcName) {}
HRESULT operator () (P1 p1, P2 p2)
{
typedef HRESULT (__stdcall* outproc)(P1 p1, P2 p2);
return ((outproc)m_Proc)(p1, p2);
}
};
//////////////////////////////////////////////////////////////////
// FARPROC that's CDECL, returns a void pointer,
// and takes two parameters
//////////////////////////////////////////////////////////////////
template <class P1,
class P2>
class TDllProc2 : public TDllProc
{
public:
TDllProc2(const TDll& dll, LPCTSTR funcName) : TDllProc(dll, funcName) {}
void* operator () (P1 p1, P2 p2)
{
typedef void* (__cdecl* outproc)(P1 p1, P2 p2);
return ((outproc)m_Proc)(p1, p2);
}
};
//Как видим
здесь они ограничились только тремя
функциями. Ну это не так
//страшно. Чтобы на будущее исключить конфликты,
обрамляю свои классы в
//namespace
MyDll и запускаю на трансляцию заново. Все
проходит без проблем
//в том числе и проверка на работоспособность. Ну
на этом с чувством
// выполненного долга можно и успокоиться.
//Для тех, кто
хочет использовать шаблоны для вызова
функций и не намерен
//набивать их руками, мой
вариант template классов с демонстрационным
//примером их использования может скачать
отсюда