На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
! Правила раздела FAQ в группе разделов С++.
1. Раздел FAQ предназначен для публикации готовых статей.
2. Здесь нельзя задавать вопросы, для этого существуют соответствующие разделы:
Чистый С++
Visual C++ / MFC / WTL / WinApi
Borland C++ Builder
COM / DCOM / ActiveX / ATL
Сопутствующие вопросы
3. Внимание, все темы и сообщения в разделе премодерируются. Любое сообщение или тема будут видны остальным участникам только после одобрения модератора.
Модераторы: B.V., Qraizer
  
> Dll FAQ , Как бороться с Dll
    Доняли, выкладываю пилотную версию. :)

    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
    ExpandedWrap disabled
      #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
    ExpandedWrap disabled
      #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, в своем проекте нужно написать примерно так:
    ExpandedWrap disabled
      #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 задает имя библиотеки
    ExpandedWrap disabled
      LIBRARY MYDLL


    Секция EXPORTS задает список експортируемых объектов
    ExpandedWrap disabled
      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 собственной программы:
    ExpandedWrap disabled
      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:
    ExpandedWrap disabled
      #pragma data_seg("mydata")  //определяем секцию для наших данных
       int i=0;
       // другие переменные общего пользования
      #pragma data_seg()
      #pragma comment(linker,"-SECTION:mydata,rws");//указываем линковщику что наша секция доступна для чтения/записи и общая
    Сообщение отредактировано: Leprecon -
      Не знаю, уместно ли, но дополнения:

      Цитата
      Q: Как мне узнать, какие функции есть в Dll (експортируются из нее)?
      A: Вместе с большинством сред разработки поставляются утилиты для получения списка експортируемых из Dll объектов. ..

      Для VC еще есть Dependency Walker (depends.exe) - полезная утилита


      Цитата

      Q: Как мне узнать, какие параметры принимает функция, експортируемая из Dll, и как ей следует пользоваться?
      A: В общем случае -- никак. Dll не содержит подобной информации.

      Если Dll была написана на C++, и использовались, т.н., декорированные имена, то типы аргументов функции можно получить, анализируя имена функций.

      Использовать IDA disassembler. Можно узнать сколько аргументов и (при дальнейшем анализе) их назначение.

        Щас внесу smile.gif
        ---
        Добавляю...
        Сообщение отредактировано: Visitor -
          можно вставить вопрос про общие глобальные переменные (сам недавно столкнулся с такой проблемой), т.е. как сделать одну и ту же переменную доступную для разных процессов, подгрузивших нашу ДЛЛ) - если надо канешна :)
          ExpandedWrap disabled
             
            #pragma data_seg("mydata")  //определяем секцию для наших данных
              int i=0;
              // другие переменные общего пользования
            #pragma data_seg()
            #pragma comment(linker,"-SECTION:mydata,rws");//указываем линковщику что наша секция доступна для чтения/записи и расшаренная
          Сообщение отредактировано: Leprecon -
            А как насчет такого вопроса... Как можно воспользоваться Dll-кой, если она находиться в памяти в буфере. Т.е. есть указатель на область памяти, куда загрузили (считали) библиотечку (типа char* Dll)

            Пример использования... файл библиотеки передается по сети, но на диск, для того, чтобы ее динамически подключить писать нельзя... А считана она в память...

            Как из char* Dll сделать HINSTANCE Dll??? Или выполнить что то вроде GetProcAddress(..., "Func"), не через HINSTANCE а через char*

            Кстати. Насущный вопрос. Сам уже не раз с таким сталкивался... :angry: когда писать на диск нельзя, а взять нужную библиотечку для работы программы можно из интернета... :huh:
              Ну ето у же за рамками топика.

              Здесь надо копаться в
              1. Windows kernel internals
              2. Portable executable structure
                Как иллюстрация к теме, и как продолжение тем "Диалоговое окно ввода строки" и "Как можно вызвать диал для ввода строки?", я написал DLL, реализующую диалог ввода строки.

                Что умеет данная DLL:
                - Создавать и вызывать имеющийся у нее в ресурсах диалог.
                - Возвращать введенную пользователем строку.
                - Может быть безболезненно использована несколькими потоками одного процесса.
                - Поддерживать ANSI и Unicode вызовы.

                DLL экспортирует 2 функции:
                ExpandedWrap disabled
                  GetInputStringA(HWND hWnd, LPSTR lpText, UINT uSize, LPCSTR lpCaption);
                ExpandedWrap disabled
                  GetInputStringW(HWND hWnd, LPWSTR lpText, UINT uSize, LPCWSTR lpCaption);
                Объединенные в хедере под один define
                ExpandedWrap disabled
                  #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:
                ExpandedWrap disabled
                  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, на вопросы отвечу. О багах сообщайте :) Тестировалось все под Win XP Pro.

                28.03.04 BUGFIX: Исправлена ошибка с EM_LIMITTEXT. Тескт должен быть ограничен по длине буфера -1 TCHAR. Спасибо ~Archer~у.
                Сообщение отредактировано: Uncle_Bob -

                Прикреплённый файлПрикреплённый файлextui32.zip (16.21 Кбайт, скачиваний: 761)
                  Здесь просто Release версия, готовая для использования + хедер + lib (для Visual Studio)
                  Прикреплённый файлПрикреплённый файлRelease.zip (3.24 Кбайт, скачиваний: 702)
                    Опробовал. Огромное спасибо автору!!! Этой вещицы как раз нехватало.
                    Но есть один вопрос: длина строки указываемая в GetInputString есть максимальная длина вводимого текста, но в качестве релультата всегда возвращается на один символ меньше. Т.е. если ввести для инициализации Hello World и просто высести ту же строку то по любому получается Hello Worl. Если на 1 увеличить длину, то можно будет ввести еще один символ после Hello World но он будет обрезан после преобразования... Это баг или я что-то не так делаю?
                      А расскажите пожалуйста про неявное подключение?

                      У меня есть только файлы dll и lib, и то lib сгенерирован из dll с помощью программы Dll2Lib, h-файла нету. Хочу подключить библиотеку неявно, скажите, такое подключение может работать?
                      #pragma comment(lib, "NeuroNet.lib")

                      У меня почему-то не работает, походу не видит библиотеку, не отображает её в ClassView. А может просто функции не так вызываю...

                      Пробовала подключить через Project -> Linker -> Input -> Additional Dependencies, всё равно библиотеку не видит.
                      Может дело в том, что нету h-файла? Тогда вопрос - как его получить??

                      Или я просто что-то не правильно делаю?
                        Ninetta
                        Честно говоря, тоже один раз пробовал Dll2Lib - ничего не вышло. Может, готовить его не умею.
                        Если есть DLL - грузи её при старте приложения через LoadLibrary, и всё.
                        0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                        0 пользователей:


                        Рейтинг@Mail.ru
                        [ Script execution time: 0,0495 ]   [ 15 queries used ]   [ Generated: 29.03.24, 10:36 GMT ]