15 мая 2023 года "Исходники.РУ" отмечают своё 23-летие!
Поздравляем всех причастных и неравнодушных с этим событием!
И огромное спасибо всем, кто был и остаётся с нами все эти годы!

Главная Форум Журнал Wiki DRKB Discuz!ML Помощь проекту


Как вызывать 16-битный код из 32-битного под Windows 95

Возможно, Вам как разработчику, иногда необходимо работать с 16-битными динамическими библиотеками (DLL) из приложения Win32. Особенно это важно, когда нет исходного кода DLL, которую необходимо портировать в Win32. В данной статье описывается механизм, при помощи которого 32-битные DLL могут вызывать 16-битными DLL. Этот механизм называется "thunk" а метод, включённый в Windows 95 называется "flat thunk".

Далее представлены три основных шага при создании flat thunk:

  1. Создаём thunk скрипт.
  2. Компилируем 32-битную DLL.
  3. Компилируем 16-битную DLL.

Flat thunk состоит из 32-битной и 16-битной DLL, которые работают вместе. Приложение Win32 работает с 32-битной DLL, а 32-битная DLL в свою очередь вызывает экспортированные функции в 16-битной DLL. Когда функция в 16-битной DLL завершает выполнение, то она возвращает управление обратно в 32-битную DLL, которая в свою очередь возвращает управление обратно в приложение Win32. 32-битные и 16-битные DLL работают вызывая 32-битное и 16-битное ядро в Windows 95 соответственно, для обработки необходимых деталей низкого уровня, чтобы создать переход от 32-битного в 16-битный код и обратно.

Разработка нового flat thunk влечёт за собой создание thunk скрипта (файл .thk). Этот скрипт создаётся при помощи Thunk компилятора в файле на языке ассемблера, который затем компилируется дважды; по разу с каждым из флагов -DIS_32 и -DIS_16. Это позволяет создать как 32-битный, так и 16-битный объектные модули. Эти объектные модули компонуются (линкуются) в 32-битную и 16-битную DLL-ки, соответственно. Следующая схема показывает все файлы, вовлечённые в процесс создания DLL:

                         +------------+
                         | 32to16.thk |
                         +------------+
                               |
                         +------------+
                         | 32to16.asm |
                         +------------+
                           /         \ 
                  -DIS_32 /           \ -DIS_16
                        /              \ 
                  +-----------+  +-----------+
                  | 32THK.obj |  | 16THK.obj |
                  +-----------+  +-----------+
                        /                 \ 
        +-------+    +-------+             +-------+
        | APP32 | -> | DLL32 | -- THUNK -- | DLL16 |
        +-------+    +-------+             +-------+ 

Инструменты, необходимые для создания Flat Thunk

  • Компилятор Microsoft Visual C++ версии 1.5x (16-битный) для создания 16-битного thunk. 16-битный thunk, это 16-битная DLL.
  • Компилятор Microsoft Visual C++ версии 2.x или выше (32-битный) для создания 32-битного thunk. 32-битный thunk, это 32-битная DLL.
  • Thunk компилятор (Thunk.exe) из Microsoft Win32 SDK для компиляции thunk скрипта.
  • Microsoft Macro Assembler (MASM) версии 6.1 или выше для трансляции выходного файла thunk компилятора на языке ассемблера.
  • 16-битный файл Rc.exe из директории BINW16 пакета Microsoft Win32 SDK для создания 16-битной thunk DLL версии 4.0.

Создание Thunk скрипта

Вам необходимо создать скрипт, который может быть использован в Thunk компиляторе для создания thunk. Thunk скрипт, это текстовый файл, который содержит объявление типов, прототипы функций, которые Вы хотите вызывать через thunk-и, а так же спецификацию направления параметров для каждой функции. Например, некоторые функции требуют как входные так и выходные параметры, в то время, как другие нуждаются только во входных параметрах. Thunk скрипт использует специальный синтаксис для описания, какого типа параметры будут: только входные, только выходные, или как входные, так и выходные.

thunk скрипт для 32->16 thunk-ов начинается со следующего выражения:

enablemapdirect3216 = true;

Thunk компилятор ожидает, что 32-битная часть thunk объявлена как __stdcall, а 16-битная как __far __pascal. (Объявление WINAPI заботится об обоих частях.) __cdecl и __fastcall не поддерживаются Thunk компилятором. Обратите внимание, что вообщето Thunk компилятор не понимает ключевые слова __far, __pascal, или __stdcall; однако они применены.

Следующий пример показывает thunk скрипт для функции, не имеющей параметров:

   enablemapdirect3216 = true;

   void MyThunk16()
   {
   } 

Эквивалентное объявление было бы:

   C   language:  void WINAPI MyThunk16(void);
   C++ language:  extern "C" void WINAPI MyThunk16(); 

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

   enablemapdirect3216 = true;

   typedef int   BOOL;
   typedef char *LPSTR;

   BOOL MyThunk16(LPSTR lpstrInput, LPSTR lpstrOutput)
   {
      lpstrInput  = input;    // optional; input is default
      lpstrOutput = output;
   } 

Выражение "lpstrOutput = output" говорит Thunk компилятору, что 16-битная функция возвращает адрес, который должен быть конвертирован из указателя selector:offset в 32-битный линейный адрес.

Следующий thunk скрипт использует более сложные типы параметров, такие как структуры. Данный пример так же показывает, как указывать входные и выходные параметры.

   enablemapdirect1632 = true;

   typedef unsigned int UINT;
   typedef char *LPSTR;

   typedef struct _POINT {
      UINT x;
      UINT y;
   }POINT, *LPPOINT;

   typedef struct _CIRCLE {
      POINT center;
      UINT  radius;
   }CIRCLE, *LPCIRCLE;

   void MyThunk32( LPCIRCLE lpCircleInOut)
   {
      lpCircleInOut = inout;
   } 

Выражение "lpCircleInOut = inout" говорит компилятору скрипта, что этот указатель будет использоваться для входа и выхода. Это заставляет Thunk компилятор преобразовать lpCircleInOut из 32-битного линейного адреса в указатель selector:offset при вызове функции и обратно в 32-битный линейный адрес, когда функция возвращает управление. Преобразование делает thunk, созданный Thunk компилятором.

Использование Thunk компилятора

Параметры Thunk компилятора следующие:

thunk.exe /options <inputfile> -o <outputfile>

Следующая командная строка показывает, как компилировать 32->16 thunk скрипт. Входным файлом является thunk скрипт с именем 32to16.thk, в итоге появится файл на ассемблере с именем 32to16.asm.

thunk -t thk 32to16.thk -o 32to16.asm

Опция "-t thk" указывает Thunk компилятору, чтобы тот делал префикс "thk_." для thunk функций в ассемблерном файле. Этот префик используется когда компонуется несколько thunk скриптов в пару DLL, и используется при создании пары DLL, которые содержат как 32->16 так и 16->32 thunk-и. Каждый thunk скрипт должен иметь уникальный префикс.

Создание 32-битной DLL

  1. В функции DllMain Вашей 32-битной DLL, надо сделать вызов функции, созданной Thunk компилятором и названной thk_ThunkConnect32 для каждого случая (dwReason) когда DllMain вызывается, как показано здесь ("thk" это префикс из Thunk компилятора -t параметр):
          // прототип для функции в файле .obj из thunk скрипта
          BOOL WINAPI thk_ThunkConnect32(LPSTR     lpDll16,
                                         LPSTR     lpDll32,
                                         HINSTANCE hDllInst,
                                         DWORD     dwReason);
    
          BOOL WINAPI DllMain(HINSTANCE hDLLInst,
                              DWORD     dwReason,
                              LPVOID    lpvReserved)
          {
             if (!thk_ThunkConnect32("DLL16.DLL", "DLL32.DLL",
                                     hDLLInst, dwReason))
             {
                return FALSE;
             }
             switch (dwReason)
             {
                case DLL_PROCESS_ATTACH:
                   break;
    
                case DLL_PROCESS_DETACH:
                   break;
    
                case DLL_THREAD_ATTACH:
                   break;
    
                case DLL_THREAD_DETACH:
                   break;
             }
             return TRUE;
          } 

     

  2. Включите следующие строки в секцию EXPORTS файла .def 32-битной DLL. Например:
       thk_ThunkData32
  3. Экспортируйте функции, которые будет вызывать приложение Win32. Для этого можно использовать как файл .def так и ключевое слово __declspec(dllexport). Убедитесь, что функции объявлены как __stdcall (или WINAPI). Если 32-битная DLL написана в C++, так же убедитесь, что функции определены как extern "C".
  4. Скомпилируйте thunk скрипт как показано ниже (если ещё не скомпилировали):
          thunk -t thk 32to16.thk -o 32to16.asm
  5. Оттранслируйте ассемблерный файл, созданный Thunk компилятором как 32-битный объектный модуль. Например:
          ml /DIS_32 /c /W3 /nologo /coff /Fo thk32.obj 32to16.asm
  6. Скомпонуйте ( link ) этот объектный модуль как часть 32-битной DLL.
  7. Скомпонуйте библиотеку Thunk32.lib как часть 32-битной DLL. Эта 32-битная библиотека содержится в Win32 SDK.

Создание 16-битной DLL

  1. 16-битная DLL должна экспортировать функцию "DllEntryPoint". Эта функция должна вызывать функцию thk__ThunkConnect16, созданную Thunk компилятором, каждый раз, когда вызывается DllEntryPoint:
          // прототип для функции в файле .obj из thunk скрипта
          BOOL WINAPI __export thk_ThunkConnect16(LPSTR lpDll16,
                                                  LPSTR lpDll32,
                                                  WORD  hInst,
                                                  DWORD dwReason);
    
          BOOL WINAPI __export DllEntryPoint(DWORD dwReason,
                                             WORD  hInst,
                                             WORD  wDS,
                                             WORD  wHeapSize,
                                             DWORD dwReserved1,
                                             WORD  wReserved 2)
          {
             if (!thk_ThunkConnect16("DLL16.DLL",
                                     "DLL32.DLL",
                                     hInst,
                                     dwReason))
             {
                return FALSE;
             }
             return TRUE;
          }

     

  2. Добавьте следующие строки в секцию IMPORTS файла .def 16-битной DLL. Например:
          C16ThkSL01      = KERNEL.631
          ThunkConnect16  = KERNEL.651
    
  3. Следующие строки следует добавить в секцию EXPORTS файла .def 16-битной DLL. THK_THUNKDATA16 определена в объектном файле, который был оттранслирован Thunk компилятором. Оба эти символа должны иметь ключевое слово RESIDENTNAME, но не могут иметь любое порядковое число.
          THK_THUNKDATA16 @1  RESIDENTNAME
          DllEntryPoint   @2  RESIDENTNAME
    
  4. Добавьте thunk функции к инструкции EXPORTS .def файла. Убедитесь, что они определены и объявлены как __far __pascal __export (или WINAPI __export). Если DLL написана в C++, убедитесь, что объявили их как extern "C". 32-битная часть thunk будет вызывать эти функции.
  5. Скомпилируйте thunk скрипт как показано ниже (если ещё не скомпилировали):
          thunk -t thk 32to16.thk -o 32to16.asm
    
  6. Оттранслируйте ассемблерный файл, созданный Thunk компилятором как 16-битный объектный модуль. Например:
          ml /DIS_16 /c /W3 /nologo /Fo thk16.obj 32to16.asm
    
  7. Скомпонуйте ( link ) этот объектный модуль как часть 16-битной DLL.
  8. Создайте 16-bit DLL версии 4.0. Для этого используйте компилятор ресурсов (Rc.exe). Вот синтаксис командной строки:

    rc -40 <DLL file>

    Опция -40 доступна в Компиляторе ресурсов, которы поставляется с Win32 SDK.

    ЗАМЕЧАНИЕ: Необходимо запускать файл Rc.exe в директории BINW16, чтобы DLL была помечена как версии 4.0. Файл Rc.exe, который поставляется с 16-битными версиями Microsoft Visual C++ не помечают DLL как версии 4.0.

ССЫЛКИ

Информацию о том, как отлаживать flat thunks смотрите в статье из Microsoft Knowledge Base:

Q133722 Как отлаживать Flat Thunks