モジュールのバージョン管理を探究する

戻る

複数のモジュールで1つのアプリケーションを構成している場合,すべて一人で作成 していることより複数の人が同時に開発したり,すでに作成されているモジュールを 流用することの方が多いのではないかと思います. 複数の人が関わっているアプリケーションでは,少しの修正がアプリケーション全体に 影響を及ぼすこともよくあります.このため,各モジュールがいつどのような修正が 行われたかを管理しておく必要があります.また,アプリケーションをリリースした あとに,限定機能追加やバグ修正によって一部のモジュールを配布した場合, インストールされた環境やモジュールの構成によって正しく動作しないことも珍しく ありません.
そこで,各モジュールがどのバージョンであるかを簡単に見分けることができる ようにしてすることにより,モジュールバージョンの組み合わせによるトラブルを防ぐ 方法について考えます.

20-1 DOS時代のモジュールバージョンを見分ける方法

DOS全盛時代によく使われていた,アプリケーションのバージョンを示す方法 には次のようなものがありました.
1) ファイル名にバージョンを含める (たとえば,APP102.EXE)
2) ファイル日付の時刻をバージョンにする
 APP EXE 79,228 96-12-06 1:02 ← APP.EXEバージョン1.02
3) コマンドラインに特定のオプションを指定するとバージョン情報が表示される
4) アプリケーションの操作でバージョンを表示させる,または起動されると必ず表示される
しかし,これらの方法はWindows95/NTの時代になっても利用できますが, 多少問題もあります.次に,主な問題点を挙げてみました.

1) の問題
  • コマンドラインから利用者がファイル名を指定して実行する場合は,バージョンが 上がることにコマンド名が変化してしまう.Windows上では,プログラムマネージャ のアイコンやスタートメニューのショートカットも修正しなければならなくなる.
  • Windows上でバージョンごとに異なる名前の共通DLLを作成した場合, バージョンごとに似たような処理のDLLが複数存在することになる. たとえば,MFCxx.DLL.DLLの場合,完全に互換性を保証できないためにファイル 名を変えるのでしょうが,少なくとも利用者の立場に立った考えであるとは 思えません.ちなみに現在のVBのランタイムDLLは下位互換性をサポート している.
  • 細かい情報を含めることができないため,小さいバグ修正を違いを示すことが できない.
    2) の問題
  • 細かい修正を行った場合には,ファイル日付の変更を行わなかったりすることが ありファイル日付のバージョン設定そのものが無意味になることの方が多い.
  • 1)と同様に,細かい情報を含めることができないため,小さいバグ修正を違いを 示すことができない.

    3),4) の問題
  • ・バージョンをチェックするためにアプリケーションを実行する必要がある. このため,バージョンを表示する処理さえ行えない状態にある場合は使えない.
  • 当然DLLには使えない.ただし,各DLLを呼び出してバージョンを表示する アプリケーションを作れば可能.
    やたら問題のみをクローズアップしまいましたが,逆に上記のような問題は発生 しないことが前提であれば便利に利用することができます.
    たとえば,インストール用のセットアッププログラムを作成し,これらのファイルを 自己解凍(自動実行)にする場合,ファイル名にバージョンを埋め込むことにより 管理が楽になります.

    20-2 Windows時代のモジュールバージョンを見分ける方法

    Windows上で動作するアプリケーションは,リソースと呼ばれる固定のデータ をモジュール内に持つことができます.リソースのソースファイルは,通常 .RC という拡張子がついており,このファイルをリソースコンパイラ RC.EXE でコンパイル することで,モジュール内に組み込むことができる .RES ファイルを作成することが できます.
    このリソースファイルには,アイコン・ダイアログボックス・ウィンドウのメニュー ・文字列・Win32 API FormatMessage で読み出すことのできるメッセージ情報など 一度設定されると変更されない,また利用する言語ごとに異なる文字コードで情報を 保存することができるようになっています.つまり,リソースのみを変更するだけで, 複数の言語(ここでは言葉の言語)に対応したアプリケーションを作ることが可能に なります.
    このリソースの中に,モジュールのバージョンを示すバージョン情報を含めることも 可能です.この仕組みを使うことにより,どのアプリケーションからも他のモジュール のバージョンを取得することができるようになります.最も簡単にモジュールの バージョンを参照するには,エクスプローラで,実行ファイルを左クリックして表示 されるプロパティのダイアログボックスから,「バージョン情報」を選択することで 行えます.ただし,実行ファイルがバージョン情報をもっていなければ, 「バージョン情報」は表示されません.

    20-3 バージョン情報

    バージョン情報は,あくまでもモジュールの利用者にこの実行ファイルが何者である かを示すために利用します.このため,利用者に見せたくない情報や見せても意味の ない情報を設定すべきではありません.バージョンリソースは,利用者が簡単に修正 することができません.
    (* WindowsNT上では,Visual C++のDeveroper Studioなどの開発ツールを利用する ことで,リソースを参照・修正することが可能である.)
    ので,無用なトラブルを防ぐことができます.
    これらの情報は,インストーラを作成するときにも重要な意味がありますので, 設定する値の管理はしっかり行っておくとよいでしょう.

    20-4 実行ファイルにバージョン情報を追加する

    それでは,実際にリソースファイルにバージョン情報を追加する方法について 説明します.
    バージョン情報は,ダイアログボックスやウィンドウのメニューと同様に記述する ことができます.他のリソースと異なる点は,設定する数値の情報はすでに定義され ている値を設定しなければならないことと,VERSIONINFO を示すリソースIDは必ず "1" でなければならないことです.
    "StringFileInfo"ブロックは,実行されるOSの言語セットごとに複数設定できるように するため,"041103a4" (日本語:日本語Windows)のように,国別コードのブロックを 定義します.複数の言語をサポートするアプリケーションでは,"StringFileInfo" ブロックの配下に複数の言語に対応したブロックを複数持つことになります.
    VERSIONINFOからBEGINまでの間に記述されている情報は,VS_FIXEDFILEINFO構造体に 設定される値を設定します.また,それ以降の値は,Win32 API で文字列のまま取得 することになります.実際にバージョン情報を生成するときは,エディタでタイプして 作成することもできますが,Visual C++ の Deveroper Studio などのツールを利用 することにより,簡単に生成することができます.

    リスト20-1:バージョンリソース
    
    1 VERSIONINFO
     FILEVERSION    1,0,0,0
     PRODUCTVERSION 3,0,0,0
     FILEFLAGSMASK  0x3fL
     FILEFLAGS      0x0L
     FILEOS         0x40004L
     FILETYPE       0x1L
     FILESUBTYPE    0x0L
    BEGIN
        BLOCK "StringFileInfo"
        BEGIN
            BLOCK "041103a4"
            BEGIN
                VALUE "Comments",         "モジュールバージョン表示ツール\0"
                VALUE "CompanyName",      "CQ出版社\0"
                VALUE "FileDescription",  "DISPVER\0"
                VALUE "FileVersion",      "1, 0, 0, 0\0"
                VALUE "InternalName",     "DISPVER\0"
                VALUE "LegalCopyright",   "Copyright (C) 1997 S.Tsuneoka\0"
                VALUE "OriginalFilename", "DISPVER.EXE\0"
                VALUE "ProductName",      "Win32サブルーチンズ\0"
                VALUE "ProductVersion",    "4, 0, 0, 0\0"
            END
        END
        BLOCK "VarFileInfo"
        BEGIN
            VALUE "Translation", 0x411, 932
        END
    END
    

    20-5 実行ファイルのバージョン情報を参照する

    バージョン情報には,DWORD の数値情報と,文字列の情報があります.
    これらの情報を,アプリケーションでバージョン情報を読み出すには, Win32 API GetFileVersionInfoSize, Win32 API GetFileVersionInfo, Win32 API VerQueryValue, を利用します.
    取得するバージョン情報は,\記号で区切られた文字列で指定します. たとえば,コメント情報を取得するには,
    \\StringFileInfo\\041103a4\\Comments
    を指定します.

    表20-1:バージョン情報 API
    DWORD GetFileVersionInfoSize(LPTSTR lpszFile, LPDWORD lpdwHandle)
    ファイルからバージョン情報を取得できるかどうかを判定する
    Win32ファイルイメージしか扱うことができない

    引数
    LPTSTR lpszFile ... ファイル名を指すポインタ
    LPDWORD lpdwHandle ... 0を指定する

    戻り値
    正常終了 情報を格納するために必要なバッファのサイズ
    異常終了 0
    BOOL GetFileVersionInfo(
     LPTSTR lpszFile, LPDWORD lpdwHandle DWORD dwSize, LPVOID lpvData)
    ファイルからバージョン情報を取得する
    Win32ファイルイメージしか扱うことができない

    引数
    LPTSTR lpszFile ... ファイル名を指すポインタ
    LPDWORD lpdwHandle ... 無視
    DWORD dwSize ... バッファのサイズ
    LPVOID lpvData ... バージョン情報用の領域を指すポインタ

    戻り値
    正常終了 TRUE
    異常終了 FALSE
    BOOL VerQueryValue(const LPVOID lpvBlock,
     LPTSTR lpSubBlock, LPVOID *lplpBuffer, PUINT lpuLen)
    ファイルからバージョン情報を取得する
    Win32ファイルイメージしか扱うことができない

    引数
    const LPVOID lpvBlock ... Win32 API GetFileVersionInfoが返す
     バージョン情報リソースを格納するバッファを指すポインタ
    LPTSTR lpSubBlock ... 円記号で区切られた名前
    LPVOID *lplpBuffer ... バージョン情報値を指すポインタを受け取るバッファ
    PUINT lpuLen ... 情報値の長さを文字単位で受け取る

    戻り値
    正常終了 TRUE
    異常終了 FALSE

    リスト20-2:バージョン情報構造体
    
    VS_VERSION_INFO {
        WORD  wLength;          VS_VERSION_INFO構造体のサイズ
        WORD  wValueLength;     Valueメンバの長さ
        WORD  bText;            バージョンリソース内のデータの種類
        WCHAR szKey[];          "VS_VERSION_INFO"
        BYTE  Padding1[];       Valueメンバを32ビット境界に合わせるため
        VS_FIXEDFILEINFO Value; VS_FIXEDFILEINFO構造体
        BYTE  Padding2[];       Valueメンバを32ビット境界に合わせるため
        BYTE  Children[];       0個以上のStringFileInfo構造体
                                またはVarFileInfo構造体 (もしくはその両方) のリスト
    };
    
    typedef struct _VS_FIXEDFILEINFO {
        DWORD dwSignature;         0xFEEFO4BD
        DWORD dwStrucVersion;      バイナリバージョン番号 0x00000029以上の値
        DWORD dwFileVersionMS;     ファイルの2進バージョン番号の上位32ビット
        DWORD dwFileVersionLS;     ファイルの2進バージョン番号の下位32ビット
        DWORD dwProductVersionMS;  製品の2進バージョン番号の上位32ビット
        DWORD dwProductVersionLS;  製品の2進バージョン番号の下位32ビット
        DWORD dwFileFlagsMask;     dwFileFlagsメンバの中の有効なビット
        DWORD dwFileFlags;         ファイルのブール値属性を指定するビットマスク
              VS_FF_DEBUG          (0x00000001) デバッグ情報が含まれている
              VS_FF_PRERELEASE     (0x00000002) ファイルは開発バージョンである
              VS_FF_PATCHED        (0x00000004) ファイルは修正されたバージョン
              VS_FF_PRIVATEBUILD   (0x00000008) 標準的なリリースされたファイルでない
              VS_FF_INFOINFERRED   (0x00000010) 構造体は動的に作成されている
              VS_FF_SPECIALBUILD   (0x00000020) 標準ファイルのバリエーション
        DWORD dwFileOS;            設計対象となったオペレーティングシステム
              VOS_UNKNOWN          (0x00000000) 不明
              VOS_DOS	           (0x00010000) MS-DOS
              VOS_OS216            (0x00020000) OS/2 16ビット
              VOS_OS232            (0x00030000) OS/2 32ビット
              VOS_NT               (0x00040000) WindowsNT
              VOS_DOS_WINDOWS16    (0x00010001) Win16
              VOS_DOS_WINDOWS32    (0x00010004) Win32s
              VOS_OS216_PM16       (0x00020002) OS/2 16ビット PM
              VOS_OS232_PM32       (0x00030003) OS/2 32ビット PM
              VOS_NT_WINDOWS32     (0x00040004) Win32
        DWORD dwFileType;          ファイルの一般的な種類
              VFT_UNKNOWN          (0x00000000) 不明
              VFT_APP              (0x00000001) アプリケーション
              VFT_DLL              (0x00000002) ダイナミックリンクライブラリ
              VFT_DRV              (0x00000003) デバイスドライバ
                                              dwFileSubtypeに詳細な記述が含まれる
              VFT_FONT             (0x00000004) フォント
                                              dwFileSubtypeに詳細な記述が含まれる
              VFT_VXD              (0x00000005) 仮想デバイス
              VFT_STATIC_LIB       (0x00000007) スタティックリンクライブラリ
        DWORD dwFileSubtype;       dwFileTypeの値に異存する
                dwFileTypeがVFT_DRVの場合
              VFT2_UNKNOWN         (0x00000000) 不明
              VFT2_DRV_PRINTER     (0x00000001) プリンタドライバ
              VFT2_DRV_KEYBOARD    (0x00000002) キーボードドライバ
              VFT2_DRV_LANGUAGE    (0x00000003) 言語ドライバ
              VFT2_DRV_DISPLAY     (0x00000004) ディスプレイドライバ
              VFT2_DRV_MOUSE       (0x00000005) マウスドライバ
              VFT2_DRV_NETWORK     (0x00000006) ネットワークドライバ
              VFT2_DRV_SYSTEM      (0x00000007) ファイルはシステムドライバ
              VFT2_DRV_INSTALLABLE (0x00000008) インストール可能なドライバ
              VFT2_DRV_SOUND       (0x00000009) サウンドドライバ
                dwFileTypeがVFT_FONTの場合
              VFT2_UNKNOWN         (0x00000000) 不明
              VFT2_FONT_RASTER     (0x00000001) ラスタフォント
              VFT2_FONT_VECTOR     (0x00000002) ベクタフォント
              VFT2_FONT_TRUETYPE   (0x00000003) TrueTypeフォント
        DWORD dwFileDateMS;        ファイルの作成日付および時刻スタンプ上位32ビット
        DWORD dwFileDateLS;        ファイルの作成日付および時刻スタンプ下位32ビット
    } VS_FIXEDFILEINFO;
    

    20-6 さらに細かい情報を取得するには?

    バージョン情報はリソースファイルを意図的作成した場合のみ参照することが できますが,EXEファイル内のPEヘッダを覗くと,作成したアプリケーションが, どのサブシステム(Win32GUI, Win32CUI, OS/2, POSIX)用に作成されたものかなどの 情報を得ることができます.
    EXEファイルやDLLファイル内のヘッダに設定されている情報は,
    >DUMPBIN /HEASERS APP.EXE
    を実行することにより知ることができます.
    プログラムでこのような情報を取得するには,EXEファイルを直接読み出して, PE(PortableExecutable)ヘッダを解析する必要があります.

    例20-1:DUMPBIN.EXEでヘッダーを表示
    
    >dumpbin /headers dispver.exe
    
    Microsoft (R) COFF Binary File Dumper Version 5.02.7188
    Copyright (C) Microsoft Corp 1992-1997. All rights reserved.
    
    
    Dump of file dispver.exe
    
    PE signature found
    
    File Type: EXECUTABLE IMAGE
    
    FILE HEADER VALUES
         14C machine (i386)           ← マシンのアーキテクチャ
           4 number of sections
    34A67C38 time date stamp Mon Dec 29 01:20:08 1997  ← 生成された時刻
                    :
                    :
    
    OPTIONAL HEADER VALUES
         10B magic #
        5.10 linker version
                    :
                    :
    
             ----- new -----
      400000 image base
        1000 section alignment
         200 file alignment
           3 subsystem (Windows CUI)       ← サブシステム
        4.00 operating system version
        0.00 image version
        4.00 subsystem version   ← サブシステムバージョン
       16000 size of image
                    :
                    :
    

    例20-2:CUIアプリケーションのスタートアップルーチンで利用されるWin32 API
    
    KERNEL32.dll
      237   TlsFree
      142   GetVersion
       6A   ExitProcess
      1D8   RtlUnwind
      23E   UnhandledExceptionFilter
       F3   GetModuleFileNameA
       95   FreeEnvironmentStringsA
      1A0   MultiByteToWideChar
       DA   GetEnvironmentStrings
       96   FreeEnvironmentStringsW
       DC   GetEnvironmentStringsW
      25A   WideCharToMultiByte
       A2   GetCPInfo
       9C   GetACP
      100   GetOEMCP
      20B   SetHandleCount
       E6   GetFileType
      120   GetStdHandle
      11E   GetStartupInfoA
       4B   DeleteCriticalSection
       D1   GetCurrentThreadId
      239   TlsSetValue
      236   TlsAlloc
       A9   GetCommandLineA
      20E   SetLastError
      238   TlsGetValue
       EB   GetLastError
       D0   GetCurrentThread
       160   HeapCreate
       162   HeapDestroy
       267   WriteFile
       16F   InitializeCriticalSection
        57   EnterCriticalSection
       184   LeaveCriticalSection
        72   FatalAppExitA
       164   HeapFree
       15E   HeapAlloc
       10D   GetProcAddress
       185   LoadLibraryA
        8D   FlushFileBuffers
       209   SetFilePointer
        17   CloseHandle
       218   SetStdHandle
       1E8   SetConsoleCtrlHandler
    

    20-7 Windowsアプリケーションとコンソールアプリケーションの違い

    違いといっても,リソースに関わる問題です.
    MS-DOSやUNIXのアプリケーションを主に作成している方から見ると, コンソールアプリケーションに,リソースのような情報を持たせることができるのか といったことを疑問に思われることが多いようです.
    さらに,Win32 API が利用できるのか?ウィンドウを生成することができるのか? といったことも疑問になっているようです.
    最近ではそのような質問はありませんが,Windows3.1では,WinMain関数がメインの Windowsアプリケーションしか作成できないがWindowsNTやWindows95でも同じなのか? main関数をメイン関数とするアプリケーションはDOSのアプリケーションしかないの では?といった質問を受けることがありました.
    実際には,リンカが生成するイメージ(EXEファイル)に多少の違いはあります. mainをメイン関数に持つCUIアプリケーションは,Cランタイムルーチンから ユーザのmain関数を呼び出すスタートアップルーチンがリンクされ,WinMainを メイン関数に持つGUIアプリケーションは,WinMainを呼び出すものがリンクされます. main関数には,argc, argv などの引数があり,コンソールを開きstdin, stdoutを生成 します.それらの処理はスタートアップルーチンで実行されます.
    実行時に行われる処理は異なりますが,プロセスとしてはWinMainがメインでも mainがメインでも何ら変わりがありません.
    リソースに関しては,WinMainをメイン関数に持つGUIアプリケーションとmain関数を メイン関数に持つCUIアプリケーションに違いはありません.
    このため,CUIアプリケーションでウィンドウを生成したり,Win32 API DialogBoxで ダイアログボックスを生成することもできますし,アイコンやバージョンリソースを EXEファイル内に持たせることができます.

    結局,GUIアプリケーションとCUIアプリケーションの大きな違いは, アプリケーションの実行時に Win32 API AllocConsole でコンソールが自動的に生成 されるか,されないかということになります.

    20-8 共通処理のDLL化

    バージョンを管理して最も意味がありそうなのは,1アプリケーション内に存在する 複数のDLLではないでしょうか?ということで,多少こじつけのようですが, WindowsNTをインターネットのサーバとして利用するシステムが増えています.
    これにより,UNIXのプログラマーがWindowsNTでプログラムを開発することも多くなって きています.そこで,大変初歩的ですが,DLLの扱いについてまとめてみます.
    DLL内の関数をエクスポートするために,__declspec(dllexport) を利用する ことが多くなってきているのではないかと思われます.
    この記述を使うことにより,DEFファイルを作成しなくてもよいなどのメリットが あります.しかし,DLLのみを修正して配布する必要があるときには,大変困った問題 が起こることがあります.
    __declspec(dllexport)で作成したDLL同士がお互いにAPIを呼び出している構造に なっている場合,一方のDLLを修正すると呼び元のDLLは修正されたDLLのLIBを再リンク する必要があります.つまり,このような構成の場合,修正されたDLLの置き換えだけで 済まなくなります.このような状態でDLLを置き換えると,確実に実行エラーになれば まだわかりやすいのですが,中途半端に動作してしまいアプリケーションが異常終了 することがあります.単なる修正だけならあまり問題がないのですが,エクスポート する関数を増やしたり,減らしたりした場合にトラブルが発生しますので,注意が 必要です.このような問題を回避するには,面倒でも APIENTRY と DEFファイルで関数 をエクスポートしておいた方が無難ではないかと思われます.
    もう1つ注意しなければならない点は,ソースファイルが CPPの場合です. C++の関数は,見た目は同じですが,Cの関数とは異なります.C++で作成した関数を, Cで作成したモジュールで利用する必要がある場合には,extern "C" を設定しなければ 関数をリンクできません.

    リスト20-3:__declspec(dllexport)でエクスポートするCファイル
    
    #ifdef	__cplusplus
    extern "C" {
    #endif
    	__declspec(dllexport) int Func(void);
    #ifdef __cplusplus
    }
    #endif
    
    
    __declspec(dllexport) int Func(void)
    {
        :
        :
        :
    }
    

    リスト20-4:Cファイル
    
    #ifdef	__cplusplus
    extern "C" {
    #endif
    	int APIENTRY Func(void);
    #ifdef __cplusplus
    }
    #endif
    
    
    
    int APIENTRY Func(void)
    {
        :
        :
        :
    }
    

    リスト20-5:DEFファイル
    
    LIBRARY        MYDLL
    CODE           PRELOAD MOVEABLE DISCARDABLE
    DATA           PRELOAD MOVEABLE SINGLE
    EXPORTS
                   Func        @10
    

    20-9 プログラムについて

    ●使い方
    モジュール内のバージョン情報を表示します.
    ファイル名は,ワイルドカードも利用できますが,フルパスで指定する必要が あります.
    ◆すべての情報を表示する
    >DISPVER /all ファイル名
    ファイル名                        :dispver.exe
    構造体の識別子                :FEEF04BD
    構造体のバイナリバージョン          :00010000
    ファイルバージョン上位32ビット         :00010000
    ファイルバージョン下位32ビット         :00000000
    製品バージョン上位32ビット         :00030000
    製品バージョン下位32ビット         :00000000
    有効なファイルフラグ                :0000003F
    ファイルフラグ                      :00000000
    設計対象となったOS            :00040004
    ファイルの一般的な種類            :00000001
    ファイルの機能                    :00000000
    ファイル作成日付・時刻上位32ビット  :00000000
    ファイル作成日付・時刻下位32ビット  :00000000
    モジュールのコメント                  :モジュールバージョン表示ツール
    モジュール提供会社名              :CQ出版社
    ファイルの説明                    :DISPVER
    ファイルバージョン                   :1, 0, 0, 0
    内部名                        :DISPVER
    著作権                        :Copyright (C) 1997 S.Tsuneoka
    オリジナルファイル名                  :DISPVER.EXE
    製品名                        :Win32サブルーチンズ
    製品バージョン                   :4, 0, 0, 0
    

    ◆バージョンのみを表示する
    >DISPVER /ver ファイル名
     dispver.exe : プロダクト: 3.0.0.0 ファイル: 1.0.0.0

    ●ソースファイル
    DISPVER.EXE (バージョン情報を表示する)
     DISPVER.C (ソースファイル)
     DISPVER.RC (リソースファイル)
     DISPVER.ICO (アイコンファイル)
     MAKEFILE (メイクファイル)