Форум — Ответы ( К темам )
? | Andrew: Метод класса в DLL (24-01-2003 13:58:45) | |
Можно ли засунуть метод класса в DLL для динамического выбора различных методов обработки? Из DLL должен быть доступ к свойствам класса. | ||
Георгий (24-01-2003 17:45:36) | ||
DLL — часть Windows которая не обьектная. т.е. и DLL — не может содержать обьекты.
| ||
Petro (24-01-2003 22:47:33) | ||
2 Георгий: Не гони, в длл можно описывать классы и создавать объекты. Можно все тоже самое, что и в обычном ехе. 2 Andrew: Можно засунуть. Доступ к свойствам будет, если класс в длл описан. Но проще выбирать методы в основной проге. Подробней напиши, может без длл можно обойтись. | ||
Георгий (26-01-2003 06:14:51) | ||
Petro — покажи как ты динамически линкуешь такую "обьектную" DLL! Как с помошью функций Win32 API получаеш доступ к конструкторам, особенно в случае работы с абстрактными классами! Проще говоря — Не гони!!! | ||
Георгий (26-01-2003 06:19:36) | ||
по поводу того, что понимать под DLL — DLL — это, то что поймёт любая программа, а не написанная только на C++ Builder X.XX
| ||
Petro (26-01-2003 16:29:20) | ||
2 Георгий Стоп-стоп. :) Ты написал, что длл не может содержать объекты. Гон? Гон. :) Если ты говоришь об _экспорте_ объектов, об этом речи не было. Длл линкуется независимо от основной проги. Ты можешь экспортировать из длл все что имеет фикс. адрес — функции и переменные. И методы класса в том числе. И объекты. И получаешь доступ ко всему этому при помощи GetProcAddress. Другой вопрос — как их потом использовать в вызывающей проге. >по поводу того, что понимать под DLL — DLL — это, то что поймёт любая >программа, а не написанная только на C++ Builder Да, я примерно так и представлял себе. ;) | ||
Petro (26-01-2003 16:29:34) | ||
2 Георгий Стоп-стоп. :) Ты написал, что длл не может содержать объекты. Гон? Гон. :) Если ты говоришь об _экспорте_ объектов, об этом речи не было. Длл линкуется независимо от основной проги. Ты можешь экспортировать из длл все что имеет фикс. адрес — функции и переменные. И методы класса в том числе. И объекты. И получаешь доступ ко всему этому при помощи GetProcAddress. Другой вопрос — как их потом использовать в вызывающей проге. >по поводу того, что понимать под DLL — DLL — это, то что поймёт любая >программа, а не написанная только на C++ Builder Да, я примерно так и представлял себе. ;) | ||
Георгий (26-01-2003 18:37:32) | ||
Нет ты покажи, как работаешь с обьектом (с помошью ф-ции GetProcAddress), который находится в DLL! и как борешься с различным выравниванием полей (без знания структуры DLL). и как экспортируешь метод класса, в отрыве от контекста! А теперь по поводу вопроса: "Можно ли засунуть метод класса в DLL для динамического выбора различных методов обработки? Из DLL должен быть доступ к свойствам класса." Фактически это — экспорт если не всего обьекта, то покрайней мере экспорт его методов подразумевался. В лоб это решить нельзя (попробуй доказать обратное), а можно примерно так: 1. в конструкторе обьекта загрузить DLL и прочитать адрес ф-ции (ИМЕННО ФУНКЦИИ), котораю будет что-то обрабатывать. 2. в методе, который как бы в DLL, используя ,полученный на первом шаге, адрес вызвать функцию обработки из DLL. 3. в деструкторе выгрузить DLL. Если это ТЫ Petro считаешь ЭКСПОРТОМ метода, то я сдаюсь. Но даже, если предположить, что МЕТОДЫ можно хранить в DLL и потом насильно загружать в основную программу, то как быть с виртуальными методами, с системными таблицами адресов этих методов — их тоже надо вручную править??? | ||
Devnvd (26-01-2003 20:10:23) | ||
Приведу вам пример статического использования DLL по поводу описания метода класса в DLL. Может быть можно было написать несколько иначе и проще. Но я не экспериментировал. Небыло необходимости. Пример работающий. Это будет реальной основой для продолжения дискуссии.
| ||
Devnvd (26-01-2003 20:55:10) | ||
Приведу вам пример статического использования DLL по поводу описания метода класса в DLL. Может быть можно было написать несколько иначе и проще. Но я не экспериментировал. Небыло необходимости. Пример работающий. Это будет реальной основой для продолжения дискуссии. Что-то разбросало пример, сделаю компактнее вид и несколько понятнее. //========== ExpFun.dll ======== #include #include #pragma hdrstop //------------------------------------- #pragma argsused //Описание класса class TMyClass{ private: int Member; public: __fastcall TMyClass(); __fastcall ~TMyClass(); int __fastcall localfun(); int __import __stdcall exportfun(); }; //функция может использовать только члены класса //Но не функции класса, реализованные в другом месте. int __export __stdcall TMyClass::exportfun() { return Member; } int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved) { return 1; } //========== Unit1.cpp ========== #include #pragma hdrstop #include "Unit1.h" #pragma link "ExpFun.lib" //---------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //Описание класса class TMyClass{ private: int Member; public: __fastcall TMyClass(); __fastcall ~TMyClass(); int __fastcall localfun(); int __import __stdcall exportfun(); }; //Здешние функции класса __fastcall TMyClass::TMyClass(){Member=1234;} __fastcall TMyClass::~TMyClass(){;} int __fastcall TMyClass::localfun() { return Member; } //----------------------------------------- TMyClass MyClass; //------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { //Вызываем функцию класса из DLL ShowMessage("Export Func="+IntToStr(MyClass.exportfun())); //Вызываем функцию класса здешнюю ShowMessage("Local Func="+IntToStr(MyClass.localfun())); } | ||
Devnvd (27-01-2003 09:58:01) | ||
Теперь пример динамического использования DLL. Один из методов класса описан в DLL. Пример работающий. //========== ExpFun.dll ======== #include #include #pragma hdrstop //----------------------------------------- #pragma argsused class TMyClass{ private: int Member; public: __fastcall TMyClass(); __fastcall ~TMyClass(); int __fastcall localfun(); int __fastcall exportfun(int param1, int param2); }; int __export __fastcall TMyClass::exportfun(int param1,int param2) { return param1+param2+Member; } int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved) { return 1; } //========== Unit1.cpp ========== #include #pragma hdrstop #include "Unit1.h" //При статическом использовании ExpFun.dll //#pragma link "ExpFun.lib" //------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; class TMyClass{ private: int Member; public: __fastcall TMyClass(); __fastcall ~TMyClass(); int __fastcall localfun(); int __fastcall exportfun(int param1, int param2); }; __fastcall TMyClass::TMyClass(){Member=1234;} __fastcall TMyClass::~TMyClass(){;} int __fastcall TMyClass::localfun() { return Member; } TMyClass MyClass; //------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { HINSTANCE h=LoadLibrary("ExpFun.dll"); if(h) { //Первый параметр в функции класса, это указатель на экземпляр класса typedef int __fastcall (*TFunDll)(void *,int,int); // Название искомой функции выглядит, не совсем приятно TFunDll funDll=(TFunDll)GetProcAddress(h,"@TMyClass@exportfun$qqrii"); if(funDll) { int ResultDll=funDll(&MyClass,1,10); //1+10+MyClass.Member; ShowMessage("Export Func="+IntToStr(ResultDll)); } FreeLibrary(h); } // При статическом использовании ExpFun.dll // ShowMessage("Local Func="+IntToStr(MyClass.exportfun(1,10))); ShowMessage("Local Func="+IntToStr(MyClass.localfun())); } //========================== Функцию можно расписать также и в Unit1.cpp int __fastcall TMyClass::exportfun(int param1,int param2) { return -param1-param2-Member; } И использовать наравне с другими функциями класса Выводы сделаете сами, что можно, а что нельзя делать в DLL. | ||
Andrew (27-01-2003 10:35:18) | ||
Большое спасибо всем принявшим участие в ответе на вопрос. Код сейчас попробую. To Devnvd : можно пожалуйста поподробнее по поводу "$qqrii" в строке TFunDll funDll=(TFunDll)GetProcAddress(h,"@TMyClass@exportfun$qqrii"); | ||
Devnvd (27-01-2003 17:08:15) | ||
Это,,"@TMyClass@exportfun$qqrii", непонятное на первый взгляд имя вы можете узнать для своих функций посмотрев в файл ExpFun.def, созданный с помощью: impdef.exe -h ExpFun.def Expfun.dll Также для Exe: impdef.exe -h Project1.def Project1.exe В таком виде представляются CPP-ные функции после компиляции. "ii" в конце это параметры функции. Функция без параметров () имела бы "qqrv", v — void. По поводу оставшихся qqr вам нескажу. Но если вас интересует, то пробуйте разные опции компилятора "Advanced Compiler" касаемые Calling convention. Если в Unit1.cpp добавить к какой-нибудь функции "__export": int __export __fastcall TMyClass::localfun() { return Member; } то в DLL вы сможете получить её адрес и вызвать эту функцию: int __export __fastcall TMyClass::exportfun(int param1,int param2) { //Получим адрес функции TMyClass::localfun // в месте где функция реализована необходимо // добавить __export: int __export __fastcall TMyClass::localfun(); //И воспользуемся ею //Чтобы получить адрес функции обращаемся к Exe так же как и к DLL HMODULE h=GetModuleHandle(0); if(h) { typedef int __fastcall (*TFunExe)(void *); TFunExe funExe=(TFunExe)GetProcAddress(h,"@TMyClass@localfun$qqrv"); if(funExe) param1 +=funExe(this); } return param1+param2+Member; } Аналогично можно поступать и c функциями не являющимися членами классов. Вы можете получить доступ к функциям Exe на этапе загрузки DLL(LoadLibrary) и произвести необходимые действия не по инициативе Exe, а сами непосредственно из DLL. | ||
Георгий (29-01-2003 01:13:58) | ||
cool — наконец то! Devnvd — спасибо большое, за аргументированный ответ. Но я хочу обратить твоё внимание на следующую строку: int ResultDll=funDll(&MyClass,1,10); здесь ты вызвал метод класса как ФУНКЦИЮ и воспользовавшись соглашением о передаче параметров (к сожалению не смотрел, формат вызова методов компиляторами производства не Borland) подсунул этой ФУНКЦИИ конкретный экземпляр обьекта. Что я могу сказать: 1. под экспортом метода (обьекта) я подразумевал возможность обратиться к методу(обьекту), физически расположенному в DLL, так, чтобы программист на некоторых этапах не задумывался о том, что этот метод (обьект) находится в DLL. 2. сила языка C++ имено в таких трюках, но что если вдруг нужно будет воспользоваться этим методом (обьектом) из программы созданной на другом компиляторе (например Watcom C++ 11.0), где аргументы функций по возможности передаются через регистры? 3. в принципе любой обьект (но без механизма наследования и виртуальных методов) можно реализовать как набор функций и структур, что ты и сделал (но замаскировал под псевдо вызов метода). На основе этих рассуждения я снова ставлю вопрос: Как быть с механизмом наследования? class A//прототип и в DLL и в основной программе { public: virtual int method(void)=0; }; class B:public A//прототип в DLL { public int method(void); }; int B::method(void){return 5;};//реализация в DLL A* anyFunction(void);//ф-ция в DLL и возвращает обьект 'B' void main (void) { A* ptrA; ptrA=anyFunction(); cout<<ptrA->method();//что будет? }; вот, если в DLL хранить потомка (потомков) обьекта A, то каким образом основная программа сможет после подключения этой DLL воспользоваться РЕАЛИЗАЦИЕЙ методов именно потомка? В случае много модульной программы (это когда код (ну обьектный код) хранится в OBJ файлах) этим вопросом занимается компилятор и для разрешения именно этих вопросов и создаются таблицы методов (виртуальных). А при динамической загрузке DLL таблицы УЖЕ созданы и именно по этому я ответил на этот вопрос, что МЕТОД нельзя импортировать. Надеюсь хоть на какой-то ответ. | ||
Devnvd (29-01-2003 19:26:53) | ||
По описанному мной механизму, ясно что главным условием является одинаковость описания класса, именно той части, которая требует выделения памяти. Обычные методы класса ничем не отличаются от простых функций. Компилятор с ними так и обращается. Моё сейчашное понимание: Для виртуальных же методов строится отдельная таблица для каждого составляющего класс классов, в которой размещаются реальные адреса методов. И следовательно требуется наличие в этом же месте, месте описания класса, кода реализации этого метода. Поэтому нельзя описать в классе виртуальный метод в Exe и в DLL, а реализацию его разместить только допустим в DLL. Но надо проверить. | ||
Георгий (29-01-2003 20:15:17) | ||
Вот и я тогоже мнения, но: 1. в таблице хранятся адреса — нет необходимости в том, чтобы код находился там же, где и основная программа (но возможно очень неудобно в таблицу записать нужный адрес). 2. при статичиском линковании нет проблем (не проверял, но слышал) Моё понимание: в конкретном экземпляре класса, хранится адрес записи в таблице виртуальных методов — что то типа: 1. адрес метода класса А 2. адрес метода класса B, наследника А 3. адрес метода класса C, наследника B и для моего примера указатель ptrA указывает на обьект, у которого у соответствующем поле находится адрес "2", что позволяет и вызвать виртуальный метод и, при необходимости, вызвать метод родительского класса. Вопрос в следующем — как влезть в эту таблицу в моём примере (из DLL, во время её инициализации?) и не бояться, что в новой версии компилятора, она будет находится где-то в другом месте. | ||
Devnvd (30-01-2003 16:51:55) | ||
Ваш пример, Георгий, я проверил. Всё работает правильно. Перед тем как собрать я сделал ассемлеровский листинг кода DLL. И не увидев там никаких таблиц, а только коды подпрограмм, решил собирать полностью. Всё заработало сразу. Поэтому можете пробовать усложнять класс. Никаких дополнительных работ с таблицами виртуальных методов на горизонте не предвидется. Также никаких особенностей касаемых разных компиляторов, тоже не предвидется. Полный текст проверки: ===== ExpFun.Dll ======= #include #include #pragma hdrstop //---------------------------------------------- #pragma argsused class A//прототип и в DLL и в основной программе { public: virtual int method(void)=0; }; class B:public A//прототип в DLL { public: int method(void); }; int B::method(void) //реализация в DLL { return 5; } B b; //Экземпляр класса //Вспомогательная Функция для получения // в Exe адреса на экземпляр класса extern "C" A* __export __stdcall anyFunction(); A* __stdcall anyFunction()//ф-ция в DLL и возвращает обьект 'B' { return &b; } int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved) { return 1; } ============ Exe — Unit1.cpp =========== #include #pragma hdrstop #include "Unit1.h" //---------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; class A//прототип и в DLL и в основной программе { public: virtual int method(void)=0; }; //--------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { HINSTANCE h=LoadLibrary("ExpFun.dll"); if(h) { typedef A* __stdcall ( *TFunDll)(void); TFunDll anyFunction=(TFunDll)GetProcAddress(h,"anyFunction"); if(anyFunction) { A* ptrA; ptrA=anyFunction(); if(ptrA) { int ResultDll=ptrA->method();//что будет? ShowMessage("Export Func="+IntToStr(ResultDll)); } else ShowMessage("PtrA=0"); } else ShowMessage("No find anyFunction"); FreeLibrary(h); } } Тема довольно таки серьёзная и жаль, что она уходит с первой страницы форума. Для таких тем необходим отдельный раздел. | ||
Георгий (30-01-2003 18:17:07) | ||
раз нет таблиц виртуальных методов, то я не понимаю как это работает. сам я писал проги на разных языках (ФОКАЛ и БЕЙСИК на БК-0010-01, PASCAL C++ Assembler на PC) но остановился на C++ из-за его строгости (ясности физической реализации и, как следствие, простоты эквивалентных реализаций — т.е. нет функций которые нельзя самому сделать (в паскале это write)) и эффективности разработки больших приложений (по сравнению с ассемблером), а тут вот тебе и фокус, который я не понимаю: если есть 2 потомка класса A: B и C и функция anyFunction возвращает как результат указатель на один из этих классов, то как при вызове метода этого базового класса компилятор (а кто кроме него?) определяет какой реальный метод вызвать? или в структуре описывающей класс есть адрес реализации метода, но как тогда реализуется вызов родительского метода, который обычно вызывается ::method() (т.е. через две точки), из которого в свою очередь можно вызвать его родительский (покрайней мере у меня такое понимание обьектно ориентированного программирования) и кстати чем ты DLL дизассемблировал? у меня есть wdis (из комплекта Watcom C++), но он работает только с obj. | ||
Георгий (30-01-2003 20:22:09) | ||
кажется понял! на закладке Project -> options-> C++ раздел Virtual tables прочёл внимательно помошь и "погулял" по EXE в окне View->Debug windows-> CPU в результате чего пришёл к выводу: 1. таблицы виртуальных функций создаются компилятором и используются линкёром (сборщиком) на этапе разрешения связей и ни для каких других целей не используются (в программу они не должны попадать т.к. уже не нужны). 2. обьект представляется в виде структуры (т.е. даже на С (не С++ а С) можно реализовать виртуальные функции) в которой есть скрытые поля (не доступные программисту), соответствующие виртуальным функциям, в которые при создании обьекта (судя по всему до вызова конструктора) записывается адрес реализации метода, соответствующего данному экземпляру класса, и в процессе работы вызов метода выполняется именно по этому адресу (проверил в пошаговой отладке). 3. из DLL нельзя вызвать метод класса "А" даже не смотря на то, что он виртуальный — редактор связей (линкёр) пишет, что не может разрешить связь!!! это ещё раз подтверждает пункт 1 (и разрушает мои иллюзии относительно принципов ООП в C++) class A { public: virtual int method(void);//реализация в EXE }; class B:public A { public: int method(void); }; class C:public A { private: int asd; char a[5]; public: int method(void); }; class D:public B { public: int method(void); }; class E:public A { public: int method(void); }; class F:public E { public: int method(void); }; int F::method(void) { return 12+E::method(); }; int E::method(void) { return 10+A::method();//не хочет линковать!!! }; int D::method(void) { return 8+B::method();//заменяет на вызов B::method() по относительному адресу (т.е. никакой виртуальности здесь нет! — вызов вполне конкретной функции) }; int C::method(void) { return 8; }; int B::method(void) { return 5; } 4. из EXE можно вызывать виртуальный метод, где бы он ни находился - mov edx, ptrA — загружается указатель на структуру (обьект) mov ecx,[edx] — из структуры считывается адрес call dword ptr ecx — вызов виртуальной функции как ты видишь — нет никакой разницы где находится реализация виртуального метода т.е. возможет обратный процесс обсуждаемому — в функцию DLL можно передать указатель на обьект и вызвать виртуальный метод (реализованный в EXE) этого обьекта из DLL (не проверял, но должно работать) 5. каждый виртуальный метод увеличавает размер экземпляра класса (и всех его потомков) на 4 байта (сюда наверное можно добавить и выравнивание, если оно включено) 6. не очевидно, но факт — наследники (те, что в DLL) не могут вызвать конструктор базового класса, если он реализован в EXE — опять "виноват" линкер. случайно обратил внимание — механизм RTTI тоже использует скрытые поля обьектов. Осталось придумать, как экспортировать именно обьекты из динамически линкуемой DLL. т.е. описание обьекта в EXE, а реализация — в DLL и как там будет с виртуальными функциями (я их методами больше не могу назвать;-). | ||
Devnvd (30-01-2003 20:47:03) | ||
Дизассемблирую я непосредственно исходный Cpp-текст в Builder5. Для этого расписываю класс в отдельном файле и добавляю в методе: _asm nop; Увидя эту строку компилятор любезно генерит мне asm-файл. Со всеми коментариями и исходными строками. В продолжение ваших исследований предложу вам следующий пример для уяснения механизма работы с виртуальными функциями. =========================== #pragma hdrstop #include #include class TObj; typedef void (TObj::* TFun)(); class TObj { public: TObj(){}; ~TObj(){}; void Func(){printf("TObj::Func ");} virtual void VFunc(){printf("TObj::VFunc ");} }; class TMObj:public TObj { public: TMObj():TObj(){}; ~TMObj(){}; virtual void VFunc(){printf("TMObj::VFunc ");} }; #pragma argsused int main(int argc, char* argv[]) { TObj *Obj=new TObj; TMObj *MObj=new TMObj; TFun fun=&TObj::Func; //Взяли указатель на функцию Obj->Func(); //Прямой вызов функции (Obj->*fun)(); // от имени класса Obj запускаем через указатель функцию Func. (MObj->*fun)(); //от имени класса MObj запускаем через указатель функцию Func. fun=&TMObj::Func; //Взяли указатель на функцию (Obj->*fun)(); // от имени класса Obj запускаем через указатель функцию TObj::Func. (MObj->*fun)(); //от имени класса MObj запускаем через указатель функцию TMObj::Func. fun=&TObj::VFunc; //Взяли указатель на виртуальную функцию (Obj->*fun)(); // от имени класса Obj запускаем через указатель функцию TObj::VFunc. (MObj->*fun)(); //от имени класса MObj запускаем через указатель функцию TMObj::VFunc. printf("nShould be:n"); //В результате должно получится printf("TObj::Func TObj::Func TObj::Func TObj::Func TObj::Func TObj::VFunc TMObj::VFunc"); getch(); return 0; } ========================== | ||
Георгий (31-01-2003 05:47:58) | ||
конечно спасибо, но это ты показал обычное явное указание типа — тоже самое, что struct strA { char A; short int B; long int C; } structA; struct strB { char arr[7]; }; void* ptr=&structA; ((strB*)ptr).arr[1]=13; ((strB*)ptr).arr[2]=0; в результате в structA.B==13, хотя формально работа велась с strB тоже самое ты показал, но применительно обьектам | ||
Георгий (31-01-2003 05:52:26) | ||
забыл звёздочку добавить (*(strB*)ptr).arr[1]=13; (*(strB*)ptr).arr[2]=0; | ||
Devnvd (31-01-2003 10:09:20) | ||
Давайте попробуем сделать промежуточный вывод. 1. Методы класса, какие они бы нибыли, это всего лишь подпрограммы. 2. Описание класса — инструкция компилятору, а не работающей программе. 3. Основным при работе является указатель на экземпляр класса. 4. Доступ к необходимым переменным класса и виртуальным методам производится по относительным смещениям определяемым из описания класса. 5. Вы можете обращаться с классом как с обычной структурой. Из всего этого следует: Для совместной работы с классами Exe и DLL необходимым условием является одинаковость описания класса в Exe и в DLL. Порядок расположения объявлений виртуальных методов и членов класса должен быть неизменным. Так как на основании этого порядка компилятор определяет смещения. Доступ к обычным методам класса производится так же как и к обычным подпрограммам, с одной лишь разницей: наличие дополнительного первого параметра в методе класса — указателя на экземпляр класса. | ||
Георгий (31-01-2003 21:05:53) | ||
Да... коротко и точно. Теперь осталось придумать практическое приложение этому всему. Удивительно, но я нигде не видел описания физической реализации обьектов (ведь это наверное стандарт?), хотя статей о C++ много прочёл... |