Программирование для Windows NT (том 2)

       

Динамический импорт функций во время выполнения приложения


В некоторых случаях невозможно выполнить динамическую компоновку на этапе редактирования. Вы можете, например, создать приложение, которое состоит из основного модуля и дополнительных, реализованных в виде DLL-библиотек. Состав этих дополнительных модулей и имена файлов, содержащих DLL-библиотеки, может изменяться, при этом в приложение могут добавляться новые возможности.

Если вы, например, разрабатываете систему распознавания речи, то можете сделать ее в виде основного приложения и набора DLL-библиотек, по одной библиотеке для каждого национального языка. В продажу система может поступить в комплекте с одной или двумя библиотеками, но в дальнейшем пользователь сможет купить дополнительные библиотеки и, просто переписав новые библиотеки на диск, получить возможность работы с другими языками. При этом основной модуль приложения не может "знать" заранее имена файлов дополнительных DLL-библиотек, поэтому статическая компоновка с использованием библиотеки импорта невозможна.

 Однако приложение может в любой момент времени загрузить любую DLL-библиотеку, вызвав специально предназначенную для этого функцию программного интерфейса Windows с именем LoadLibrary. Приведем ее прототип:

HINSTANCE WINAPI LoadLibrary(LPCSTR lpszLibFileName);

Параметр функции является указателем на текстовую строку, закрытую двоичным нулем. В эту строку перед вызовом функции следует записать путь к файлу DLL-библиотеки или имя этого файла. Если путь к файлу не указан, при поиске выполняется последовательный просмотр следующих каталогов:

  • каталог, из которого запущено приложение;
  • текущий каталог;
  • 32-разрядный системный каталог Microsoft Windows NT;
  • 16-разрядный системный каталог;
  • каталог в котором находится операционная система Windows NT;
  • каталоги, перечисленные в переменной описания среды PATH
  • Если файл DLL-библиотеки найден, функция LoadLibrary возвращает идентификатор модуля библиотеки. В противном случае возвращается значение NULL. При этом код ошибки можно получить при помощи функции GetLastError.


    Функция LoadLibrary  может быть вызвана разными приложениями для одной и той же DLL-библиотеки несколько раз. В этом случае в среде операционной системы Microsoft Windows версии 3.1 загрузка DLL-библиотеки выполняется только один раз. Последующие вызовы функции LoadLibrary приводят только к увеличению счетчика использования DLL-библиотеки. Что же касается Microsoft Windows NT, то при многократном вызове функции LoadLibrary различными процессами функция инициализации DLL-библиотеки получает несколько раз управление с кодом причины вызова, равным значению DLL_PROCESS_ATTACH.

    В качестве примера приведем фрагмент исходного текста приложения, загружающего DLL-библиотеку из файла DLLDEMO.DLL:

    typedef HWND (WINAPI *MYDLLPROC)(LPSTR);

    MYDLLPROC    GetAppWindow;



    HANDLE       hDLL;

    hDLL = LoadLibrary("DLLDEMO.DLL");

    if(hDLL != NULL)

    {

      GetAppWindow = (MYDLLPROC)GetProcAddress(hDLL,

        "FindApplicationWindow");

      if(GetAppWindow != NULL)

      {

        if(GetAppWindow(szWindowTitle) != NULL)

          MessageBox(NULL, "Application window was found",

            szAppTitle, MB_OK | MB_ICONINFORMATION);

        else

          MessageBox(NULL, "Application window was not found",

            szAppTitle, MB_OK | MB_ICONINFORMATION);

      }

      FreeLibrary(hDLL);

    }

    Здесь вначале с помощью функции LoadLibrary выполняется попытка загрузки DLL-библиотеки DLLDEMO.DLL. В случае успеха приложение получает адрес точки входа для функции с именем FindApplicationWindow, для чего используется функция GetProcAddress. Этой функцией мы займемся немного позже.

    Если точка входа получена, функция вызывается через указатель GetAppWindow.

    После использования DLL-библиотека освобождается при помощи функции FreeLibrary, прототип который показан ниже:

    void WINAPI FreeLibrary(HINSTANCE hLibrary);

    В качестве параметра этой функции следует передать идентификатор освобождаемой библиотеки.

    При освобождении DLL-библиотеки ее счетчик использования уменьшается. Если этот счетчик становится равным нулю (что происходит, когда все приложения, работавшие с библиотекой, освободили ее или завершили свою работу), DLL-библиотека выгружается из памяти.



    Каждый раз при освобождении DLL-библиотеки вызывается функция DLLEntryPoint с параметрами DLL_PROCESS_DETACH  или DLL_THREAD_DETACH, выполняющая все необходимые завершающие действия.

    Теперь о функции GetProcAddress.

    Для того чтобы вызвать функцию из библиотеки, зная ее идентификатор, необходимо получить значение дальнего указателя на эту функцию, вызвав функцию GetProcAddress:

    FARPROC WINAPI GetProcAddress(HINSTANCE hLibrary,

      LPCSTR lpszProcName);

    Через параметр hLibrary вы должны передать функции идентификатор DLL-библиотеки, полученный ранее от функции LoadLibrary.

    Параметр lpszProcName является дальним указателем на строку, содержащую имя функции или ее порядковый номер, преобразованный макрокомандой MAKEINTRESOURCE.

    Приведем фрагмент кода, в котором определяются адреса двух функций. В первом случае используется имя функции, а во втором - ее порядковый номер:

    FARPROC lpMsg;

    FARPROC lpTellMe;

    lpMsg = GetProcAddress(hLib, "Msg");

    lpTellMe = GetProcAddress(hLib, MAKEINTRESOURCE(8));

    Перед тем как передать управление функции по полученному адресу, следует убедиться в том, что этот адрес не равен NULL:

    if(lpMsg != (FARPROC)NULL)

    {

      (*lpMsg)((LPSTR)"My message");

    }

    Для того чтобы включить механизм проверки типов передаваемых параметров, вы можете определить свой тип - указатель на функцию, и затем использовать его для преобразования типа адреса, полученного от функции GetProcAddress:

    typedef int (PASCAL *LPGETZ)(int x, int y);

    LPGETZ lpGetZ;

    lpGetZ = (LPGETZ)GetProcAddress(hLib, "GetZ");

    А что произойдет, если приложение при помощи функции LoadLibrary попытается загрузить DLL-библиотеку, которой нет на диске?

    В этом случае операционная система Microsoft Windows NT выведет на экран диалоговую панель с сообщением о том, что она не может найти нужную DLL-библиотеку. В некоторых случаях появление такого сообщения нежелательно, так как либо вас не устраивает внешний вид этой диалоговой панели, либо по логике работы вашего приложения описанная ситуация является нормальной.



    Для того чтобы отключить режим вывода диалоговой панели с сообщением о невозможности загрузки DLL-библиотеки, вы можете использовать функцию SetErrorMode, передав ей в качестве параметра значение SEM_FAILCRITICALERRORS:

    UINT nPrevErrorMode;

    nPrevErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS);

    hDLL = LoadLibrary("DLLDEMO.DLL");

    if(hDLL != NULL)

    {

      // Работа с DLL-библиотекой

      . . .

    }

    SetErrorMode(nPrevErrorMode);

    Приведем прототип функции SetErrorMode:

    UINT WINAPI SetErrorMode(UINT fuErrorMode);

    Эта функция позволяет отключать встроенный в Windows обработчик критических ошибок. В качестве параметра этой функции можно указывать комбинацию следующих значений:

    Значение

    Описание

    SEM_FAILCRITICALERRORS

    Операционная система Microsoft Windows NT  не выводит на экран сообщения обработчика критических ошибок, возвращая приложению соответствующий код ошибки

    SEM_NOGPFAULTERRORBOX

    На экран не выводится сообщение об ошибке защиты памяти. Этот флаг может использоваться только при отладке приложений, если они имеют собственный обработчик такой ошибки

    SEM_NOOPENFILEERRORBOX

    Если Microsoft Windows NT  не может открыть файл, на экран не выводится диалоговая панель с сообщением об ошибке

    Функция SetErrorMode возвращает предыдущий режим обработки ошибки.


    Содержание раздела