Windows 9xで16/32ビットDLLを相互に利用する方法

戻る

Windows3.1用に作成したDLLをWindows95で32ビットアプリケーションで利用したい または,32ビットDLLを作成したが,16ビットアプリケーションで利用したいといった 状況をたまに見かけます.依然として,Windows3.1を利用したシステムは多く存在 し,Windows3.1で動作することがアプリケーションに,Windows95では32ビットで利用 できる便利な機能も利用するといった要求も少なくありません.
また,16ビットコードは32ビットコードと比較してサイズが小さくなるため, アプリケーション全体のファイルサイズを小さくしたいときには16ビットでコードを 利用することは有効な手段として利用できます.

16-1 16/32ビットコードが混在したWindows95

Windows95 は,Windows3.1の次期バージョンとして開発されており,Windows3.1の 16ビットアプリケーションを可能な限り実行できる必要がありました.また,マシンの スペックも,できる限りWindows3.1 が動作する環境で動作させる必要がありました. これらの条件を満足させるには,OS内部のコードを完全に32ビット化することが できませんでした.このため,OS内部では16ビットコードと32ビットコードが 入り交じって動作することとなり,16ビットコードと32ビットコードが相互に利用 できる機能は,Windows95を開発するためにも重要だったのではないかと 思われます.
実際,Windows3.1やWindowsNTのサンクに比べてWindows95のサンクは高機能で, サンクコンパイラのようなツールを利用することで簡単に利用できるように配慮されて います.

16-2 サンクAPI

サンクは,Windows で動作するアプリケーションが16ビットから32ビットにシフト する過度期に必要となる機能です.Microsoft社 はすべてのアプリケーションが 32ビット化されることを望んでいるのでしょうが,そうなるにはもう少し時間がかかる のではないかと思われます.しかし,今後Windows95とWindowsNTが統合化され, 16ビットアプリケーションを誰も使わなくなった(切り捨てられた)ときサンクAPIも その役目を終えることになるでしょう.

●WindowsNTのサンク(汎用サンク)
WindowsNTでは,汎用サンクを利用することができます.
汎用サンクは,Windows95でも利用できますが,アドレス変換などの処理を追加する 必要があり,使い勝手がよくありません.しかし,Windows95でも動作するため, Windows95/NTの双方で動作させる必要があるときには有効に利用できます.
ただし,汎用サンクはフラットサンクとは異なり,16ビットから32ビットを利用する ことしかできません.
(* 詳細は,Win32サブルーチンズ第2章を参照)

●Windows3.1のサンク(ユニバーサルサンク)
Windows3.1にWin32sをインストールすることにより,32ビットアプリケーション を実行させることができるようになります.これによりマルチプラットホームの 32ビットアプリケーションを作成することも可能になりますが,Win32sは制限も多く, 1つのアプリケーションを実行させるために,ユーザのマシンにWin32sをインストール させることが困難なこともあります.このため,結局16ビットアプリケーションのまま 動作させることになってしまうこともあります.
Win32s では,ユニバーサルサンクで制限の多い32ビットコードを16ビットコードで 補うことができます.しかしながら,現状ではほとんどのアプリケーションがWindows95 やWindowsNT に対応させる方向に進んでいるため,Windows3.1版の32ビット アプリケーションは少数派になってきているのではないかと思われます.

●Windows95のサンク(フラットサンク)
Windows3.1のユニバーサルサンクは,32ビットアプリケーションから16ビットの機能を ,WindowsNTの汎用サンクは,16ビットアプリケーションから32ビットの機能を利用する ために利用します.Windows95のフラットサンクは,16ビットと32ビットの機能を双方 から利用することを可能にします.また,他のサンクと比較してサンクコンパイラや マクロアセンブラなどのツールを利用する必要がありますが,複雑な処理を必要と しません.関数の呼び出し処理はサンクコンパイラがスクリプトファイルから生成した 処理が行うため,関数の実体が16ビットなのかまたは32ビットであるかを意識せずに 呼び出すことができるようになります.
ただし,サンクを利用するプロセスが16ビットの場合,スタックサイズは16ビット アプリケーションの制限(64KB)以内に制限されます.また,32ビット側のDLLであっても マルチスレッドで処理を行うことができません.その他,GDIやダイアログボックスを 表示するなどのWin16 APIでも実行可能な処理はできる限り32ビットDLLには持ち込 まないようにする必要があります.実際にどのAPIが正常に動作するかは動作させて 見なければわからないところもありますが,32ビットDLLであっても16ビットプロセス で動作するということは,16ビットWindowsが持つ制限(たとえばプリエンティブな マルチタスクでない)といったことも考慮して処理を組み込む必要があります.
逆に,32ビットアプリケーションから16ビットDLLを実行する場合,16ビットの処理 は再入可能ではありませんので,処理の流れを16ビットアプリケーションから呼び出す ときと同じような流れになるようにする必要があります.

16-3 16ビットコードに頼る必要があるWindows95

Windows95の内部では,16ビットモジュールが多く動作しています.
今やマイナーな存在となった,ファイルマネージャも16ビットコードに異存して います.さらに,WINHELP.EXE は WOW(Windows On Win32) を利用しているようです. これは,32ビットコードだけでは,実現できない機能がまだ多く潜んでいることを 表わしているのではないでしょうか?

●サポートされない Network DDE
Windows95の提供する機能のうち Network DDE は,Windows for Workgroups で 実装された16ビットのAPIのみをサポートしています.
Windows95で Network DDE を利用するには,フラットサンクでこれらのAPIを利用 しなければなりません.Win16 で動作する機能が Win32 で提供されなかった原因は 定かではありませんが,サンクではWindowsNTのWin32と完全に互換性が保てないのか, もしくは Windows95(WindowsNTを含めたWin32)では,Network DDE の利用を推奨して いないとも見ることもできます(考えすぎ?).
日本語版のシステムで Network DDE を利用するには,US版 Windows for Workgroups SDK から,NDDEAPI.H, NDDEAPI.LIB などの必要なファイルを取り出して利用する必要 があります.
しかし,実際に日本語版 Windows95 で動作する Network DDE を利用した アプリケーションを見たことがないため,正常に動作するかは定かでは ありません.

●クイック再起動
Windows95の内部では,MS-DOS + Windows3.1 の構造が多く残っており, システムを再起動する Win16 API ExitWindows の EW_RESTARTWINDOWS フラグの ように,リアルモードのシステムを残して,プロテクトモードシステムのみを再起動 する機能も残っています.
この機能は,MS-DOSに戻らずにWindows3.xのみを再起動するために利用しましたが, Windows95 でもドライバをインストールしてシステムを再起動させる必要があるときに システム内で利用されているようです.しかし,WindowsNT用に設計されたWin32 API では,これに該当する機能はなく,Windows95 に実装されたWin32 API でも, 利用することができません.このため,この機能を利用するには,処理を16ビット アプリケーションで行うか,32ビットアプリケーションで利用するにはフラットサンク を利用して16ビットDLLで処理を行う必要があります.
(* EW_RESTARTWINDOWSでの再起動は,"Windowsの終了"を選択して表示される ダイアログボックスで"コンピュータを再起動する(B)" を選択して,[SHIFT]キーを押し ながら[OK]ボタンをクリックすると実行される.ただしWindows95のみ)

16-4 フラットサンクの開発環境

●必要不可欠なMicrosoft Macro Assemblerのありか
フラットサンクは,Visual C++ などのコンパイラ以外に Win32 SDK と 32ビット リンカが読み込むことができるオブジェクトファイルを出力することができる アセンブラが必要になります.
Win32 SDK を入手するには,MSDNプロフェッショナルサブスクリプション(旧レベル2) 以上に入会する必要があります.MSDNには SDK 以外に DDK なども含まれますが, DDKには Microsoft Macro Assembler Ver6.11 も含まれます.
Windows3.1 DDK は以前パッケージで販売していましたが,この中には MASM Ver 5.10A がツールの一部として含まれました.Windows95やWindowsNTのDDKでも アセンブラは必要になりますので,これらのツールの一部として提供されます.
ただ,不思議なのは,Win32 SDK のディレクトリ \MSTOOLS\BINW16 にフラットサンク を利用するために必要な,サンクコンパイラ(THUNK.EXE)と,Windows95 に対応した 16ビット版リソースコンパイラ(RC.EXE)が含まれますが,マクロアセンブラ(ML.EXE) は含まれません.元々SDK/DDKは別々のパッケージとして販売されていた訳ですから しかたがないのかもしれませんが,MSDNで一括して配布しているのですから,フラット サンクの利用者のためにマクロアセンブラの場所が分かるような配慮があってもよい のではないかと思います.

●フラットサンクで利用する16ビットDLLに付ける「印」
古いバージョン用の16ビットDLLを作成する環境では,フラットサンクを行うことが できません.
フラットサンクで利用する16ビットDLLは,バージョン4.0(Windows95)と互換性がある ことを示す「印」をつける必要があります.
この「印」をつけるには,Win32 SDK 付属の16ビット版リソースコンパイラを 利用する必要があります.実際には,Visual C++ ver1.x の \BIN\MSVCVERS.BAT を 修正して,新しいリソースコンパイラを実行できるようにして,オプションに "-40" を指定して実行します.

表16-1:フラットサンク用の開発ツールの場所(MSDN プロフェッショナルサブスクリプション)ただし,March 1997版現在
THUNK.EXE, RC.EXE Win32 SDK (Platform - SDK DISK 10)
(for Windows95 and WindowsNT Workstation 4.0 - U.S. with Far East Updates)
\MSTOOLS\BINW16
ML.EXE WindowsNT DDK (Deveropment Platform - DDK DISK 4,5)
(for WindowsNT Workstation 3.51 - U.S.)
(for WindowsNT Workstation 4.0 - U.S.)
\DDK\BIN\I386\FREE (MASM ver 6.11d)

Windows95 DDK (Deveropment Platform - DDK DISK 2,3)
(U.S. / Far East DDK)
\MASM611C (MASM ver 6.11c)

リスト16-1:16ビットコンパイラの MSVCVERS.BAT の例

@echo off
set TOOLROOTDIR=C:\LANG\MSVC
set PATH=c:\lang\mstools\binw16;C:\LANG\MSVC\BIN;%PATH%
                 ↑
              追加する
set INCLUDE=C:\LANG\MSVC\INCLUDE;C:\LANG\MSVC\MFC\INCLUDE;%INCLUDE%
set LIB=C:\LANG\MSVC\LIB;C:\LANG\MSVC\MFC\LIB;%LIB%

リスト16-2:フラットサンク用16ビット側DLLのメイクファイルでのリソースコンパイラの記述例

                 :
                 :
                 :

$(TARGET).dll: $(OBJS)
    link $(LFLAGS) $(OBJS), $(TARGET).DLL, NUL.MAP, \
       oldnames libw sdllcew, $(TARGET).def 
    rc -40 $(TARGET).dll
    implib /nowep $(TARGET).lib $(TARGET).dll

                 :
                 :
                 :

16-5 開発手順

●サンクスクリプトの記述
フラットサンクを行うには,16ビット,32ビットのDLLを作成する必要があります. サンクスクリプトの記述により,16ビットからの32ビット処理の呼び出し,32ビット からの16ビット処理の呼び出しの両方向へのサンクが可能になります.

●サンクの制限
サンクコンパイラが読み込むことができるスクリプトファイルは,C言語の プロトタイプ宣言に似た形式で,サンクを行う関数を記述します.
サンクで利用する関数が利用できる引数の型には制限があり,ポインタや構造体の配列 や浮動小数点をサポートしていません.引数にポインタを設定するときには, ポインタ修飾子で入力(input)・出力(output)・入出力(inout)ポインタの指定を行う 必要があります.また,1つのサンクスクリプトでは,片方向へのサンクの記述しか 行えませんので,1組の16/32ビットDLLで,双方向のサンクを行うときには, 2つのサンクスクリプトを用意する必要がありますが,1つのスクリプトファイル には,複数の関数を定義することができます.

リスト16-3:16ビットから32ビット関数を呼び出すためのスクリプト

enablemapdirect1632 = true;            /* 16ビットから32ビットを呼び出す */

typedef long          LRESULT;
typedef unsigned long DWORD;
typedef char          *LPSTR;

LRESULT TestFunc32(DWORD dwData, LPSTR lpszIn, LPSTR lpszOut)
{
    lpszIn  = input;
    lpszOut = inout;
}

リスト16-4:32ビットから16ビット関数を呼び出すためのスクリプト

enablemapdirect3216 = true;            /* 32ビットから16ビットを呼び出す */

typedef long          LRESULT;
typedef unsigned long DWORD;
typedef char          *LPSTR;

LRESULT TestFunc16(DWORD dwData, LPSTR lpszIn, LPSTR lpszOut)
{
    lpszIn  = input;
    lpszOut = inout;
}

●フラットサンクの実装方
フラットサンクの実装方法は,RPC(Remote Procedure Call)の IDL(Interface Define Language)を利用した開発手順と似て,実際の処理へのアクセス は,サンクコンパイラの生成したスタブ関数を呼び出すことにより,アドレス変換など の細かい処理を意識することなく,関数の呼び出しを行うことができます.
実際の手順は,サンクコンパイラでサンクスクリプトをコンパイルし,16ビットと 32ビット共通のアセンブラのソースコードを生成します. このソースコードは DIS_16 を定義すると16ビット用,DIS_32 を定義すると32ビット 用にアセンブルされます.サンクを行う16ビットDLLは,DllEntryPoint をエクスポート し,DLLの初期化処理の記述を行います.そして,C16ThkSL01, ThunkConnect16 を インポートします.この2つのAPIは,Windows95 の16ビットKERNEL.EXE しか持って いませんので,これらの関数をインポートしたDLLは,WindowsNTなど他のシステムで 実行することができなくなります.32ビット側のDLLでは DllEntryPoint は必要なく, DllMain に初期化処理を記述します.そして,THUNK32.LIBをリンクすることにより, サンクスクリプトから生成されたコードを利用可能な状態にします.THUNK32.LIBは KERNEL32.DLL にあるAPIを定義しています.これらのAPIもWindows95以外では存在 しないため,このライブラリを利用する32ビットDLLは,16ビットDLLと同様に Windows95専用のDLLになってしまいます.

●初期化処理
初期化処理は16/32ビット共に,サンクコンパイラが生成したコードの中にある関数 XXXXX_ThunkConnect16またはXXXXX_ThunkConnect32を呼び出すことにより 行われます.そして,サンクを行う関数の情報(データ) XXXXX_ThunkData16または XXXXX_ThunkData32 をエクスポートします.このデータは,サンクの初期化処理で 利用されます.
サンクによって利用できるようになった関数は,呼び出し側のスタブ関数を呼び出す ことにより利用します.スタブ関数は,サンクスクリプトから得られたアセンブラの ソースコードにあり,C言語からは16ビットであるか32ビットであるかを意識すること なく,通常の関数を呼び出すときと同様に呼び出すことができるようになります.
 XXXXX_ThunkConnect16(32) や XXXXX_ThunkData16(32) の XXXX はスクリプト ファイルのファイル名または,t オプションで設定された文字列が設定されます. ファイル名の先頭が数字のときには,先頭の文字が"X"に変換されます.
<サンクコンパイラのオプション>
THUNK [{-|/}オプション] 入力ファイル名[.拡張子]
? または h ヘルプを表示する
o 出力するファイル名
p 16ビットの構造体アライメント (既定値 2)
P 32ビットの構造体アライメント (既定値 4)
t ベース名を変更する
Nx セグメント名を設定する
x: C32 32ビットコードセグメント
  C16 16ビットコードセグメント
サンクコンパイラは,変数のアライメントを変換する機能を持っているため, サンクコンパイルで設定するパック設定と,Cコンパイラのパック設定を同一にしておく 必要があります.

表16-2:Windows95フラットサンクAPI
32ビットAPI K32Thk1632Prolog
K32Thk1632Epilog
MapSLFix
UnMapSLFixArray
ThunkConnect32
16ビットAPI C16ThkSL01
ThunkConnect16
※ これらのAPIはサンクスクリプトから生成された
アセンブラコードから呼び出されるため,
サンクの利用者が直接呼び出すことはない.

図16-1:フラットサンクの概要

                         +------------------------+
                         |       16TO32.THK       |
                         +------------------------+
                                    | サンクコンパイラ(THUNK.EXE)
                                    ↓  でコンパイルする
                         +------------------------+
                         |       16TO32.ASM       |
                         +------------------------+
                            |16ビット側      |32ビット側
                            ↓アセンブル      ↓アセンブル
                    +-----------+          +-----------+
                    |16TO32.OBJ |          |16TO32.OBJ |
                    +-----------+          +-----------+
                         ↓                     ↓
+----------------+  +-----------+          +-----------+    +----------------+
|16ビット        |→|16ビットDLL|←サンク→|32ビットDLL| → |32ビット        |
|アプリケーション|←|           |          |           | ← |アプリケーション|
+----------------+  +-----------+          +-----------+    +----------------+

16-6 プログラムについて

●プログラムの概要
16ビット - 32ビット双方向で呼び出しを行うフラットサンクDLLのサンプルと, Win16 API ExitWindows を32ビットアプリケーションから呼び出すサンプルです. 3つのEXEファイル(TEST16.EXE, TEST32.EXE, QBOOT95.EXE)共に,FTHUNK16(32).DLL を必要とします.16/32ビットDLL共に,TestFunc16, TestFunc32 をエクスポートし, 再起動を行う関数 RestartWindows95 は16ビットDLLからエクスポートされます. TEST16.EXE, TEST32.EXEは,16ビット版と32ビット版で全く同様の動作を行います. サンプルDLLは,Windows95以外ではロードすることができません. フラットサンクで利用する関数を,WindowsNTなど他のOSで実行する必要があるとき には,サンクを行うDLLから呼び出す関数を別のDLLに分離する必要があります.

●ソースファイル
FTHUNK16.DLL
 FTHUNK16.C (16ビット側DLL)
 FTHUNK16.H (ヘッダファイル)
 FTHUNK16.DEF (モジュール定義ファイル)
 MAKEFILE (メイクファイル)
FTHUNK32.DLL
 FTHUNK32.C (32ビット側DLL)
 FTHUNK32.H (ヘッダファイル)
 FTHUNK32.DEF (モジュール定義ファイル)
 FTHUNK32.RC (リソースファイル)
 MAKEFILE (メイクファイル)
F16TO32.THK (16ビットから32ビットへのサンクスクリプト)
F16TO32.ASM (生成されたアセンブラソースファイル)
F32TO16.THK (32ビットから16ビットへのサンクスクリプト)
F32TO16.ASM (生成されたアセンブラソースファイル)
TEST16.EXE,TEST32.EXE
 TEST.C (テスト用アプリケーション本体)
 TEST.DEF (16ビット用モジュール定義ファイル)
 TEST.RC (リソースファイル)
QBOOT95.EXE
 QBOOT95.C (クイック再起動アプリケーション本体)
 QBOOT95.RC (リソースファイル)
 QBOOT95.ICO (アイコン)
 QBOOT95.MAK (メイクファイル)