Microsoft Windows 2000 アプリケーションの互換性

Kyle Marsh
Microsoft Corporation

November 1999

概要 : Microsoft® Windows® 2000 でアプリケーションの互換性が損なわれる問題について、以下に説明します。

はじめに
セットアップとインストールの問題
Windows 2000 の互換性の問題
アプリケーションの安定性の問題
Windows プラットフォーム間の違い

はじめに

過去数ヶ月間にわたり、Windows 2000 オペレーティング システムでのアプリケーションの互換性について、問題を探し続けてきました。ここでは、Windows 2000 においてアプリケーションの互換性が損なわれる可能性のある問題について説明します。残念ながら、アプリケーションの互換性について、関心が示されることはほとんどありません。

Windows 2000 テスト チームは、過去数ヶ月間に何百ものアプリケーションのテストを行いました。このチームでは、Windows 2000 のアプリケーションの稼動に影響を与える要因について、詳細な記録を行いました。見つかった問題は次の 4 つに分類することができます。

  • Windows 2000 にアプリケーションをインストールできない  これは現時点での最多の問題です。Windows 2000 であっても、アプリケーションのインストール方法に大きな違いはありません。問題は、アプリケーション自体を新しいバージョンのオペレーティング システムにインストールできないということです。

  • オペレーティング システムの変更が、アプリケーションの実行に影響するもの   Microsoft Windows NT® 開発チームは、より信頼性の高い安定したプラットフォームのシステムを作成するか、あるいはアプリケーションの互換性を維持するかの二者択一を迫られた場合、常により信頼性の高いシステムの実現を優先しています。Windows 2000 開発の主な目標の 1 つは、システムをプラットフォームとしてより信頼性の高いものにすることです。この目的のために加えられたいくつかの変更によって、残念ながら既存アプリケーションと Windows 2000 の互換性は損なわれることになりました。

  • オペレーティングシステムに対する変更   オペレーティングシステムに対する変更が、アプリケーションとの互換性を損なう原因ではないが、結果的に、一部のアプリケーションが破綻する場合があります。

  • Windows 9x プラットフォーム専用のアプリケーション   Windows 9x から Windows 2000 にアップグレードするであろう多くのユーザーを対象としているため、Windows 9x アプリケーションを Windows 2000 上で使用するテストを行ってきました。このテストにより、一部のアプリケーションは Windows 9x 上のみで実行可能であることが判明しました。

セットアップとインストールの問題

まず最初に、セットアップとインストールの問題について述べます。どのような場合にアプリケーションが Windows 2000 にインストールできないのかという点が最も一般的な問題になります。実際には、Windows 2000 が Windows NT のバージョン 5.0 であることが、アプリケーションをインストールできない最も一般的な理由です。

テスト チームはさまざまな方法でアプリケーションのテストを行っています。アプリケーションのテストは、対象となるアプリケーションを実際に Windows 2000 ベースのシステムにインストールして行われます。また、Windows NT 4.0 または Windows 95 にアプリケーションをインストールしてからシステムを Windows 2000 にアップグレードするテストも行われます。

その結果、クリーンなマシン (対象となるアプリケーションが何もインストールされていないマシン) に Windows 2000 をインストールしてからアプリケーションをインストールしようとすると、上記の問題により、対象アプリケーションをインストール済みの Windows NT 4.0 または Windows 98 から OS だけをアップグレードする場合よりも互換性テストの成績が低くなることが判明しています。

バージョンの確認

アプリケーションを Windows 2000 にインストールできない一番の理由は、アプリケーションがバージョン番号を正しく扱わないことです。多くのアプリケーションが次のサンプル コードと同じような動作をすることが判明しています。アプリケーションを実行すると "GetVersionEX" が呼び出されます。そして "バージョン 3 ならば、新しいシェルがないと動作できないのでインストールを行わない。バージョン 4 ならば、インストールして設定を行う" という "if" ステートメントが作成されます。バージョン 5 の場合、この "if" ステートメントに該当する条件がないので問題が発生します。このように、バージョン番号が 5.0 であるためインストールできないという理由でセットアップが失敗するものが多数見つかりました。

if (osvi.dwMajorVersion == 3)
   {
   // 処理を行います。
   }
else if (osvi.dwMajorVersion == 4)
   {
   // 処理を行います。
   }

テスト チームはこの問題に対して、虚偽の値を与えることでこれらのアプリケーションをだますことにしました。初期の構造では、GetVersionEx からの戻り値を変更することができます。戻り値をバージョン 4.0 に変更すると、アプリケーションは問題なくインストールを行い、正しく動作します。アプリケーションによっては、故意に Windows 2000 へのインストールを拒否するものもあります。これは、特定のオペレーティング システムに密接に関連しているウィルス スキャナやそのほかの低レベルのユーティリティなどでは仕方のないことです。ただし、これらのアプリケーションでは通常、インストールできないことを示すメッセージが表示されます。これまで問題が発見されたアプリケーションではインストールや動作が正常に行われないだけで、ユーザーに対するメッセージは表示されません。

どうすればバージョン番号を正しく確認できるのでしょうか。Windows 2000 には "VerifyVersionInfo" という新しい API が追加されています。この API は、階層構造を使用してメジャー バージョン、マイナー バーション、およびサービス パックを確認します。そうすれば、新しいバージョンのオペレーティング システムであっても、アプリケーションは従来通りインストールされて動作します。"VerifyVersionInfo" の使用には数多くのオプションや方法がありますが、「アプリケーションの動作に必要なオペレーティング システムかどうか?」 を確認するだけならば、これらの 3 つのフラグを使用して呼び出しを行い、メジャー バージョン、マイナー バーション、サービス パックを確認することができます。「アプリケーションには SP 2 を適用した Windows NT バージョン 4.0 が必要である」 というような内容を記述し、"VerifyVersionInfo" に 「実行している OS は必要要件に沿っているか?」 とたずねます。"VerifyVersionInfo" は真 (True) または偽 (False) を返します。

VerifyVersionInfo(&osvi, 
      VER_MAJORVERSION |
      VER_MINORVERSION |
      VER_SERVICEPACKMAJOR,
      dwlConditionMask);

この方法でバージョン方式を確認すると、「利用可能ならば、新しいオペレーティング システムにインストールできなければならない」 という Windows 2000 の基本的なアプリケーション仕様に沿うことができます。

"VerifyVersionInfo" を使用する上での問題は、現在 Windows 2000 プラットフォームでしか利用できない点です。Windows 95 などの旧バージョンのプラットフォームを確認するには "GetVersionEx" を使用します。以下のコード例を見ると、"VerifyVersionInfo" と同じ動作をしていることがわかります。この場合も、階層的なコードを利用してメジャー バージョン、マイナー バーション、およびサービス パックの確認を行います。

BOOL bIsWindowsVersionOK(DWORD dwMajor, DWORD dwMinor, DWORD dwSPMajor )
   {
   OSVERSIONINFO osvi;
   // OSVERSIONINFO 構造を初期化します。
   //
   ZeroMemory(&osvi, sizeof(OSVERSIONINFO));
   osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
   GetVersionEx((OSVERSIONINFO*)&osvi);
   // 最初にメジャー バージョンです。
   if ( osvi.dwMajorVersion > dwMajor )
      return TRUE;
   else if ( osvi.dwMajorVersion == dwMajor )
      {
      // 次にマイナー バージョンです
      if (osvi.dwMinorVersion > dwMinor )
         return TRUE;
      else if (osvi.dwMinorVersion == dwMinor )
         {
         // OK です。サービス パックも確認します。
         if ( dwSPMajor && osvi.dwPlatformId == VER_PLATFORM_WIN32_NT )
            {
            HKEY   hKey;
            DWORD   dwCSDVersion;
             DWORD   dwSize;
            BOOL   fMeetsSPRequirement = FALSE;
 
            if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                         "System\\CurrentControlSet\\Control\\Windows", 0, 
                         KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS)
               {
               dwSize = sizeof(dwCSDVersion);
               if (RegQueryValueEx(hKey, "CSDVersion", 
                        NULL, NULL, (unsigned char*)&dwCSDVersion, &dwSize) == ERROR_SUCCESS)
                  {
                  fMeetsSPRequirement = (LOWORD(dwCSDVersion) >= dwSPMajor);
                  }
                RegCloseKey(hKey);
                }
            return fMeetsSPRequirement;
            }
         return TRUE;
         }
      }
 
   return FALSE;

   }

//
// この例は Windows2000 以降のバージョン用です。
//
BOOL bIsWindowsVersionOK(DWORD dwMajor, DWORD dwMinor, DWORD dwSPMajor )
    {

    OSVERSIONINFOEX osvi;
   ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
   osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
   osvi.dwMajorVersion = dwMajor;
   osvi.dwMinorVersion = dwMinor;
   osvi.wServicePackMajor = dwSPMajor;
   // 条件マスクを設定します。
   VER_SET_CONDITION( dwlConditionMask, VER_MAJORVERSION, VER_GREATER_EQUAL );
   VER_SET_CONDITION( dwlConditionMask, VER_MINORVERSION, VER_GREATER_EQUAL );
   VER_SET_CONDITION( dwlConditionMask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL );
   // テストを実行します。
   return VerifyVersionInfo(&osvi, 
                             VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR,
                          dwlConditionMask);
     }

最初にメジャー バージョンの確認を行います。稼動しているオペレーティング システムのメジャー バージョンが必要なメジャー バージョンより高い場合、それ以上の確認を行う必要はありません。そのまま先に進みます。メジャー バージョンが同じ場合、同様にマイナー バーションを確認します。最後にサービス パックを確認します。ポイント リリースの際には常に 「はい、結構です。これがサービス パック 3、4、または 5 であるかどうかは重要ではありません」 と指定することができます。メジャー リリースまたはマイナー リリースの番号が高くなっても問題はありません。バージョンを確認するあらゆる種類のアプリケーションがあり、これらのアプリケーションの中には各々を別々に確認します。例えば、「はい、これはメジャー バージョン 5 です。必要なバージョンは 4 なので問題ありません。マイナー バージョンも .0 で問題はありません。ただし、サービス パックは 0 でなく 3 が必要です」 と報告します。現時点でまだ Windows 2000 にサービス パック 3 がないことは明らかです。このため、この方法はバージョンを確認する正しい方法とは言えません。

Windows NT でバージョン番号を確認する例を挙げます。バージョン番号やサービス パック情報を確認する方法は 3 つあります。1 つ目は GetVersionEx の戻り値を取得して "szCSDVersion" 文字列を確認する方法です。この文字列のどこかに実際のサービス パック番号が含まれています。解析は非常に困難で常にこれがローカライズされたものかどうかを念頭に置いておく必要があるため、この方法は最善の策とは言えません。Windows NT 4.0 で実行する場合は、以下のレジストリ キーを確認します。レジストリ キーにはサービス パックの番号のみが含まれています。そこで、これを取り出して「同じ」 か、それより「大きい」といった比較を行ってください。

HCLM\System\CurrentControlSet\Control\Windows\CSDVersion

Windows 2000 以降のバージョンでも "GetVersionEx" を使用できますが、これまで使用していた "OSVERSIONINFO" 構造ではなく "OSVERSIONINFOEX" 構造を使用する必要があります。Windows 2000 は最初のオペレータ メンバのサイズからこれをより大きな構造と見なし、サービス パック、メジャー バージョン、マイナー バージョンといった新しいフィールドを比較可能な整数として提供します。実際に "VerifyVersionInfo" を使用すると、これが最も簡単な方法だとわかります

DLL バージョンの確認

Windows のバージョン確認と並ぶもう 1 つの問題は、ユーザーによる DLL のバージョンの確認が一般的に行われていないことです。DLL をシステムにコピーする前に、それがシステム ディレクトリの Microsoft DLL なのか、あるいは独自の DLL なのかを確認する必要があります。新しい DLL の上に古いバージョンの DLL をコピーしないよう常にバージョンを確認してください。

バージョン情報を実際に DLL に追加して、バージョンの確認が可能になっている事も確かめてください。これは、あらゆる問題を回避するために非常に重要です。そして、システム ディレクトリ内の DLL は変更しないでください。また、システム DLL のアップグレードやダウングレード、既存の DLL の上書きを行わないでください。システム DLL は、Windows 2000 の CD-ROM またはサービス パックによって配布されるシステム ディレクトリに含まれる DLL です。

新しい Windows 2000 アプリケーションを開発する場合、常に DLL のバージョン確認を行う Windows インストーラを使用できます。特定の DLL を特定のディレクトリにコピーしようとすると、Windows インストーラによって常にバージョンが確認されます。これにより、コードを使ってこの処理を行う必要がなくなります。

DLL Hell

DLL のバージョン番号を正しく確認しないと、DLL Hell を招きます。DLL Hell についての説明は必要ないでしょう。皆さんもこのために多くの時間を費やしたことと思います。私自身も ctl3d.dll を使って作業を行っていたため、DDL Hell を体験してきました。

何年もの間、壁にぶつかりながらも DLL を正常に動作させようと努力した結果、アプリケーション提供者は過去の DLL との互換性を維持できないという結論に達しました。互換性を維持するという大きな目標のために全員が多大な努力を払いましたが、うまくいきませんでした。事実上、DLL の互換性を完全に維持することは不可能です。結論として、あるアプリケーションが動作するには常に特定のバージョンの DLL を必要とし、ほかのアプリケーションは別のバージョンの DLL を必要とします。これらの 2 つのアプリケーションは、共有するコンポーネントや共有する DLL をめぐって互いに競合するため、同じシステム上で動作させることはできません。

しかし、今もなお DLL の共有が Windows のアプリケーションにとって好ましく重要であると考えています。DLL によって受けることができる利益は今でも貴重なものです。問題は、バージョンのテストを行うことができないままアプリケーション間で DLL を汎用的に共有すると多くの障害が起こる点です。

Side-by-Side DLL

Windows 2000 には Side-by-Side DLL と呼ばれる機能が実装されています。我々は、アプリケーションを Side-by-Side バージョン方式へ移行することをお勧めします。Windows 2000 では、DLL Hell を少なくする対応をあらかじめ行っています。第一に、どんなアプリケーションがインストールされる場合でも必ずシステムは保護され、外部からの影響を受けないようになります。Windows File Protection (Windows のファイル保護) 機能についても簡単に説明します。

もう一つは、コンポーネントの Side-by-Side を可能にすることで、アプリケーション ベンダにも実行していただきたいことですが、既に Microsoft のコンポーネントでは行われており、皆さんのコンポーネントでも同じことを行うようお勧めします。今後は Side-by-Side バージョン方式を取り入れていきます。ぜひ皆さんも現在汎用的に共有しているすべてのコンポーネントに Side-by-Side バージョン方式を採用してください。

システムの安定性

Windows NT チームが積極的に取り組んでいるもう 1 つの作業は、時間の経過にかかわらずシステムの安定性を保証することです。Microsoft で起きている問題の 1 つに、サービス パックの配布によりさまざまな機能を Windows NT プラットフォームに追加したことです。これはかなりの修正になるため、Windows NT をインストールしている消費者と企業はこのサービス パックを取得することに懐疑的になりました。修正が数箇所で終わることはほとんどなく、"この機能をここに追加して、この機能をあっちに追加して" となると、あるべきシステムの安定性が損なわれます。

基本的に、今後のサービス パックにはバグ修正機能だけが含まれます。オペレーティング システムにとって重要な変更となる性能や機能が開発された場合、それを Windows 2000 のポイント リリースとして出荷します。 Windows NT 5.1 や 5.2 といった形のリリースを行い、もちろん各バージョンのためのサービスパックを提供する予定です。これにより、QFE、バグの確認、最新の修正に関する限りでは、各プラットフォームを引き続き完全に機能させ続けることができます。新しい機能や仕様が追加されるときは、新しいオペレーティング システムがリリースされるということを意味します。

最後に皆さんにぜひお勧めしたいのは、どのコンポーネントがどの製品に付属していたかを認識しておくことです。そして我々は、それぞれの製品に付属するコンポーネントの数が最小限になるようにしています。特定のコンポーネントが別の特定のコンポーネントと共に動作する場合には、必ずそれらを一緒に出荷しています。今後は、これら再配布可能なすべてのコンポーネントの出荷方法を体系化していく予定です。

Side-by-Side DLL

汎用的に共有されるコンポーネントまたは DLL を新しい Side-by-Side DLL に変更する場合、現在の DLL にいくつか変更を加える必要があります。DLL 自体にその変更を加えます。ここでは、「複数のバージョンを同時に実行するようにコンポーネントを設計する」 と宣言する必要があります。

コンポーネントを完全に Side-by-Side コンポーネントに移行するには、まず DLL 自体の名前を変更してから、COM オブジェクトである OCX コントロールにあるすべての GUID も変更する必要があります。名称変更を一度行うだけで、汎用的に共有されるのではなく Side-by-Side で実行可能な新しい DLL を実際に入手することができます。

DLL の名前を変更すると、アプリケーションは DLL をシステム ディレクトリにインストールする代わりに、自身が管理するディレクトリにインストールできます。つまりアプリケーションの開発者は、「この DLL の特定バージョンを使って製品は詳細にテストされています。もう一度テストするまでこの DLL がアップグレードされることはありません」 と言うことができるのです。これにより開発者は、システムの停止や DLL Hell の原因となる共有のコンポーネントによってアプリケーションが破壊されないと確信することができます。

このようなコンポーネントをコンシューマとして使用する場合、システム ディレクトリではなく自身のローカル ディレクトリに相対パスを登録します。こうすれば、システムのどこか離れた場所にあるグローバル コピーの代わりにローカル コピーが読み込まれます。

アプリケーションが相対パスを使用してコンポーネントを登録している場合、システム ディレクトリに存在しているか別の場所で実行されているかにかかわらず、常にコンポーネントを相対パスで読み込むように "LoadLibrary" を変更しました。これにより、アプリケーションをテストしたコンポーネントのコピーを確実に入手することができます。

アプリケーションの分離

DLL のリダイレクトをサポートするために "LoadLibrary" コードも変更されています。これにより管理者は、1 か所での DLL の読み込みを、ローカル ディレクトリから DLL を読み込むように変更することによって、DLL を分離することができます。アプリケーションが使用可能かどうかのテストが大企業内で行われているとします。既に別のアプリケーションがインストールされているので、2 つのアプリケーションを同時に実行できるかどうかを調べています。2 つが同時に実行されない場合、管理者は、どこで、どのコンポーネントが原因でアプリケーションが競合しているかを明確にする必要があります。そのコンポーネントを見つけた上で、従業員が両方のアプリケーションを使用する必要があるとした場合、管理者は DLL (またはオブジェクトを含む OCX) を外して、アプリケーションのディレクトリに入れます。その後、foo.exe 名の付いたファイルを成して、末尾に .local と付け足します。"LoadLibrary" が呼び出されると、"LoadLibrary" は foo.exe.local ファイルを見つけ、アプリケーションが "LoadLibrary" の呼び出しに指定したパスにかかわらず、まずアプリケーション ディレクトリからコンポーネントを読み込みます。これにより、ユーザーは別バージョンのコンポーネントを必要とする複数のアプリケーションを展開し、同じシステム上で実行させることができます。

Windows File Protection

プラットフォームの信頼性を高め、システムをより安定させるための第一歩は、システム自体が DLL Hell に陥らないようにすることです。何が起こっても確実に動作し、起動し続けるシステムこそ我々の望みです。だからこそ、ユーザーはシステムの安定性を信頼することができるのです。

WFP (Windows File Protection) では、アプリケーションがシステム ファイルの一部を変更した場合に Windows 2000 がそれを元に戻します。アプリケーションをインストールすると、いくつかの機能に対して "この DLL の新しいバージョンが必要だ" といった確認をするかもしれませんし、品質の悪いアプリケーションならばバージョンの確認すら適切に行わないかもしれません。Windows 2000 はファイルを探し、変更されているかどうかを確認します。Windows 2000 はシステム ファイルを確認すると、"このファイルを変更することはできない" としてこれを元に戻します。

システムによってロックされているファイルをアップグレードする唯一の方法は、Windows NT チームから提供されるサービス パック、QFE、最新の修正などのサポート ファイル交換メカニズムを利用する方法です。このメカニズムを使用する場合のみシステム ファイルを変更することができ、ほかのアプリケーションでは変更することができません。

たとえば、ロックしたファイルの 1 つに mfc42.dll があるとします。言語グループがこの DLL をアップグレードすることはできません。システム上でこの DLL を変更できるのは、Windows NT グループだけです。C 言語の使用者が DLL をアップグレードする (Windows 2000 と Windows NT の次期バージョンの出荷の間に更新する) 唯一の方法は、Side-by-Side コンポーネントのバージョン方式を採用することです。

ほとんどの、*.sys、*.dll、*.exe、*.ocx ファイルといくつかのフォント ファイルが保護の対象となっています。

ここで互換性の問題が 2 つあります。まずバックアップや復元アプリケーションと同様に、アンチウィルス アプリケーションは Windows のファイル保護を認識して共に動作しなければなりません。これらのファイルを単にコピー、バックアップ、復元することはできません。システムがサポートするファイル交換メカニズムを利用しないと、作業を行っても Windows のファイル保護に阻止されます。

ファイル保護による阻止が行われないように、システムに 2 つの API を追加しました。

WFP API

最初の API は、"SFCGetNextProtectedFile" です。この API を使用すると、保護されている、または保護されている可能性のある全ファイルのリストを取得できます。 NULL の状態から開始してこの API を繰り返し呼び出し、保護されているファイルのリストを取得します。

BOOL WINAPI SfcGetNextProtectedFile (IN HANDLE RpcHandle, IN PPROTECTED_FILE_DATA ProtFileData );
//
// この機能により、保護ファイルが一覧表示されます。
//
void ListProtectedFiles(HWND hWnd)
   {
   HWND   hwndList;
   PROTECTED_FILE_DATA pfd;
   int iCount;
   char szFileName[260];
   int iLen;
   RECT rt;
   
   hwndList = GetWindow(hWnd,GW_CHILD);
   if ( hwndList == NULL )
      {

      GetClientRect(hWnd, &rt);
      // 初めての場合、リスト コントロールを作成します。
      hwndList = CreateWindow("LISTBOX", NULL,
                        WS_CHILD | WS_VISIBLE | LBS_STANDARD | LBS_NOINTEGRALHEIGHT |
                        LBS_USETABSTOPS,
                        0,20,rt.right,rt.bottom-40,
                        hWnd,
                        NULL,
                        hInst,
                        NULL);
      }
   else
      SendMessage(hwndList, LB_RESETCONTENT, 0, 0);

   ZeroMemory(&pfd,sizeof(PROTECTED_FILE_DATA));
   iCount = 0;
   while ( g_pfnSfcGetNextProtectedFile(NULL, &pfd) != 0 )
      {
      // ANSI アプリケーション用に WCHAR を ASNI に変換します。
      iLen = WideCharToMultiByte(CP_ACP,NULL,pfd.FileName, wcslen(pfd.FileName),
                      szFileName,260,NULL,NULL);
      szFileName[iLen] = '\0';
      SendMessage(hwndList, LB_ADDSTRING, 0, (LPARAM)szFileName);
      iCount++;
      }
   }

より直接的な API は "SfcIsFileProtected" です。ほとんどのアプリケーションにとっては、直接呼び出しを行って "このファイルは保護されているのか?" とたずねるほうがはるかに簡単です。ただし、ファイルの絶対パスが必要である点に注意してください。NTS.sys と指定するだけでなく、NTS.sys が存在する場所までのパスを指定する必要があります。このファイル名を API に渡すと、"はい、これは保護されているファイルです" または "いいえ、これは保護されているファイルではありません" という答えが返ってきます。このため、バッグアップや復元作業を行う場合は、この API を使用する必要があります。設定作業やシステム ファイルの更新を行う場合も、この API を呼び出します。目的のファイルがあり、それをシステム ディレクトリに入れようとする場合は、Windows ファイル保護によって阻止されるのを避けるために事前に API を呼び出してください。次のバージョンの Windows インストーラ (Windows 2000 に付属) はコピーの前に確認を行うため、予期せず WFP が動作することはありません。

BOOL WINAPI SfcIsFileProtected (IN HANDLE RpcHandle,IN LPCWSTR ProtFileName);

//
// この関数は、File open を使用してユーザーからファイル名を取得し、それが保護されているかどうかを確認します。
void CheckFileForProtection(HWND hWnd)
   {
   OPENFILENAME OpenFileName;
   CHAR szFile[MAX_PATH]      = "\0";
   CHAR szSystem32[MAX_PATH];
    strcpy( szFile, "");
   ZeroMemory(&OpenFileName, sizeof(OPENFILENAME));
   // OPENFILENAME 構造を作成し、テンプレートおよびフックをサポートします。
   OpenFileName.lStructSize       = sizeof(OPENFILENAME);
    OpenFileName.hwndOwner         = hWnd;
    OpenFileName.hInstance         = hInst;
    OpenFileName.lpstrFile         = szFile;
    OpenFileName.nMaxFile          = sizeof(szFile);
    OpenFileName.lpstrTitle        = "Select a File";
    OpenFileName.Flags             = OFN_FILEMUSTEXIST;

   if (g_pfnSHGetFolderPath != NULL )
      g_pfnSHGetFolderPath(NULL, CSIDL_SYSTEM, NULL, NULL, szSystem32);
   else
      szSystem32[0] = '\0';
   OpenFileName.lpstrInitialDir   = szSystem32;
   // 共通ダイアログ関数を呼び出します。
    if (GetOpenFileName(&OpenFileName))
      {
      // ファイルを確認します。
      WCHAR wzFileName[260];
      int iLen;
      iLen = MultiByteToWideChar(CP_ACP,NULL,szFile, strlen(szFile), wzFileName, 260);
      wzFileName[iLen] = '\0';
      if (g_pfnSfcIsFileProtected(NULL, wzFileName) == TRUE )
         {
         MessageBox(hWnd,"Is Protected", szFile, MB_OK);
         }
      else
         MessageBox(hWnd,"Is NOT Protected", szFile, MB_OK);

      }

コンポーネントの確認

Windows 2000 上でセットアップできないアプリケーションに関する次の問題は、コンポーネントの確認に関する問題です。確かに、オペレーティング システムの各バージョンは多くの異なったコンポーネントで構成されています。コンポーネントには、TAPI、MAPI、Microsoft DirectX® などがあります。コンポーネントが存在する場所や、コンポーネントの存在そのものについて、多くのアプリケーションが仮説を立てていることが判明しました。アプリケーションはあるコンポーネントのバージョン 2 が存在するなら別のコンポーネントのバージョン 3 も存在するはずであるとというように、あるコンポーネントの存在により別のコンポーネントの存在を想定しています。コンポーネントに依存する必要がある場合は、システム上にその必要なコンポーネントがあるか、また正しいレベルにあるかを正しく確認する必要があります。

また、一般に Windows NT には DirectX がないと考えらえています。過去には、これが正しかった時期があり、アプリケーションがこれに基づいて作成されたことがあります。確認の段階で、アプリケーションは Windows 2000 に DirectX がないと想定し、「実行できない」 と答えます。しかし Windows 2000 には DirectX があるため、この想定は正しくありません。

我々が直面したもう 1 つの問題は、ハードコードの問題です。この場合アプリケーションは、コンポーネントが間違った場所にあると見なしてしまいます。使用できないパスのハードコードを行っているうちはアプリケーションは動作しません。

もう 1 つの例は、Windows 2000 には現在 TAPI (最新バージョンである TAPI 3.0) と DirectX (最新バージョンである 7) が標準で含まれますが、MAPI が含まれていない点です。従来であれば、Windows NT であれば MAPI があると判断できましたが、必ずしもそうとは限りません。コンポーネントを使用する場合は、MAPI の存在を常に確認し、プラットフォームやコンポーネントに対してこのような仮定しないようにする必要があります。

間違った場所へのファイルのインストール

もう 1 つのセットアップ時の問題は、間違った場所へのファイルのインストールに関する問題です。誰かがどこか別の場所に既にインストールしているファイルをアップグレードする場合は問題ありません。ユーザーが都合の良い場所にファイルを入れてください。ただし、最初にインストールする場合は常に Program Files ディレクトリを既定値にしてください。

メモ これは必ずしも C:\Program Files ではありません。C:\Program Files である場合と C:\Program Files でない場合があります。例えば、私のマシンでは C:\Program Files ではありません。このマシンでは、Windows NT パーティションを別のパーティションにインストールし、C パーティションを Windows 98 用に空けています。

可能であれば、ファイルを Windows ディレクトリや System32 のようなサブディレクトリに入れないでください。以前は常にこれをお勧めしてきたため、これ自体は何ら悪いこととは言えません。しかし、システム上の全ファイルをもう少し組織立てたいと思います。ユーザーが System32 ディレクトリの何百何千ものファイルにうんざりしていることは知っています。ユーザーは各ファイルの内容について何も知らないため、削除もしません。「毎年システムを掃除して、最初から Windows をインストールし直した方がいいよ」 と言う人もいるかもしれません。アンインストール ソフトやクリーン アップ ソフトなどのアプリケーションは広く市販されています。我々はマシンをクリーン アップしてシンプルにしたいと考えています。

可能であれば、いくつかの共有コンポーネントも別の場所に書き移すことをお勧めします。Microsoft Visual Basic はこのよい例です。これは多くのアプリケーションで使用されています。Visual Basic を旧バージョンの Visual Basic が常にインストールされていたシステム ディレクトリに入れるだけではなく、Program Files の特定のフォルダにインストールしています。

どこにファイルを入れたらよいかを知るためには、Windows 2000 に含まれている "SHGetFolderPath" という新しい API を呼び出します。また、Windows 98 Second Edition にも既に含まれています。システムに API がない場合は、"SHFolder.dll" を配布します。この DLL は、この新しい API シェル "SHGetFolderPath" を公開します。この API は、重要なフォルダがシステム上のどこにあるかを把握しています。SDK で API を調べることができます。これは、使用可能な全フォルダを集めた非常に長いリストです。

旧バージョンのプラットフォームでは、これらの 4 つの CSLID のみがサポートされていることに注意する必要があります。特定のディレクトリを探していて見つからない場合、指定すれば "SHGetFolderPath" によって作成できますが、Windows 95 などの旧バージョンのプラットフォームでは、これら 4 つの CSIDL に対してのみ機能します。

CSIDL_PERSONAL

CSIDL_APPLICATIONDATA

CSIDL_MYPICTURES

CSIDL_LOCAL_APPLICATIONDATA

次のコード例は、"SHGetFolderPath" の使用方法を示しています。これは、非常に簡単に使用できる API です。まず注意しなければならないのは、Windows 2000 のようにネイティブの "SHGetFolderPath" を備えたプラットフォームと、Windows 95 のようにこれを備えていないものを含むすべてのプラットフォームでコードを実行する場合、アプリケーションが常に SHFolder.dll の実装に動的にリンクする必要があることです。

            // SHFOLDER がインストールされていれば、どこでも SHGetFolderPath を使用することができます。
            HMODULE hModSHFolder = LoadLibrary("shfolder.dll");
            if ( hModSHFolder != NULL ) 
               {
               (*(FARPROC*)&g_pfnSHGetFolderPath = GetProcAddress(hModSHFolder, "SHGetFolderPathA"));
               }
            else
               g_pfnSHGetFolderPath = NULL;
            }

   if (g_pfnSHGetFolderPath != NULL )
      g_pfnSHGetFolderPath(NULL, CSIDL_SYSTEM, NULL, NULL, szSystem32);
   else
      szSystem32[0] = '\0';
   OpenFileName.lpstrInitialDir   = szSystem32;

セキュリティ問題

設定とインストールの最後の問題として、Windows 2000 のセキュリティ問題についていくつか説明します。

  • システム全体で使用するアプリケーションをPower Usersがインストールできるように設定することをお勧めします。アプリケーションによっては、Administrators のみがインストールできるように設定されているものもあります。企業ではマシンをロックしたいと考え、そして多くのユーザーでマシンを共有したいと考えています。1 日 2、3 人が使用して、いつかは誰もがこのシステムを使用することを望んでいます。特定のユーザーが管理者権限を使って変更を加えたり、自分勝手にシステムを改造してほしくないと考えています。顧客の多くは、Administrators ではなく Power Users がインストールできるアプリケーションを望んでいると考えられます。

  • もう 1 つ大切なのは、ユーザーがシステム上の誰かのためではなく、自分で使用するためにアプリケーションをインストールできるようにすることです。やってみたいゲームがあれば、そのゲームをインストールしてシステムのほかのユーザーに使用させないようにする必要があります。しかし、Power Users 以外は Program Files ディレクトリに書き込むことができないことを思い出してください。このようなユーザーは HKEY_LOCAL_MACHINE に書き込むことができません。設定時に、HKEY_LOCAL_MACHINE を開いて書き込みが可能かどうかを確認し、システムが「あなたはここに書き込むことができません。このアプリケーションを個人用にインストールしますか?」 とユーザーにたずねるようにする必要があります。

  • もう 1 つのセキュリティ問題は、既定のサーバー権限に関するものです。ローカルの Administrators グループのメンバになっているアカウントなど、権限のないサービス アカウントを使用してサーバー アプリケーションやサービスをインストールする場合、このアカウントがサービスを実際に起動する権限を持つことは、非常に好ましくありません。Windows 2000 では、実際に権限のないサービス アカウントを持つ権限のないユーザーは、Windows NT 4.0 の場合とは別の権限を持っています。彼らの権限はほとんどありません。たとえば、権限のないユーザーは Windows システム ディレクトリに対して書き込みを行うことはできません。サーバーが何かの作業を行い、何かをセットアップしようと稼動している場合、正しい権限でアクセスされていない可能性があります。サーバーのアプリケーションを実行している場合は、正しいアカウントでログインし、正しく作業を行うために必要な権限があることを確認してください。

Windows 2000 の互換性の問題

次に、私が Windows 2000 の互換性の問題と呼んでいるものについて説明したいと思います。プラットフォームを改良してユーザーにとってより信頼性の高いプラットフォームを作成するという目標に近づくために、我々は Windows プラットフォームに変更を加えました。これらの変更は、Windows 2000 上でのいくつかのアプリケーションの実行に影響を与えます。

フォアグラウンド ウィンドウの設定

まず、フォアグラウンド ウィンドウの設定という簡単なものから始めましょう。実際、この変更は Windows 98 から始まっています。自動的にウィンドウを手前に表示させるために "SetForegroundWindow" に頼っていてはいけません。そうすれば、誰でもウィンドウを手前に表示できるということに含まれる潜在的な問題を回避することができます。入力作業を行っている場合に何かの要求ウィンドウが表示されると、本人が気付く前に知らない何かに同意するよう入力している場合があります。

これを避けるために、アプリケーションがいつ手前に表示されるかということについての規則ができました。これらの規則は Windows 98 から採用されています。

  • プロセスがフォアグラウンド プロセスの場合、ウィンドウをフォアグラウンドに表示できます。

  • プロセスがフォアグラウンド プロセスによって開始されたばかりの場合、ウィンドウをフォアグラウンドにできます。

  • プロセス直前の入力を受け付けていた場合は、そのウィンドウをフォアグラウンドにできます。

  • その時点でフォアグラウンド ウィンドウがない場合、ウィンドウをフォアグラウンドにできます。

  • フォアグラウンド プロセスのデバッグ中は、任意のウィンドウをフォアグラウンドにできます。

  • フォアグラウンド プロセスでタイムアウト ロックが発生した場合 (フォアグラウンド プロセスがしばらくの間何も行わず、応答していないような場合)、ほかのウィンドウをフォアグラウンドにできます。

  • 何らかのシステム メニューがアクティブの場合、アプリケーションはフォアグラウンドにはできません。これは、[スタート] メニューを使用する時のやっかいなインスタンスを対象としています。メニュー階層に沿って開始しようとしているアプリケーションを選択すると、突然画面から消えてしまうのです。この Windows 2000 の新しい規則によって、この現象をなくすことができます。

スーパー隠しファイル

Windows 2000 でのもう 1 つの新しい機能は、我々がスーパーの隠しファイルと呼んでいるものです。ここでシステムは、いくつかのファイルを「システム」 と 「隠しファイル」 という両方の属性でマークします。ファイルは引き続きそこに存在し、それらを使用できますが、この影響により、Windows エクスプローラではこれらのファイルが表示されません。この場合、隠しファイルを表示するオプションを選択してもこれらを表示することはできません。フォルダの属性リストに新しいチェック ボックスがあり、これを選択するとユーザーはこれらのファイルを表示することができますが、選択をしていない通常のユーザーがこれらのファイルを表示することはできません。

また、Windows 環境で目障りなファイルは表示されなくなりました。これは主に古い MS-DOS® ベースのファイルなどです。一般的なユーザーにはほとんど影響せず、よりクリーンなシステムが表示されていると認識されます。

32 ビット アプリケーションでは、これは本当の意味での互換性の問題とは言えません。アプリケーションはファイルを通常の [ファイルを開く] ダイアログ ボックスで確認して問題なくファイルを開くことができます。コマンド ラインからも従来通り動作します。上位の隠しファイルを表示するために Dir /ASH を実行すると、すべてのファイルを表示することができます。

ここでの唯一の互換性の問題は、16 ビット アプリケーションが多少の落とし穴に陥ることです。これらの呼び出しは MS-DOS の INT21 を介して行われます。隠しファイルを見つけるように指示しても、MS-DOS の INT21 は隠されているサービス ファイルだけを見つけだします。

隠されているいくつかのファイル

  • io.dos のような MS-DOS システム ファイル

  • Office Fast Find ファイル

NetBIOS

NetBIOS は常に Windows NT の一部でしたが、Windows 2000 では異なります。これは、通常の構成ではありませんが、NetBIOS を読み込まず存在しないようにユーザーがシステムを設定することができます。アプリケーションが NetBIOS の存在しないシステムで NetBIOS を使用する API を呼び出すと、正常に動作せず、エラーを返します。たとえば、"NetServerEnum" などの呼び出しを使用し、これが NetBIOS のないシステムで動作している場合、エラーが返されます。NetBIOS 呼び出しを使用している場所を調べて、NetBIOS のないマシンにこれが起こることを理解し、正しく処理してください。また、代わりに non-NetBIOS 呼び出しに切り替えることもできます。皆さんのシステムが常に NetBIOS を必要とすることをユーザーがきちんと理解していることを確認し、これをアプリケーションのセットアップまたはリリース ノートに記述することをお勧めします。

新しく必要になったネットワーク .inf ファイル

ネットワーク ドライバ、トランスポート ドライバ、ネットワーク ファイル 印刷機などのネットワーク機器をお持ちの場合、デバイスが Windows 2000 のプラグ アンド プレイをサポートするために、新しいネットワーク用の .inf ファイルがあることを確認する必要があります。ネットワーク機器を使用するシステムがクリーン インストールされた場合や、マシンを Windows NT 4.0 から Windows 2000 にアップグレードした場合は、常にこれらの新しいファイルが必要です。この形式は、Windows 98 と互換性があるため、皆さんも既にお使いになっていると思います。いずれの場合も、システムが Windows 2000 に移行したあとでもデバイスが利用され続けるように、これらのファイルを直ちにユーザーに提供する必要があります。

物理ドライブ番号

ご使用のアプリケーションがウィルス スキャナなどのように低レベルの方法でハード ドライブとボリュームにアクセスしようとする場合、物理ドライブ番号を見つけて必要がありますが、その番号の検索方法を変更する必要があります。かつてはシンボリック リンクを使用できました。これは、次のような形式の値を返しました。

\Device\HarddiskX\PartitionY

この場合では、"Harddisk" とそれに続く番号 X が見つかり、これによってハード ディスク 2 またはハード ディスク 3 であることを判断できます。今後、シンボリック リンクは以下のような形式の値を返します。

\Device\HarddiskVolumeZ

物理ドライブ番号は、このシンボリック リンクにはありません。その代わりに、取得可能な 2 つの IOCTL を使用する必要があります。まず最初に、

IOCTL_STORAGE_GET_DEVICE_NUMBER

シングル ドライブ番号の処理を行います。たとえば、ドライブが C ドライブの場合や、ドライブ内に複数のパーティションがある場合も、処理が行われます。ただし、マルチボリューム セットがある場合は、以下を使用する必要があります。

IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS

シンボリック リンクで物理ドライブ番号を見つける際には、常にわずかながら危険を伴います。シンボリック リンクはマルチドライブ セットになっている可能性のある最初のドライブのみしか報告してくれません。これは、Windows NT 4.0 でも同様でした。これは、Windows NT 4.0 でも同様でした。

テープ ドライブへのアクセス

テープ ドライブを使用するアプリケーションをお持ちの場合、そのテープ ドライブへのアクセス方法を変更する必要があります。新しい階層構造記憶域管理ではリムーバブル ストレージ マネージャと呼ばれるものを使用します。リムーバブル ストレージ マネージャはサーバーを調査して特定のファイルが長期間アクセスされなかったことを突き止めます。つまり、"このファイルをテープに記録しよう。もしだれかがこのファイルを要求したら、検索してテープから取り出せばいい" と考えるのです。ユーザーは長い間待つ必要がありますが、ファイルを取得することができます。このように、より大きなスペースをサポートしているように見えるより小型のドライブを使用できます。

リムーバブル ストレージ マネージャは常にサーバー上で実行され、アプリケーションがテープ ドライブを要求しようとしたとき、テープ ドライブが使用中であることが予想されます。アプリケーションが単純にテープ ドライブを独占することはできません。これを説明するためには、このWebページを遥かに上回るスペースが必要です。Microsoft.com で入手可能な「Development Considerations for Storage Applications in Windows NT 5.0」を一読することをお勧めします。このドキュメントには、新しいテープ ドライブの扱い方の概要が記載されています。実装を行う場合は、SDK に付属の『Removable Storage Manager Programmer's Reference』を調べる必要があります。これには、リムーバブル ストレージ マネージャとテープ ドライブを共有できるアプリケーションの作成方法が記載されています。

ディスプレイ ドライバのフック

ディスプレイ デバイスに割り込みを試みるアプリケーションを使用する場合は (たとえば、すべての呼び出しを横取りしてから元のディスプレイ ドライバを呼び出すディスプレイ ドライバなど)、その方法を変更する必要があります。皆さんも、リモート コントロールアプリケーションなど、このような動作を行うアプリケーションをご存知でしょう。これらのアプリケーションはディスプレイ ドライバのコマンドを受け、Line を通して呼び出しを 1 つ送り、ローカルで 1 つ実行します。Windows 2000 これを行う場合は、新しい DDML (Display Driver Management Level) を使用して出力をリモート デバイスにミラー化する必要があります。これは、リモート コントロール アプリケーションが何を行う場合でも、複数のディスプレイ ドライバの使用を可能にします。この説明は、DDK の Windows 2000 ベータ 3 リリース版にあります。

書き込み禁止のカーネル モード

これはプラットフォームの信頼性を高めるために Microsoft が設定した機能です。カーネル モードで実行されるものは、すべて実際にメモリの書き込み禁止領域を確保します。従って、いくつかのコード セグメントまたは文字列セグメントを使用するデバイス ドライバを持っていて、ショートカットとして読み取りのみのセクションにメモなどを入れた場合、Windows 2000 では使用できません。通常、保護機構を無効にすることはできません。これはシステムの停止の原因となります。

しかし、我々のテストにおいて、Windows 2000 のこの規則に違反している数多くのデバイス ドライバが発見されています。ドライバを調査して、デバイス ドライバが Windows 2000 ではなく Windows NT 4.0 を対象としていると判断した場合、システムはこの規則を強制しません。そうでなければ、多くのデバイス ドライバが動作しなくなります。ただし、Windows 2000 を対象として作成されたデバイス ドライバや、Windows 2000 で使用できるようにアップグレードされたものに対しては、システムはこの規則を強制します。

スタック消費の増加

互換性の根本的な問題に取り組むために、まず最初に Windows 2000 が Windows NT 4.0 より多くのスタック スペースを使用するようにしました。これにより、単一で世界共通の実行可能ファイルを持ち、かつてない大きなユニコード スペースを使用して、より多くの文字列を宣言することができます。結果として、システムはより多くのスタックを必要とします。アプリケーションによっては、自身のスタック サイズをできる限り小さくすることで自身の性能を調整しているものもあります。より速く動作することを望む場合、このような手法は好ましいことです。なぜなら、通常、使用するメモリが少なければ少ないほど動作速度が速くなるからです。だたし残念ながら、これらのいくつかは小さすぎ、システムとアプリケーションが共にスタック スペースを使い果たしてしまうために、システムが停止する場合があります。

開発するアプリケーションでこれを調べるためには /STACK リンカ オプションを確認するか、コンパイラの /F オプションまたは STACKSIZE パラメータを使用する STACKSIZE-.def ファイルを確認することによってわかります。これらすべてを調べ直し、スタック スペースが小さすぎないことを確認してください。

Win32 API の変更

Windows 2000 では、多くの Microsoft Win32® API が変更されました。今後、互換性に関して思わぬ障害が見つかるかもしれません。これらは Windows 2000 のテストで最も頻繁に見られたものです。

Windows 2000 では、新しい入力方法をサポートしています。この方法をサポートするために、WM_KEYUP と WM_KEYDOWN メッセージで取得可能な "wParam" でいくつかの情報を渡します。そのためには、"TranslateMessage" に手を加えていないこの "wParam" を渡す必要があります。これを受け取らなければ、この新しい入力方法の効果を十分に活用することができません。

もう 1 つの問題は、ダイアログ構造の内部にある DS_SHELLFONT に関する問題です。DS_SHELLFONT を指定すると、フォント フェイスを変更することができなくなります。通常、フォント フェイスとして Microsoft Shell Dlg 2 を使用しています。皆さんはサイズを変更することはできますが、フェイスを変更することはできません。

[ファイルを開く] ダイアログ ボックス用の OPENFILENAME STRUCTURE では、初期ディレクトリに対する動作がわずかに異なります。[ファイルを開く] で、探す種類のファイルが全く見つからない場合、既定によりショートカットとして My Documents フォルダに直接移動します。

GetWindowsDirectory は、ユーザーごとのシステム ディレクトリを返します。ターミナル サーバーで作業している場合、正しいシステム ディレクトリを取得していると気付かずに、特定のユーザーのために設定されたシステム ディレクトリを取得する場合があります。新しい GetWindowsSystemDirectory 呼び出しを使用すると、常にターミナル サーバーで実際のシステム ディレクトリが返されます

アプリケーションの安定性の問題

ここでは、我々がアプリケーションの安定性の問題と呼んでいるものについて説明します。これは Windows 2000 に加えた変更によって、それまで見えなかった、アプリケーションの実装でのバグが表面化したり、細かい部分で、アプリケーションの互換性を損なう原因となっています。しかし、我々の加えた変更はアプリケーションに損傷を加えるものではありません。そのような場合、アプリケーションが非公式の方法で実装されている可能性があります。

ハードコード化されたパス

アプリケーションがハードコード化された参照先を使用している場合が頻繁にあります。この場合、Microsoft がシステムの内容を移動させて変更を行うと、アプリケーションが探しているものがこれまでの場所にないために動作しなくなります。この主な原因はハードコード化されたパスにあります。Windows 2000 や Windows NT 4.0 でも、移動は頻繁に行われます。たとえば、Windows 9x では My Documents フォルダは以下のように C ドライブまたは D ドライブのルートの下にありました。

\My Documents

Windows NT ではこのフォルダが移動され、ユーザーによっては Windows システム ディレクトリの奥深くにある場合もあるかもしれません。次の例では、"personal" と名前が変わっていますが、事実上は Windows NT 4.0 の My Documents フォルダです。

%windir%\profiles\kylemar\personal

Windows 2000 ではこのフォルダを再び移動しました。システム ディレクトリやルートのかわりに Windows 2000 はフォルダを次の場所に入れました。

\Documents and Settings\KYLEMAR\My Documents

ご承知のように、フォルダなどは移動するため、ハードコード化すると間違った場所に導かれます。事実、きちんと管理された環境下では My Documents フォルダをネットワーク ドライブに入れることも可能です。これを避けるために、先に説明した "SHGetFolderPath" を使用します。Windows 95、Windows 98 などのプラットフォームでこれを使用していることを確認してください。Windows 2000 の既定では、"SHGetFolderPath" で正しい位置を知ることができます。

長いファイル名とプリンタ名

Windows 95 のリリース以来、長いファイル名とプリンタ名について説明してきました。元来、アプリケーションでこれらをサポートするようにお願いしてきました。Windows 2000 では、アプリケーションにこれらを確実に正しくサポートするようにする必要があります。一部のアプリケーションが長いファイル名のサポートを正しく実装していないことが判明しています。これは、アプリケーションがこれらの実装を全く行わないということではなく (全く行わないものもありますが)、アプリケーションが長いファイル名のサポートを実装する方法にバグが見つかったのです。たとえば、長いファイル名のために 256 文字用のバッファを備えていると宣言しているアプリケーションがあります。しかし、ファイルを移動させて、アプリケーションにそれが探しているファイルの 55 文字程度のパスを示すと、アプリケーションが停止することがあります。アプリケーションは長いバッファを持っていると宣言していますが、実際には短いバッファを渡していることがわかりました。これはアプリケーション中の単純なバグです。ルートや Windows システム ディレクトリから Documents and Settings フォルダに移動すると、このようなバグは特に頻繁に見受けられます。パスはより長くなり、平均 30 ~ 40 文字が平均 60 ~ 70 文字になります。パスが長くなるとバグの出現頻度が高くなることがわかっています。

Documents and Settings フォルダで発見したもう 1 つの問題は、「Documents (スペース) and (スペース) Settings」 に移動したために数多くのアプリケーションが正常に動作しなくなったということです。この、アプリケーションはディレクトリ全体を解析し、「documents」 という単語を見つけると、「My (スペース) Documents」 で終わるチェーンの末尾であると見なします。「´documents´ という単語を見つけたから My Documents フォルダにいる」 と理解して、結果的にアプリケーションは正常に動作しません。

長いファイル名のサポートをよく調べて、テストしてください。「Windows 2000 アプリケーション仕様書」 を参照してください。このドキュメントで、長いファイル名を正しくサポートしていることを確認できます。

ヒープ管理

もう 1 つのアプリケーションの安定性の問題は、Windows NT プラットフォームに加えたヒープ管理の変更によるものです。これはここで説明する問題の中で最も恐ろしいものです。これは最も油断できない問題で、アプリケーション中であらゆるやっかいな問題を引き起こす可能性があります。これは、実際これまで悩まされてきた不適切なポインタまたはメモリの使用などの古い C における問題と同じです。しかし、処理が非常に困難な問題の 1 つです。

この問題は、サービス パック 4 を適用した Windows NT 4.0 から始まりました。特にマルチプロセッサ マシンでヒープ マネージャがより効率よく、より速く動作するように変更されています。Windows 2000 ではさらに変更が加えられています。従って、Windows NT 4.0 上で実行することができるアプリケーションが、サービス パック 4 をインストールした結果、動かなくなることもあります。また、ヒープ管理の動作方法をさらに改良したために、サービス パック 4 では動作して、Windows 2000 で動作しない場合もあります。確かに、システムから性能を引きだそうとしてヒープ管理をより速く実行できると、ロット全体をよりよく処理できます。実は、API の仕様自体には変更が加えられていません。要するに、ヒープの動作方法に論理的な変更は加えられていませんが、アプリケーション中のバグを公開し、ブロックを再使用する方法にわずかな変更を加えました。

これらの変更方法は以下の通りです。以前は、ブロックを解放すると、そのブロックが未使用ブロック リストの末尾に追加され、最終的にフィルタ処理されて再使用されていました。現在は、使用された最後のブロックをキャッシュします。これらは効率的にリストの最初に追加され、別のブロックが必要な場合はより素早くブロックを取得できます。ブロックを解放してから取得すると、ユーザーが同じページにとどまりシステムの動作をスピード アップできるように、今まで使用していたブロックが取得されます。

これは、C プログラミングと C++ 開発で当初から経験した問題です。これらを見つけだす理想的な方法はありません。問題点を見つけだす市販のツールはあります。それ以外の方法としては、アプリケーションを Windows 2000 上でできる限り詳細にテストする必要があります。

多くのアプリケーションが抱えているヒープ問題の中で最も一般的なものは、メモリを解放してからそのメモリにアクセスしようとするものです。この場合、アプリケーションが割り当てを行い、読み取りと書き込みを行ってからブロックを解放し、再び読み取りと書き込みを行おうとします。この作業の怖いところは、データが破損する可能性があることです。アプリケーションが停止する可能性がありますが、アプリケーションは既に所有しているブロックまたはページ上にあるため、またユーザーの書き込み用に割り当て可能な場所から読み取りと書き込みを行っているため、アクセスが妨害されることはありません。ただし、データが破損してしまう可能性があります。運がよければ、アプリケーションは停止します。しかし、実際にどうなるかは誰にも予想できません。

この問題が頻繁に発生する場所がもう 1 か所あります。ページ上の空間をより有効に使用するために、小さなブロックに再割り当てを行った場合、Windows 2000 とサービス パック 4 は割り当てを移動させることがあります。多くの開発者が次のように考えて怪しげな最適化を行ってきました。「元のブロックよりも小さなブロックを再割り当てすると、だれもそのポインタを私の上に移動させないだろう。ならば再割り当てを行って移動しないポインタに依存することができる。小さくすればだれも移動させないだろう」 残念ながら、これは完全に間違っています。

呼び出しの規則

次の問題は、呼び出しの規則に関する問題です。ドキュメントには、すべてのウィンドウ プロシージャに STDCALL を使用するように指示してあります。しかし残念ながら、多くのアプリケーションがウィンドウ プロシージャやダイアログ プロシージャに STDCALL を使用していないことがわかりました。STDCALL を使用しないと、アプリケーションが正しく動作しない可能性があります。Windows 95 と Windows 98 では、C_DECL 規則をうまく回避できていました。つまり、ウィンドウ プロシージャに C_DECL を置き忘れていても、システムは損傷を受けることはありませんでした。

Windows 2000 ではこれに対していくつかの対策を講じ、できるだけ多くの問題点を捕らえることができます。しかし、先週もバグを発見して、「ハンドラを所定の位置で取得して標準の呼び出しを自分で処理しようとしている。しかし、アプリケーションが標準の呼び出しを使用していないため、これを正しく捕らえることができない」 というようなバグです。ウィンドウ プロシージャで別の呼び出し規則の代わりに STDCALL を使用していれば、これははるかに簡単になります。

また、アプリケーションが正しい呼び出し規則を使用していても、それらを正しく実装していない場合があることを発見しました。そして、コンパイラでバグが発見された場合には、呼び出し規則が正しく使用されていないことを意味したり、アプリケーションが通常よりも速くレジスタをつぶしていることを意味します。我々は、さらに最適化を行い、レジスタの使用を厳しく制限したり、より小さく速く動作するように改良しているため、呼び出し規則に従ってないがために、互換性を失っている多くのアプリケーションがみつかっています。

バッファされていないファイルの I/O

皆さんが、システムによって提供されているバッファを使用せずにファイルの I/O を行おうとする場合は、Create File の FILE_FLAG_NO_BUFFERING フラグ (バッファを提供し、読み取り/書き込みを行う場合にシステムが割り当てたバッファは使用しない、ということを意味します) を使用します。その場合、"ReadFile" と "WriteFile" の API に渡したバッファがデバイスに対して正しく設定されていることを確認する必要があります。これは通常の場合と変わりませんが、Windows 2000 では特に Ultra 66 IDE ドライブなど新しいデバイスをサポートする場合、境界合せが少し異なっています。このため、バッファを割り当てる場合は、正しく割り当てていることを確認する必要があります。最も簡単な方法は、"VirtualAlloc" を使用する方法です。"VirtualAlloc" は常にバッファを偶数の境界に合わせます。このため、バッファはファイルの I/O を行うデバイスに必要なサイズに合わせて常に正しく設定されます。これを行う場合は、読み取り書き込みが I/O デバイスの実際のセクタ サイズの倍数になることを確認する必要があります。セクションのサイズは "GetDiskFreeSpace" から取得できます。これらのセクタの倍数だけを割り当て、読み込んでいることを確認します。

大容量ドライブ

ドライブ関連のもう 1つの問題は、大容量ドライブによる影響の問題です。確かに今日では、ドライブは 4 ギガバイト (GB) をはるかに上回る大きさで、4 GB を超える空きスペースがあります。ここで、"GetDiskFreeSpace" を使用して多くの 32 ビット値が返されても、これらを上書きし、あらゆる場所にこれらの値をラップするため、問題は解決しません。その結果、負の値のディスク容量を返してしまうアプリケーションでは、あらゆる問題点が発生します。

使用する必要があるのは "GetDiskFreeSpaceEx" です。これは INT の代わりに ULARGE_INTEGER_ を使用します。これを使うと空きスペースの容量を正確に把握できます。

その他の HKEY_CURRENT_USER を開く

アプリケーション、特にサーバー アプリケーションによっては、開始時に使用したもの、または現在のものとは別の HKEY_CURRENT_USER から情報を得なければならない場合があります。問題は、HKEY_CURRENT_USER が実際各プロセス ベースでキャッシュされる点です。この場合、アプリケーションは HKEY_CURRENT_USER を閉じようとし、別のユーザーに偽装して HKEY_CURRENT_USER を開き、正しいものを取得しようとします。問題は、このために複数のスレッドを使用すると、1つのスレッドが処理を終了しても、そのほかのスレッドでは処理が途中になっているという事が考えられることです。そして、2度目に開くときに「HKEY_CURRENT_USER を開いています。そして私はキャッシュされていたほうを使用しています。」と言われてしまうのです。これでは、結局どちらのHKEY_CURRENT_USERを開いたのか判断できません。従って、これは非常に危険な方法です。これを改善するために、新しい API、RegOpenCurrentUser を追加しました。これを使用すると正しく偽装できるため、間違ったものでなく、きちんと目的の HKEY_CURRENT_USER を取得できます。

ビット フラグの確認

低レベルの C に関連したもう 1つの問題です。我々は、ビット フラグを確認し、実際に特定のビットの有無をきちんと確認する代わりに、一種の等式オペレータを使用しているアプリケーションを見つけました。今後も、Windows 2000 や Windows 2000 以降のあらゆるバージョン毎にフラグが追加される予定です。このため、等式を利用した単純比較ではなく個々のビットをきちんと確認する必要があります。例えば、我々は Windows 2000において、 "owner draw no focus" 値と "no accelerator" 値を追加したため、異なった種類の描画パラメータが使用できます。これは、ビット フラグを確認する方法です。

if (fItemState & ODS_FOCUS)

メッセージの順序

私が覚えている限り、メッセージの順序を使用したり、アプリケーションが受け取ったメッセージの順序が特別なことを意味するという考えを前提とした実装をしないように常に開発者に警告してきました。Windows 2000では、このような実装に頼ることはできません。我々は、一部の既存アプリケーション、特にいくつかのマルチスレッド アプリケーションがメッセージの順序に依存していることを認識しています。たとえば、あるスレッドがシャット ダウンしてメイン メッセージ ループにメッセージを渡して、そして別のスレッドがシャット ダウンして同じようにメッセージを渡すという場合があります。前述した一部のアプリケーションでは、渡されたメッセージの順序を、スレッドがシャット ダウンした順序だという前提で実装されている場合があります。我々はスレッド スケジューラの動作に変更を加えています。ですから、残念ながらメッセージの順序がスレッドのシャットダウンの順序に整列されてくることは期待できません。もし、皆さんがこの例と同じような実装を行うと、スレッドがシャット ダウンした順序とは異なった順序でこれらのメッセージが取得されるなど、さまざまな障害が発生します。また、メッセージの順序に依存する必要あり、特に複数のスレッドにまたがっている場合は、その同期について自分自身で考慮する必要があります。メッセージが同期的に渡されてくるとは考えないようにしてください。

複数のモニタ

Windows 98 から複数のモニタの使用が可能になりました。そして、Windows 2000 では初めてこの機能が Windows NT ベースのプラットフォームで使用できるようになりました。ここで大切なのは、アプリケーションが負座標と巨大座標の両方を正しく操作し、また巨大座標の場合に実際、何が表示されるかを確認する必要があることです。複数のモニタを設定しており、主モニタが副モニタの右側にある場合、副モニタは完全に負座標空間にあります。アプリケーションが副モニタに表示されるウィンドウを開き、完全表示しようとした場合、複数モニタに対応していないアプリケーションはウィンドウを完全に主モニタに移動させます。これは現在の座標がすべて負であるためです。これは望ましくありません。ウィンドウを配置した場合は複数のモニタでアプリケーションをテストし、これらの負の座標をきちんと処理していることを確認してください。高い位置の正座標でも同様です。下記の図で確認できる新しいシステム測定基準を使用して、あるべき場所にウィンドウを開いていることを確認する必要があります。

Windows プラットフォーム間の違い

最後に説明する問題点のカテゴリは、ほかのカテゴリより手短ですが、Windows プラットフォームの基本的な違いについてです。Windows 2000 は Windows NT を基準にしています。しかし、我々は多くのユーザーが Windows 9x を Windows 2000 にアップグレードするのではないかと考えています。しかし、我々は、アプリケーションをWindows 9x から Windows 2000 プラットフォームに移行するテストを行いました。これについては雑誌の記事や SDK などで多くの情報を得ることができます。まず、あなたのアプリケーションが Windows 9x プラットフォームだけでなく Windows NT プラットフォームに対応していることを確認する必要があります。

アプリケーションの対象

アプリケーションの動作OSに対象がある原因は、Windows NT ではなく Windows 9x でしか使用できない API を使用するためです。私がいつも使っていた API は Tool Help API でした。Windows 2000 でも Tool Help API をサポートしていますが、Windows 9x では使用できて Windows NT では使用できない API が数多くあります。これらを見つけだすための適切な方法はありませんが、SDK に含まれている .csv ファイルを使用することができます。これは基本的にスプレッドシート形式で、これにより、すべての API がどの OS で実装されていて、逆にどれで使えないか、どのように動作するかなどがわかります。もう 1 つの方法はアプリケーションを徹底的にテストすることです。アプリケーションを Windows 98 から Windows NT プラットフォームに移行できるか、実行できるかを確認してください。こちらのほうがより簡単で迅速な方法でしょう。

また、Windows NT プラットフォームは GDI 呼び出しに対してフル 32 ビット座標系を使用していることに注目してください。これに対し、Windows 9x は 16 ビットを使用しています。この違いに注意してください。実際、Windows NT 上のハンドルはすべてフル 32 ビットを使用しています。Windows 9x のハンドルが 32 ビットであるという点を利用しようとした開発者もいますが、16 ビットのみが使用されています。これを Windows NT で行うのは、非常に好ましくありません。

汎用的なサンク

多くのアプリケーションが、サンクの使用に関する機能の問題に陥ります。Windows 9x ではフラット サンクと呼ばれるものを実装していました。これは、16 ビット アプリケーションが 32 ビットを呼び出し、32 ビット アプリケーションが 16 ビット コンポーネントまたは 16 ビット アプリケーションを直接呼び出すことを可能にしました。この機能は Windows 2000 ではサポートされていません。特に 32 ビット アプリケーションが 16 ビット アプリケーションを直接呼び出すことはできません。Windows 2000 または Windows NT が実装しているのは、"汎用的なサンク" と呼ばれるものです。汎用的なサンクは、16 ビット アプリケーションが 32 ビット コンポーネントを呼び出すことを可能にします。また、16 ビット アプリケーションが 32 ビット コンポーネントの呼び出しを開始し、その後 32 ビット コンポーネントから逆に呼び出されることも可能にしました。16 ビット コンポーネントを単に呼び出す 32 ビット コードを作成することはできず、サポートもされていません。実際、 16 ビットから 32 ビットの呼び出しを開始するだけで、そのほかはできません。サンクについてもう 1 つ注意しなけばならないのは、Windows 9x と Windows NT では基礎となっているプロセス モデルも異なるということです。汎用的なサンクでその違いがわかります。最も簡単な解決方法は、16 ビット コンポーネントを 32 ビット コンポーネントに移行することです。2 つのプラットフォームでのサンクの問題については、特に注意する必要があります。