複数のOSに対応するアプリケーションの作り方がわかりたい

戻る

Win32 API を利用できるWindowsには Windows3.1+Win32s, Windows95, WindowsNTが あります。
「利用できる」とはいえ、OSが異なるとAPIのサポート範囲が異なり、同じAPIでも 動作が異なることがあります。さらに、バージョンごとにAPIが追加されたり、動作が 完全に同じでないことさえあります。
これは、Win16 API でも同じであると言えます。16ビットアプリケーションが動作する OSには、Windows, OS/2 ver2以降, Windows95, WindowsNT などがありますが、 これらのOSで動作する Win16 API は、完全に同じではありません。
Windowsのアップグレードは、1〜2年に1回ペースで行われ、その度に新しい機能やAPI が追加されており、少しづつですが高機能なアプリケーションが楽に作れるように なってきている反面、複雑さも増大していると言えるでしょう。
ここでは、そのような状況に対応するための、アプリケーションとして、複数のOSに 対応するためのアプリケーションの作り方について考えてみましょう。

7-1 32ビットアプリケーションでOSを識別する

○Windows95とWindowsNTの互換性の問題
32ビットアプリケーションは Windows95とWindowsNT をターゲットにする アプリケーションがほとんどではないかと思われます。
WindowsNTは、バージョン3.5から一般的に利用されるようになってきましたが、 Windows95 の出現により、双方で動作しないアプリケーションが問題になりはじめて きました。
Windows95 では新しいユーザインタフェースが採用され、見た目も変化しましたが、 Windows95用に作成されたアプリケーションは、WindowsNT3.5では 正常に動作するどころか、Windows95 で新たにサポートされた API(DLLのエントリ ポイント)が存在しないため、ロードさえできない状態になりました。
このため、WindowsNT3.51 で Windows95 で実装された API の全エントリポイントと 一部の機能をサポートし、サポートできない機能に関してはスタブ API(実際には処理 を行わない API)を用意して実行できるようにしました。
しかし、ロードできるからといって正常に動作するとは限りません。
エラーチェックを厳密に行わないアプリケーションや、もともと WindowsNT をサポート 対象としないアプリケーションでは厳密にテストを行わなかったり、サポートされない 機能を他の機能でカバーするといった対策ができていないため、正常に動作しない ものもあるのです。

○OSチェックが必要となる理由
このような状況は、Windows95/WindowsNT3.51に限ったことではなく、再び起こる 可能性があります。
作成したアプリケーションを新しいOSへスムーズに移行させるためには、OSの バージョンやOS自身をチェックして処理を動的に変更できるようにしておくと 無用なトラブルを回避することができます。
すでに Windows3.1 で動作しているアプリケーションを、新しいOS機能をサポート させる必要があるときにも、OSをチェックして動作する必要があります(注1)。
(注1)Visual C++ ver4.2 からは、Win32s を正式にサポート外にしています。 Win32s を他の32ビットOSと共にバージョンアップしていくのも限界が近いのかも しれません。しかし、Windows3.1 はまた現役のOSとして多く動作しているため、 完全にサポート外とできないことも多いと思われます。 すでに Windows3.1 で動作しているアプリケーションを、新しいOSの機能をサポート させる必要があるときにも、OSをチェックして動作する必要があります。

○新しいOSの機能を利用した場合の古いOSへの対応方法
このとき注意しなければならないのは、新しいOSの機能を利用すると、古いOSでは ロードできない可能性があるということです。
インポートライブラリをリンクしたアプリケーションは、起動時にリンクされた全ての DLLがロードされるために、実行するOSに存在しないAPI(エントリポイント)が あると起動さえできなくなります。
このため、サポート範囲が異なるOSで同一のアプリケーションを実行させるには、 問題となるAPIの呼び出しをDLLにまとめ、実行するOSに対応するDLLを Win32 API LoadLibrary() で動的にロードさせる必要があります。
○Win32APIによるOSチェック
Win32 API で実行中のOSをチェックするには、Win32 API GetVersion() または Win32 API GetVersionEx() を利用します。
これらのAPIは、Win32s(Windows), Win32c(Windows95), Win32(WindowsNT)で利用可能 です。
GetVersion は、Win16 からの互換性のために用意されています。このため、 32ビットOSで拡張された情報を取得できません。ビルド番号など、Win16 では 存在しなかった情報を得るには GetVersionEx を利用します (表7-1)。

表7-1:Win32 のバージョン取得API
書 式 DWORD APIENTRY GetVersion(VOID)
説 明 現在のバージョン番号とオペレーティングシステムプラットフォームについての情報を取得する
引 数 なし
戻り値
32                 16        8         0
+------------------+---------+---------+
|X                 |メジャー |マイナー |
+------------------+---------+---------+
↑                 |←  バージョン   →|
 0 ... WindowsNT
 1 ... Win32sまたはWindows95
書 式 BOOL APIENTRY GetVersionEx(LPOSVERSIONINFO lpVerInfo)
説 明 現在のバージョン番号とオペレーティングシステムプラットフォームについての情報を取得する
引 数 LPOSVERSIONINFO lpVerInfo ... OSVERSIONINFO構造体を指すポインタ
dwOSVersionInfoSize に sizeof(OSVERSIONINFO)を設定してから呼び出す必要がある。
戻り値 正常終了 ... TRUE
異常終了 ... FALSE
*構造体のサイズが実際に設定するサイズより小さいときには失敗する

OSVERSIONINFO構造体
typedef struct _OSVERSIONINFO{
    DWORD dwOSVersionInfoSize;     構造体のサイズ
    DWORD dwMajorVersion;          メジャーバージョン
    DWORD dwMinorVersion;          マイナーバージョン
    DWORD dwBuildNumber;           ビルド番号
    DWORD dwPlatformId;            OSの種類
    TCHAR szCSDVersion[128];       OS固有の文字列
} OSVERSIONINFO;

dwOSVersionInfoSize
  構造体のサイズを指定します

dwMajorVersion
  OSのメジャーバージョンが設定される
  WindowsNT3.51 ... 3
  WindowsNT4.0  ... 4
  Windows95     ... 4

dwMinorVersion
  OSのマイナーバージョンが設定される
  WindowsNT3.51 ... 51
  WindowsNT4.0  ... 0
  Windows95     ... 0

dwBuildNumber
  OSのビルド番号が設定される
  WindowsNT     ... ビルド番号
  Windows95     ... 下位WORDビルド番号
                    上位WORDメジャー・マイナーバージョン

dwPlatformId
  OSの種類が設定されます
  WindowsNT     ... VER_PLATFORM_WIN32_NT
  Windows95     ... VER_PLATFORM_WIN32_WINDOWS
  Win32s        ... VER_PLATFORM_WIN32s

szCSDVersion
  OS固有の文字列が設定される
   WindowsNT    ... Service Pack 3 がインストールいるときには
                    "Service Pack 3"が設定される
                    インストールされていないときには、文字列は
                    設定されません
   Windows95    ... OSに関する追加情報文字列が設定される


7-2 16ビットアプリケーションでOSを識別する

○16ビットアプリケーションの扱いをどうするか
16ビットアプリケーションでOSを識別する方法は、Win32 とほとんど同様です (表7-2)。
Win16 API は、もともと複数のプラットホームで実行されることを前提にされて いません。このため、実行中のアプリケーションがどのようなOSで実行されているか を明確に取得する方法を提供していません。
Windows95 は、Windows3.1の新しいバージョンとして発表されましたが、 アプリケーションは Win32 API を利用できます。
しかし、依然として有用な16ビットアプリケーションは多く存在しまし、また 16ビットアプリケーションの中には、Windows3.1 を厳密にチェックして、将来の バージョン(Windows95)では動作しないようにしているものもあります。
つまり、実行できるかどうかわからないOSでアプリケーションを実行することによって 引き起こされるトラブルのデメリットが大きいと考えられるときには、実行させない ようにすることもありえます。
しかし、アプリケーションを利用する側は、完全に動作することを望んでいるため、 将来発表されるOSが予期しない動作をしても、回避ができるものはできるだけ動作 させ、最悪の場合でも例外を起こさせないような配慮を、できる限り行う必要が あるのではないかと思います。


表7-2:Win16 のバージョン取得API
書 式 DWORD WINAPI GetVersion(VOID)
説 明 現在のバージョンを取得する
引 数 なし
戻り値
32      24         16        8         0
+---------+---------+---------+---------+
|マイナー |メジャー |メジャー |マイナー |
+---------+---------+---------+---------+
|←   MS-DOS     →|←    Windows    →|
    のバージョン        のバージョン
書 式 DWORD WINAPI GetWinFlags(VOID)
書 式 現在のシステムとメモリの設定を取得する
引 数 なし
戻り値 WF_80x87 ... 数値演算プロセッサを搭載している
WF_CPU286 ... システムのCPUは80286WOWではRISCであることを示す
WF_CPU386 ... システムのCPUは80386
WF_CPU486 ... システムのCPUはi486
WF_ENHANCED ... エンハンスドモードで実行されている
WF_PAGING ... ページングメモリをサポートしている
WF_PMODE ... プロテクトモードで実行されている
WF_STANDARD ... スタンダードモードで実行されている
WF_WIN286 ... WF_STANDARDと同様
WF_WIN386 ... WF_ENHANCEDと同様
WF_WINNT ... システムはWindowsNTのWOW *

* Windows3.1 SDK ではサポートされないため、各自定義する必要がある


○メジャーバージョン3
Microsoft社 は、動作しないアプリケーションを最小限に抑えるために、メジャー バージョンを3のままとしています。
しかし、これでは16ビットアプリケーションが、OSを識別することができなくなる ため、メジャーバージョンは95を設定しています。
次に発表される Windows95 の新バージョンではどうなるかはまだわかりませんが、 何が設定されても困らないようにしておく必要があるでしょう。

○識別の困難なWindowsNTで動作するWindows3.1
WindowsNT では、DOSサブシステム上のWOW(Windows On WindowsNT)で Win16 API をサポートしています。
WindowsNT は、サポートするCPUが Intel 製に限定されているわけではないので、 16ビットアプリケーションはソフトウェア・エミュレーションで動作させることも ありえます。
WOW上で Win16 API GetVersion() を利用してOSのバージョンを取得すると、 メジャーバージョンは3、マイナーバージョンは10を返却します。 また、MS-DOSのバージョンを取得すると、5.0を返却します。
これは、WindowsNT の WOW は、Windows3.1 を基準に作られているためです。 しかし、これではOSを識別することができません。

○Windows3.1, Windows95, WindowsNT の識別方法
16ビットアプリケーションから、実行中のOSが Windows3.1 や Windows95 であるか、 または WindowsNT であるかを識別するには、Win16 API GetWinFlags を 利用します。
このAPIは、Windows3.0から追加された、実行中のマシンのCPUなどを識別するための APIです。
WindowsNT の WOW では、このAPIを拡張して実行中のOSが WindowsNT であることを 示すフラグを返却するようになっています。
GetWinFlags は Win32 API ではサポートされていないので、32ビット アプリケーションでは、Win32 API GetSystemInfo() を利用します (表7-3)。
16ビットアプリケーションで、GetSystemInfo で取得できる情報を得るには、 汎用サンクを利用して、32ビットDLLで値を取得する必要があります。
OSやバージョンによって取得できる情報が異なりますので、利用するときには 注意が必要になります。

表7-3:Win32 のシステム情報取得API
書 式 VOID APIENTRY GetSystemInfo(LPSYSTEM_INFO lpSysInfo)
説 明 現在のシステムの情報を取得する
引 数 LPSYSTEM_INFO lpSysInfo ... SYSTEM_INFO構造体を指すポインタ
戻り値 なし

SYSTEM_INFO構造体
typedef struct _SYSTEM_INFO {
    union {
       DWORD  dwOemId;                  OEM識別子
       struct {
           WORD wProcessorArchitecture; プロセッサアーキテクチャ 
           WORD wReserved;              予約
       };
    };
    DWORD  dwPageSize;                  ページサイズ
    LPVOID lpMinimumApplicationAddress; アクセス可能な最下位メモリ
    LPVOID lpMaximumApplicationAddress; アクセス可能な最上位メモリ
    DWORD  dwActiveProcessorMask;       プロセッサ集合マスク値
    DWORD  dwNumberOfProcessors;        プロセッサ数
    DWORD  dwProcessorType;             プロセッサの種類
    DWORD  dwAllocationGranularity;     メモリ割り当て単位
    WORD   wProcessorLevel;             プロセッサレベル
    WORD   wProcessorRevision;          プロセッサリビジョン
} SYSTEM_INFO;
-----------------------
dwOemId
  特定のOEMに固有なコンピュータ識別子
  Windows95, WindowsNT3.51 以降は、
  wProcessorArchitecture を利用します
-----------------------
wProcessorArchitecture
  PROCESSOR_ARCHITECTURE_INTEL
  WindowsNTのみ
   PROCESSOR_ARCHITECTURE_MIPS
   PROCESSOR_ARCHITECTURE_ALPHA
   PROCESSOR_ARCHITECTURE_PPC
   PROCESSOR_ARCHITECTURE_UNKNOWN 
-----------------------
dwPageSize 
  ページ サイズを返しページ保護とコミットメントの粒度を指定します
  この値は、Win32 API VirtualAlloc() で利用されます
-----------------------
lpMinimumApplicationAddress
  アプリケーションやDLLからアクセス可能な最下位メモリアドレス
-----------------------
lpMaximumApplicationAddress
  アプリケーションやDLLからアクセス可能な最上位メモリアドレス
-----------------------
dwActiveProcessorMask
  システムに合わせて設定されたプロセッサの集合を表すマスク
  ビット0はプロセッサ0、ビット31はプロセッサ31を示します
-----------------------
dwNumberOfProcessors
  システムのプロセッサ数
-----------------------
dwProcessorType
  WindowsNT3.51以降ではWindows95やWindowsNT3.5以前のバージョン
  との互換性のために設定されます
  WindowsNT3.51以降では wProcessorLevel, wProcessorRevision を
  を利用します
  PROCESSOR_INTEL_386
  PROCESSOR_INTEL_486
  PROCESSOR_INTEL_PENTIUM
  WindowsNTのみ
   PROCESSOR_MIPS_R4000
   PROCESSOR_ALPHA_21064
-----------------------
dwAllocationGranularity
  仮想メモリを割り当てるときの割り当て単位
  現状では、64Kバイトを返します
  Win32 API VirtualAlloc() が確保するメモリの最小単位を表します
-----------------------
wProcessorLevel
   Windows95        : 利用しません
   WindowsNT3.5以前 : 予約
   WindowsNT3.51以降: プロセッサレベル
  wProcessorArchitecture が PROCESSOR_ARCHITECTURE_INTEL のとき
   3      ... 80386
   4      ... i486
   5      ... Pentium
  wProcessorArchitecture が PROCESSOR_ARCHITECTURE_MIPS のとき
   0x0004 ... このビットがONなら MIPS R4000
  wProcessorArchitecture が PROCESSOR_ARCHITECTURE_ALPHA のとき
   21064  ... Alpha 21064
   21066  ... Alpha 21066
   21164  ... Alpha 21164 
  wProcessorArchitecture が PROCESSOR_ARCHITECTURE_PPC のとき
   1      ... PPC 601
   3      ... PPC 603
   4      ... PPC 604
   6      ... PPC 603+
   9      ... PPC 604+
   20     ... PPC 620
-----------------------
wProcessorRevision
   Windows95        : 利用しません
   WindowsNT3.5以前 : 予約
   WindowsNT3.51以降: プロセッサリビジョン


7-3 プログラムについて

○ソースリスト \MULTIOS
OSのチェックを行い、ウィンドウに実行中のOSとバージョンを表示します。
本サンプルソースは16ビット、32ビット共通です。
CHKOS.H には、関数のプロトタイプと、特定のOSであるかを返すマクロが あります。
CHKOS.CGetOSVersion() は、最初に実行されたときにはチェックを 行いますが、2度目以降は、チェックを行わず最初に呼び出されたときに取得した値を 返却します。
CHKOS.C(サンプルメイン)
CHKOS.H(サンプルメインヘッダファイル)
OSVER.C(OSチェックルーチン)
OSVER.H(OSチェックルーチンヘッダ)
CHKOS.RC(リソース)
CHKOS.ICO(アイコン)
CHKOS16.DEF(16ビット用モジュール定義ファイル)
MAKEFILE.16(16ビット用メイクファイル)
MAKEFILE.32(32ビット用メイクファイル)