![>](style_images/1/nav_m.gif)
![]() |
Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
|
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[18.119.106.226] |
![]() |
|
Сообщ.
#1
,
|
|
|
Доняли, выкладываю пилотную версию.
![]() Q: Как мне узнать, какие функции есть в Dll (експортируются из нее)? A: Вместе с большинством сред разработки поставляются утилиты для получения списка експортируемых из Dll объектов. Для MSVC ето dumpbin.exe, которая просто выдает список експортируемых объектов, если ее запустить с ключом /EXPORTS: dumpbin.exe /EXPORTS mydll.dll Для продуктов Borland ето impdef.exe, которая делает .DEF-файл (см. ниже), содержащий названия експортируемых из Dll объектов: impdef.exe mydll.DEF mydll.DLL Кроме того, можно воспользоваться утилитой DllView, или утилитой Dependency Walker, входящей в состав MS Visual Studio. Q: Как мне узнать, какие параметры принимает функция, експортируемая из Dll, и как ей следует пользоваться? A: В общем случае -- никак. Dll не содержит подобной информации. Если Dll была написана на C++, и использовались, т.н., декорированные имена, то типы аргументов функции можно получить, анализируя имена функций. Если библиотека была написана на одном из распространенных языков высокого уровня, можно воспользоваться дизассемблерами, вроде IDA, чтобы проанализировать сруктуру параметров функции. Q: Мне нужно сделать Dll, из которой експортируется одна функция. С чего вообще начинать? A: Необходимо, как и для обычной библиотеки, создать файл интерфейса и файл реализации: mydll.h ![]() ![]() #ifndef __MYDLL_H #define __MYDLL_H #ifdef __cplusplus extern "C"{ #endif __declspec( dllexport ) int Sum(int, int); #ifdef __cplusplus } #endif #endif /* __MYDLL_H */ mydll.cpp ![]() ![]() #include <windows.h> #include "mydll.h" BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { return TRUE; } int Sum(int a, int b) { return a+b; } блок extern "C" можно убрать, если требуется, чтобы експортируемые имена соответствовали соглашениям C++ о декорации имен, и Dll будет использоваться только в проектах на C++. Q: Как мне теперь воспользоваться только что созданной Dll mydll? A: Существует два способа подключить Dll к своему проекту: 1. Использовать раннее динамическое связывание, то есть подключить к своему проекту библиотеку импортированных функций. Если мы сами откомпилировали dll, или нам вместе с dll дали ету библиотеку, то ее нужно просто подключить к своему проекту. Если нет, нужно библиотеку импортированных функций создать. В MSVC ето делается, например, так: link /DLL /DEF:mydll.def /IMPLIB:mydll.lib /OUT:dummy Линкер ругается, но создает библиотеку mydll.lib, которую можно подключать к своему проекту. В продуктах Borland для етого существует специальная утилита implib.exe mydll.def -- .DEF-файл, который содержит список експортируемых из Dll функций (см. ниже). 2. Использовать позднее динамическое связывание, т.е, загружать Dll и получать адреса експортируемых из нее функций не автоматически в момент запуска программы, а вручную и в произвольный момент времени. Для етого нужно знать только имя файла библиотеки и название функции. Чтобы воспользоваться функцией Sum, содержащейся в библиотеке mydll.dll, в своем проекте нужно написать примерно так: ![]() ![]() #include "mydll.h" #define SUM_FUNC_NAME "Sum" ... typedef int (* SumFunctionType)(int, int); ... SumFunctionType SumFunctionAddress; ... HMODULE hLib = LoadLibrary("mydll.dll"); if(hLib) { SumFunctionAddress = (SumFunctionType)GetProcAddress(hLib, SUM_FUNC_NAME); if(SumFuncAddress) x = SumFuncAddress(y, z); } Более подробно, см. справку по функциям LoadLibrary и GetProcAddress Q: Почему у меня не работает пример из предыдущего вопроса? Я разбирался, и выяснил, что GetProcAddress возвращает NULL? A: Видимо, функция Sum в Dll называется не "Sum", а так, как ее обозвал компилятор. Чтобы он ее обзывал так, как захотите вы, используйте .DEF файл (см. ниже). Q: Что ето за такой .DEF файл, и для чего он нужен. A: .DEF-файл содержит т.н., определения модуля, и используется сборщиком при компоновке Dll и Exe. .DEF-файл содержит много разных секций, в отношении работы с Dll нас будут интересовать две: Секция LIBRARY задает имя библиотеки ![]() ![]() LIBRARY MYDLL Секция EXPORTS задает список експортируемых объектов ![]() ![]() EXPORTS Sum @1 В етой же секции можно указывать псевдонимы для функций, т.е., експортировать одну и ту же функцию из Dll под разными именами. Более подробно см. описание Module Definition File Q: Я создал и отладил Dll, которую хочу использовать (через библиотеку импортированных функций) в своих проектах. Куда мне ее следует поместить, чтобы все мои проекты могли получить к ней доступ? A: Windows ищет Dll следующим образом: 1. Значала проверяются все уже загруженные библиотеки, если среди них находится Dll с тем же именем, программа связывается с ней. 2. Затем Windows пытется найти Dll в папке, где расположен исполняемый файл программы, использующей Dll. 3. Затем, Windows пытается найти Dll в текущем каталоге. 4. Затем, Windows пытается найти Dll в системном каталоге Windows (что-то вроде C:\Windows\System32 для NT-систем) 5. Затем, Windows пытается найти Dll в каталоге Windows (что-то вроде C:\Windows) 6. Наконец, Windows пытается найти Dll в одной из папок, перечисленных в переменной окружения PATH Q: Как мне воспользоваться ресурсами, хранящимися в Dll? A: Точно так же, как и ресурсами, хранящимися в собственной программе -- загрузить и использовать. Единственное отличие -- необходимо загрузить Dll, и в функцию LoadResource передать ее хендл, вместо HINSTANCE собственной программы: ![]() ![]() HMODULE hLib = LoadLibrary("rsrclib.dll"); if(hLib) { HRSRC hRes = FindResource(hLib, "MYDATA", "MYRESTYPE"); if(hRes) { HGLOBAL hMem = LoadResource(hLib, hRes); if(hRes) { char * pMyData = (char *)LockResource(hMem); // pMyData указывает на загруженные из ресурса данные ... } } } Q: Почему при вызове функции из Dll я получаю сообщение типа "ESP is not correctly saved across function call" или "Bad calling convention", или моя программа вылетает, хотя функция, по всем признакам, сработала как надо? A: Чтобы ето понять, надо разобраться с тем, как осуществляется вызов подпрограмм, передача им параметров и возврат из подпрограмм в языках высокого уровня. В паскале, перед вызовом подпрограммы, все параметры подпрограммы помещаются в стек, в тот же, в котором хранятся адреса возврата. Подпрограмма, завершая свою работу, извлекает из стека адрес возврата и все переданные ей параметры (т.е., паскалевская подпрограмма всегда знает сколько и каких параметров ей передают). В Си перед вызовом подпрограммы все ее параметры также помещаются в стек, а подпрограмма завершая свою работу НЕ извлекает ети параметры. Етим должна заниматься вызвавшая программа. Существуют и другие варианты вызова функций. Соответственно, чтобы корректно вызвать функцию Dll, необходимо знать какой формат вызова (Calling convention) поддерживает ета функция. Опять же, в общем случае ету информацию из Dll получить невозможно, но чаще всего используется формат вызова __stdcall и __cdecl. Q: А как сделать в Dll переменную, общую для всех использующих ету Dll процессов? Например, глобальный счетчик? Я объявляю в Dll глобальную переменную-счетчик, но она считает только внутри одного процесса ![]() A: Для етого необходимо поместить переменную в общую для всех процессов секцию данных. По умолчанию, для каждого процесса подключающего Dll, создается новая отдельная секция данных Dll. Чтобы переменные помещались в общую секцию данных, используйте директивы pragma: ![]() ![]() #pragma data_seg("mydata") //определяем секцию для наших данных int i=0; // другие переменные общего пользования #pragma data_seg() #pragma comment(linker,"-SECTION:mydata,rws");//указываем линковщику что наша секция доступна для чтения/записи и общая |
Сообщ.
#2
,
|
|||||
|
Не знаю, уместно ли, но дополнения:
Для VC еще есть Dependency Walker (depends.exe) - полезная утилита
Использовать IDA disassembler. Можно узнать сколько аргументов и (при дальнейшем анализе) их назначение. |
Сообщ.
#3
,
|
|
|
Щас внесу
![]() --- Добавляю... |
Сообщ.
#4
,
|
|
|
можно вставить вопрос про общие глобальные переменные (сам недавно столкнулся с такой проблемой), т.е. как сделать одну и ту же переменную доступную для разных процессов, подгрузивших нашу ДЛЛ) - если надо канешна
![]() ![]() ![]() #pragma data_seg("mydata") //определяем секцию для наших данных int i=0; // другие переменные общего пользования #pragma data_seg() #pragma comment(linker,"-SECTION:mydata,rws");//указываем линковщику что наша секция доступна для чтения/записи и расшаренная |
Сообщ.
#5
,
|
|
|
А как насчет такого вопроса... Как можно воспользоваться Dll-кой, если она находиться в памяти в буфере. Т.е. есть указатель на область памяти, куда загрузили (считали) библиотечку (типа char* Dll)
Пример использования... файл библиотеки передается по сети, но на диск, для того, чтобы ее динамически подключить писать нельзя... А считана она в память... Как из char* Dll сделать HINSTANCE Dll??? Или выполнить что то вроде GetProcAddress(..., "Func"), не через HINSTANCE а через char* Кстати. Насущный вопрос. Сам уже не раз с таким сталкивался... ![]() ![]() |
Сообщ.
#6
,
|
|
|
Ну ето у же за рамками топика.
Здесь надо копаться в 1. Windows kernel internals 2. Portable executable structure |
Сообщ.
#7
,
|
|
|
Как иллюстрация к теме, и как продолжение тем "Диалоговое окно ввода строки" и "Как можно вызвать диал для ввода строки?", я написал DLL, реализующую диалог ввода строки.
Что умеет данная DLL: - Создавать и вызывать имеющийся у нее в ресурсах диалог. - Возвращать введенную пользователем строку. - Может быть безболезненно использована несколькими потоками одного процесса. - Поддерживать ANSI и Unicode вызовы. DLL экспортирует 2 функции: ![]() ![]() GetInputStringA(HWND hWnd, LPSTR lpText, UINT uSize, LPCSTR lpCaption); ![]() ![]() GetInputStringW(HWND hWnd, LPWSTR lpText, UINT uSize, LPCWSTR lpCaption); ![]() ![]() #ifdef UNICODE #define GetInputString GetInputStringW #else #define GetInputString GetInputStringA #endif // UNICODE Параметры: Цитата hWnd [входной] - хэндл родительского окна (или NULL) lpText [входной/выходной] - указатель на буфер, где хранится исходная строка (на входе) и куда помещается введенная пользователем строка (на выходе) uSize [входной] - размер буфера lpText (в TCHARах) lpCaption [входной] - заголовок диалога DLL написана на чистом WinAPI C, не использует библиотек MFC и VC runtime. Использованные функции WinAPI: ![]() ![]() KERNEL32 Thread Local Storage (TLS) functions TlsAlloc TlsFree TlsGetValue TlsSetValue Memory Management LocalAlloc LocalFree Debugging/Error handling SetLastError USER32 Dialog Boxes DialogBox EndDialog GetDlgItem GetDlgItemText SetDlgItemText Windows PostMessage WM_COMMAND SendMessage EM_LIMITTEXT EM_SETSEL SetFocus SetWindowPos SetWindowText Файл проекта представлен в 2-х версиях - для Visual Studio 6 и Visual Studio 2003. Оптимальный размер выходного файла (5120 байт) - под VS 2003. Код содержит массу комментариев, так что разобраться в нем, IMHO, не представляет проблем. Пользуйтесь, на здоровье. Комментарии welcome, на вопросы отвечу. О багах сообщайте ![]() 28.03.04 BUGFIX: Исправлена ошибка с EM_LIMITTEXT. Тескт должен быть ограничен по длине буфера -1 TCHAR. Спасибо ~Archer~у. Прикреплённый файл ![]() |
Сообщ.
#8
,
|
|
|
Здесь просто Release версия, готовая для использования + хедер + lib (для Visual Studio)
Прикреплённый файл ![]() |
Сообщ.
#9
,
|
|
|
Опробовал. Огромное спасибо автору!!! Этой вещицы как раз нехватало.
Но есть один вопрос: длина строки указываемая в GetInputString есть максимальная длина вводимого текста, но в качестве релультата всегда возвращается на один символ меньше. Т.е. если ввести для инициализации Hello World и просто высести ту же строку то по любому получается Hello Worl. Если на 1 увеличить длину, то можно будет ввести еще один символ после Hello World но он будет обрезан после преобразования... Это баг или я что-то не так делаю? |
Сообщ.
#10
,
|
|
|
А расскажите пожалуйста про неявное подключение?
У меня есть только файлы dll и lib, и то lib сгенерирован из dll с помощью программы Dll2Lib, h-файла нету. Хочу подключить библиотеку неявно, скажите, такое подключение может работать? #pragma comment(lib, "NeuroNet.lib") У меня почему-то не работает, походу не видит библиотеку, не отображает её в ClassView. А может просто функции не так вызываю... Пробовала подключить через Project -> Linker -> Input -> Additional Dependencies, всё равно библиотеку не видит. Может дело в том, что нету h-файла? Тогда вопрос - как его получить?? Или я просто что-то не правильно делаю? |
Сообщ.
#11
,
|
|
|
Ninetta
Честно говоря, тоже один раз пробовал Dll2Lib - ничего не вышло. Может, готовить его не умею. Если есть DLL - грузи её при старте приложения через LoadLibrary, и всё. |