Текущий раздел:

Описания RDS

Руководство программиста

Глава 2. Создание моделей блоков

§2.2. Главная функция DLL и файлы заголовков

# Руководство программиста

## Глава 2. Создание моделей блоков

### §2.2. Главная функция DLL и файлы заголовков

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

Для создания библиотеки с моделью блока в файл исходного текста необходимо включить по крайней мере два файла заголовков: «windows.h», содержащий стандартные описания Windows, и «RdsDef.h» из комплекта RDS (папка «Include\»), содержащий описания типов и констант, необходимые для моделей блоков. Для создания простейших моделей ничего больше не требуется. Если в модели будут использоваться какие-либо сервисные функции RDS, надо предпринять несколько дополнительных шагов, чтобы получить к ним доступ.

Сервисные функции RDS – это обычные функции, экспортированные из исполняемого файла «Rds.exe» или «Rds64.exe». Все они имеют тип вызова RDSCALL, описанный в файле «RdsDef.h». Этому типу полностью соответствуют CALLBACK в Windows API (аргументы функции передаются в стеке справа налево, стек освобождается вызванной функцией). Для получения адресов сервисных функций (указателей на функции) следует использовать функцию Windows API GetProcAddress.

Проще всего получить указатели на все необходимые сервисные функции в момент загрузки библиотеки с моделями блоков. Допустим, что для написания модели какого-либо блока требуется сервисная функция rdsMessageBoxW, выводящая окно с сообщением, заданным строкой в кодировке UTF16 (при написании моделей она может быть удобнее стандартной функции Windows API MessageBoxW, поскольку для функции RDS не нужно указывать окно-владелец, и, кроме того, при вызове этой функции из режима расчета расчет не останавливается). Ниже приведен фрагмент исходного текста библиотеки с главной функцией DLL, включающей команды получения доступа к этой сервисной функции.

      #include <windows.h>  // Стандартные описания Windows
      #include <RdsDef.h>   // Описания RDS

      // Описание переменной-указателя на функцию
      RDS_IWsWsI rdsMessageBoxW;

      // Главная функция DLL
      int WINAPI DllMain(
             HINSTANCE /*hinst*/,   // Дескриптор модуля этой DLL
             unsigned long reason,  // Причина вызова (загрузка или
                                    // выгрузка DLL)
             void* /*lpReserved*/   // Способ загрузки – динамический
                                    // или статический
        )
      { if(reason==DLL_PROCESS_ATTACH) // Загрузка DLL
          // Получение указателя на функцию rdsMessageBox
          rdsMessageBoxW=(RDS_IWsWsI)GetProcAddress(GetModuleHandle(NULL),
                                                    "rdsMessageBoxW");
        // Возврат – успешное завершение
        return 1;
      }
      //------------------------------------------
      // ... здесь должны быть функции моделей ...
      //------------------------------------------

Сначала описывается глобальная переменная rdsMessageBoxW – указатель на функцию соответствующего типа. Тип RDS_IWsWsI описан в файле «RdsDef.h» следующим образом:

      typedef int (RDSCALL *RDS_IWsWsI)(RDSWCSTR,RDSWCSTR,RDSINT32);

Таким образом, переменная rdsMessageBoxW становится указателем на функцию, получающую в качестве параметров две строки (RDSWCSTR – тип, эквивалентный типу const wchar_t*) и целое число. Теперь нужно присвоить этой переменной указатель на одноименную функцию в исполняемом модуле RDS – для этого мы используем главную функцию DLL.

Каждая динамически подключаемая библиотека в Windows обязательно имеет главную функцию, которая автоматически вызывается при загрузке и выгрузке этой библиотеки, а также при создании и уничтожении потоков в загрузившем библиотеку процессе. Она обычно имеет название DllMain или DllEntryPoint и всегда принимает три параметра и возвращает целое число:

      int WINAPI DllMain(
            HINSTANCE hinst,      // Дескриптор модуля этой DLL
            unsigned long reason, // Причина вызова (загрузка или
                                  // выгрузка DLL)
            void* lpReserved)     // Способ загрузки – динамический
                                  // или статический

В первом параметре функции hinst передается дескриптор (уникальный идентификатор) модуля данной DLL – он может понадобиться, если функции библиотеки будут загружать из нее же какие-либо ресурсы (строки, растровые изображения и т.п.). Во втором параметре reason передается одна из четырех целых констант, описанных в файлах заголовков Windows API, указывающая на причину вызова главной функции DLL: загрузка библиотеки (DLL_PROCESS_ATTACH), ее выгрузка (DLL_PROCESS_DETACH), создание или уничтожение потока внутри процесса (DLL_THREAD_ATTACH и DLL_THREAD_DETACH соответственно). Нас будет интересовать именно загрузка библиотеки – это самый подходящий момент для инициализации ее глобальных переменных. Наконец, третий параметр lpReserved указывает на способ загрузки и выгрузки DLL – нас он интересовать не будет. Возвращаемое главной функцией целое число информирует загружающий библиотеку процесс об успешности инициализации библиотеки: если все в порядке, нужно вернуть ненулевое число, если при инициализации произошли какие-либо ошибки и библиотеку нельзя использовать, нужно вернуть 0. Моменты вызова главной функции DLL и ее параметры подробно описаны в документации по Windows API.

В приведенном выше примере в главной функции DLL DllMain при загрузке библиотеки (когда параметр reason равен константе DLL_PROCESS_ATTACH из стандартных описаний Windows) глобальной переменной rdsMessageBoxW присваивается указатель на функцию с именем «rdsMessageBoxW», экспортированную из главного модуля. Для получения дескриптора, то есть уникального идентификатора, главного модуля приложения используется вызов API GetModuleHandle, а для получения указателя на функцию с заданным именем из этого модуля – GetProcAddress. Затем главная функция возвращает значение 1, что сигнализирует об успешной загрузке библиотеки.

В этом примере используется только один параметр главной функции – причина вызова reason. Два других параметра – дескриптор модуля DLL hinst и дополнительный параметр lpReserved, указывающий способ загрузки или выгрузки DLL, не используются, поэтому их имена записаны внутри комментария. При создании библиотек с моделями блоков RDS эти параметры используются крайне редко. Если же они, тем не менее, понадобятся, комментарий можно будет убрать.

После получения указателя на функцию, ее можно вызывать непосредственно по имени, например:

      rdsMessageBoxW(L"Проверка вызова функции", // Текст сообщения
                     L"Сообщение",               // Заголовок сообщения
                     MB_OK|MB_ICONINFORMATION);  // Кнопка OK и иконка "i"

Рис. 14. Результат вызова функцииrdsMessageBoxW

Окно сообщения, выводимое этой функцией (рис. 14), знакомо практически каждому программисту, когда-либо создававшему программу для Windows. Префикс «L» перед строками в кавычках в вызове функции означает, что строки записаны в кодировке UTF16. Если бы для вывода сообщения использовалась сервисная функция rdsMessageBoxA, строки необходимо было бы записать в кодировке UTF8 (пока еще не все компиляторы поддерживают префиксы для этой кодировки).

Если для написания моделей блоков требуется несколько сервисных функций, необходимо описать столько глобальных переменных и сделать столько вызовов GetProcAddress, сколько функций предполагается использовать. При большом количестве функций это не очень удобно, поэтому в таких случаях лучше включить в исходный текст заголовочный файл «RdsFunc.h», который позволяет получить доступ сразу ко всем сервисным функциям RDS. Ниже приведен пример использования этого файла (отличия от предыдущего примера выделены цветом).

      #include <windows.h>
      #include <RdsDef.h>

      // Подготовка описаний сервисных функций
      #define RDS_SERV_FUNC_BODY GetInterfaceFunctions
      #include <RdsFunc.h>

      // Главная функция DLL
      int WINAPI DllMain(HINSTANCE /*hinst*/,
                               unsigned long reason,
                               void* /*lpReserved*/)
      { if(reason==DLL_PROCESS_ATTACH) // Загрузка DLL
          { // Получение доступа к функциям
            if(!GetInterfaceFunctions())
              MessageBoxW(NULL,L"Нет доступа к функциям",L"Ошибка",MB_OK);
          }
        return 1;
      }
      //------------------------------------------
      // ... здесь должны быть функции моделей ...
      //------------------------------------------

В этом примере перед включением файла «RdsFunc.h» находится описание вида

      #define RDS_SERV_FUNC_BODY имя_функции_пользователя

Наличие этого описание приведет к тому, что в месте включения файла (в приведенном примере – в следующей строке) будет вставлен полный набор переменных-указателей на все сервисные функции RDS, после которого будет автоматически сформирована дополнительная функция с именем, указанным пользователем (в данном случае – GetInterfaceFunctions), которая получает указатели на функции и присваивает их этим переменным. Теперь, вместо того, чтобы вручную присваивать указатели на функции глобальным переменным, нужно просто вызвать эту функцию внутри DllMain и, при желании, проверить возвращаемое логическое (BOOL) значение. Возврат FALSE указывает на то, что не все указатели на сервисные функции удалось получить (чаще всего это говорит о том, что версия исполняемого файла RDS более старая, чем версия описаний в «RdsFunc.h», и некоторые сервисные функции в ней еще не реализованы).

В примере выше при невозможности получить указатели на сервисные функции пользователю выводится сообщение об ошибке при помощи функции Windows API MessageBoxW. В «RdsFunc.h» описаны макроопределения RDS_SERV_ERROR_MSGW и RDS_SERV_ERROR_MSGA для вывода таких сообщений с указанием требуемой версии RDS, т.е. версии, на работу с которой расчитаны описания в файлах «RdsDef.h» и «RdsFunc.h».

Если проект DLL состоит из нескольких модулей, заголовочный файл «RdsFunc.h» следует включить в те из них, которые используют сервисные функции RDS. При этом следует помнить, что описание константы «#define RDS_SERV_FUNC_BODY» может находиться только в одном модуле. При его наличии в нескольких модулях будет создано несколько комплектов глобальных переменных с одинаковыми именами и несколько одинаковых функций, что приведет к ошибкам при компоновке библиотеки.

По понятным причинам, описание «#define RDS_SERV_FUNC_BODY» и файл «RdsFunc.h» можно использовать только тогда, когда модель блока пишется на языке C или C++. В других языках необходимо получать указатель на каждую сервисную функцию вручную, при помощи функции Windows API GetProcAddress, как описано в первом примере.

Замечание для Borland C++ Builder. Если в моделях блоков будут использоваться функции и классы библиотеки VCL, и если модели будут открывать собственные окна, в главной функции DLL необходимо добавить два присваивания (выделены цветом):

      int WINAPI DllMain(HINSTANCE /*hinst*/,
                               unsigned long reason,
                               void* /*lpReserved*/)
      { switch(reason)
          { case DLL_PROCESS_ATTACH: // Загрузка DLL
              if(!GetInterfaceFunctions())
                MessageBoxW(NULL,L"Нет доступа к функциям",L"Ошибка",MB_OK);
              else
                Application->Handle=rdsGetAppWindowHandle();
              break;
            case DLL_PROCESS_DETACH: // Выгрузка DLL
              Application->Handle=NULL;
              break;
          }
        return 1;
    }

Здесь при загрузке библиотеки инициализируется глобальная переменная библиотеки VCL Application: ее свойству Handle присваивается дескриптор главного окна RDS, возвращаемый сервисной функцией rdsGetAppWindowHandle. Это необходимо для того, чтобы привязать все окна, открываемые в этой DLL, к приложению-владельцу, то есть к RDS (подробнее это описано в документации к Borland C++ Builder и библиотеке VCL). При выгрузке библиотеки (когда параметр reason принимает значение DLL_PROCESS_DETACH) необходимо очистить это свойство, присвоив ему NULL.