次の方法で共有


Ask Dr. GUI #49

1999 年 11 月 /12 月

Dr. GUI が帰ってきました ...

Dr. GUI はこの秋、とても素敵な休暇を過ごしました。ミシガンの紅葉のなんと素晴らしかったことか!(生粋のミシガン子の Dr. GUI が故郷の秋の景色を目にしたのは、少なくとも 11 年ぶりのことです)。

Windows 2000 がより身近に

運がよければ、みなさんがこの記事を読むころには Microsoft(r) Windows(r) 2000 がもう手元にあるか、近々手に入るところかもしれませんね。みなさんもすでに噂は聞いていて、名医も確信していることですが、「十分速いプロセッサと十分な大きさの RAM があれば、Windows 2000 は素晴らしいアップグレードです」。みなさんも Windows 2000 を気に入ることは間違いありません(ただし、必要なデバイスがサポートされていることを必ず確認してください)。

また、Windows 2000 でアプリケーションをインストールして実行できるか確認するためのテストは欠かせません。Windows 2000 では、システムをより堅牢にするためにたくさんの変更が加えられており、その一部がみなさんに「かみつく」ことも考えられます。それを見つけ出す方法はテストしかありません。

Microsoft Visual Studio 6.0 か、その Professional ツールまたは Enterprise ツールのいずれかをお持ちなら、ぜひ利用してほしい無料サービスが 2 つあります。

まず、Windows 2000 Developer's Readiness Kit が無料で入手できます。これには、Windows 2000 に関係する Microsoft Visual Basic と Microsoft Visual C++ の問題点に関するトレーニング、COM+ に関するトレーニング、Windows 2000 対応の新しい Visual Studio Installer、そして非常に重要な『Windows 2000 Applications Specifications and Compatibility Guide』が含まれています。詳細については、https://msdn.microsoft.com/vstudio/prodinfo/datasheet/winkit.asp をご覧ください。Visual Studio を最近購入された方は、すでにこのキットを持っているはずです。

それから、MSDN ライブラリの CD か DVD を 1 年間無料で入手できます。これについては、https://msdn.microsoft.com/subscriptions/offer/ でご確認ください。これで節約できたお金で DVD ドライブを購入しましょう。

Windows 2000 Developer Center の Windows 2000 の Web ページ(https://msdn.microsoft.com/windows2000/)には、最新情報が最もよくまとめられています。

Windows 2000 Developer Center では、さらに堅牢でスケーラブルなプログラムをより簡単に作成できる、Windows 2000 の新しい API に関する最新情報もすべて入手できます。

新しい Web の作成

Web のプログラミングをしている方(最近は、していない人を見つけるほうがむずかしいように思えます)、Microsoft が Web のプログラミングをさらに簡単かつ強力にするために何をしているのか知ることをお勧めします。この情報については、MSDN を含め、あらゆる場所で目にすることになるでしょう。

では、みなさんからの質問です

スタックつぶし

親愛なる Dr. GUI へ

私たちは C 言語で ISAPI モジュールを書いています。現在、このモジュールのアクセス違反やスタックのオーバーフローを防ぐことに取り組んでおり、__try/__except フレームワークを使ってみました。いくつかのテストを行ったのですが、アクセス違反からは簡単に回復できるものの、スタックのオーバーフローからはうまく回復できません。

この問題を説明するために、ちょっとした例を次に示します(テストは Visual C++ 6.0 で行いました)。

  void stack_overflow()
{
   char speed_up_the_overflow[1000];
   stack_overflow();
}

int main()
{
   for(;;) {
      __try {
      stack_overflow();
      } __except(1) { /* EXCEPTION_EXECUTE_HANDLER */
         printf ("Executing exception handler\n");
      }
   }
   return 0;
}

ループの最初の繰り返しでは、例外ハンドラ コードが正しく実行され、「Executing exception handler」というメッセージが表示されます。しかし、2 回目の繰り返しではプログラムが死んでしまい、何のメッセージも表示されません。デバッガを使ってプログラムを実行すると、2 回目の繰り返しでプログラムが停止したのは、スタックのオーバーフローではなくアクセス違反が起きたためであることがわかります。

stack_overflow() を変更して単純なアクセス違反が起きるようにすると(memcpy(NULL, "foo", 3); など)、予想どおり無限ループとなり、「Executing exception handler」というメッセージが繰り返し表示されます。

ほかに longjmp() と goto も試してみましたが無駄でした。

私たちは何を間違えているのでしょうか。スタックのオーバーフローを整然と回復する方法はありますか。

どうぞよろしく。

アンディ グットマンズ、ジーブ シュラスキ

Dr. GUI の回答:

無限再帰!まさにプロセスが自分のスタックをつぶす原因の 1 つですね。逆に言えば、問題を確実に再現できる方法の 1 つでもあります。

このような場合に何が起きるのか、救急対応チームと協力して解明したいと思います。

スレッドで EXCEPTION_ACCESS_VIOLATION または EXCEPTION_INT_DIVIDE_BY_ZERO が生じる場合、おそらくコードにバグがあります。しかし、通常は実行中の関数から抜け出るか、失敗を示すコードを返すだけでよく、無視することができます。しかし、スレッドで EXCEPTION_STACK_OVERFLOW が生じると、スタックは壊れた状態のままになってしまいます。

なぜか説明しましょう。スレッドのスタックは、スレッドの要求を満たすために拡大します。これは、PAGE_GUARD 保護属性が設定されている現在のスタックの最後(一番下)に、ページを配置することにより行われます。コードによってスタック ポインタがこのページのアドレスを指すと、例外が発生し、システムは次の 3 つのことを行います。

  1. スレッドがメモリのデータの読み書きできるように、「保護ページ」の PAGE_GUARD 保護属性をはずす。

  2. 最後のページの下に新しい保護ページを割り当てる。

  3. 例外を起こした命令を再実行する。

システムはこうしてスレッドの要求に応じてスタックを拡大できますが、スレッドは何も気付きません。

ただし、プロセスの各スレッドに割り当てられるスタック サイズの上限は決まっています。これは、/STACK:reserve[,commit] というリンカ スイッチか、プロジェクトの .def ファイルの STACKSIZE ステートメントによってコンパイル時に設定されます。しかし、質問のケースではコードを ISAPI DLL で開発しているので、IIS によって設定されるスタック サイズの上限は変えられません(IIS は、実行するすべてのスレッドのスタック サイズの上限を設定します)。では、スタックが上限サイズに達すると何が起きるでしょうか。システムは通常どおり例外を受け取りますが、その処理方法は次のとおりです。

  1. 通常どおり、保護ページの PAGE_GUARD 保護属性をはずす。

  2. 最後のページの下に新しい保護ページを割り当てようとするが失敗する!

  3. Microsoft Win32®の例外を生成し、スレッドが例外ブロックでそれを処理する。

ここで重要なのは、この時点でスタックには保護ページがないということです。つまり、壊れているのです!このため、アプリケーションが次にスタックを最後(保護ページがあるべき場所)まで拡大すると、スタックの、末尾を超えて書き込むので、サンプル アプリケーションで確認したアクセス違反が生じるわけです。

Windows の例外処理は非常に優れており「魔法」のように思えることを実行して、コードの堅牢性を保ちます。しかし、スタックを自動的に修復はしてくれません。いくらか泥臭いプログラミング(ちょっとしたインライン アセンブリも含め)を厭わなければ、自分で直すこともできます。ただし、インライン アセンブリを使うので、これは x86 専用の解決方法であることに注意してください。

必要な作業は、スタックに対して保護ページを再指定することです。ただし、秩序正しさを保ちたいなら、放棄されたスタックのページも解放するとよいでしょう。これを行うように質問のサンプルを書き換えてみました(また、ループ カウンタを追加して、stack_overflow() 関数を通過するたびに前回と同じ深さまで入ったことを確認できるようにしています)。下記のコードをご覧ください。

これで Windows の壊れたスタックを修復する方法がわかりましたね。しかし、名医なら誰でもするように、このデジタル バンドエイドの代わりに使える予防薬を処方するのが私の義務です。

  #include <windows.h>
#include <stdio.h>

void stack_overflow(DWORD dwCount )
{
    char speed_up_the_overflow[10240];
    printf("Iteration count: %d\n", dwCount) ;
    stack_overflow(dwCount+1 );
}

int main()
{
    for(;;) {
        __try {
            stack_overflow(0);
        } __except(1) { /* EXCEPTION_EXECUTE_HANDLER */
            LPBYTE lpPage;
            static SYSTEM_INFO si;
            static MEMORY_BASIC_INFORMATION mi;
            static DWORD dwOldProtect;

            // We do this to get the page size of the system
            GetSystemInfo(&si);
            // We want the address that the stack pointer is // pointing to _asm mov lpPage, esp;
            // We wanna learn the allocation base of the stack
            VirtualQuery(lpPage, &mi, sizeof(mi));
            // go to the page below the current page.
            lpPage = (LPBYTE) (mi.BaseAddress) - si.dwPageSize;
            // Free the portion of the stack that you just abandoned
            // If it looks like you are freeing the portion
            //of the stack that you still need,
            // Remember that stacks grow down.
            if (!VirtualFree(mi.AllocationBase,
               (LPBYTE)lpPage - (LPBYTE) mi.AllocationBase,
               MEM_DECOMMIT))
            {
                // if we get here, exit
            }
            // Reintroduce the guard page
            if (!VirtualProtect(lpPage, si.dwPageSize,
               PAGE_GUARD | PAGE_READWRITE, &dwOldProtect))
            {
                // if we get here, exit
            }
            printf ("Executing exception handler\n");
            Sleep(2000) ;
        }
    }
    return 0;
}

詳細については、ジェフリー リヒターによる『Advanced Windows, 3rd Edition 』(Microsoft Press 刊。http://mspress.microsoft.com/books/1292.htm)の第 7 章と第 16 章を参照してください。

共依存の問題

親愛なる Dr. GUI へ

こんにちは。

Visual Basic プロジェクトを配備しようとしているのですが、重大な問題が発生しています。データベースには DB ライブラリを使って接続しており、DB ライブラリの管理用関数をいくつか使用しています。すべてがうまく機能しているのにもかかわらず、SQL Server Client Utility または SQL Server がすでにインストールされているコンピュータでしかこのプロジェクトを実行できません。Visual Basic のディストリビューション ウィザードを使ってセットアップを作成しているときに依存関係情報は見つからず、SQL Server がないコンピュータでこのアプリケーションを実行すると、エラー番号 429 の「Active X object can't be created.」が表示されます。

なるべく早く回答をいただければと思います。

アームガン ラファト(MCSD)

Dr. GUI の回答:

Dr. GUI は精神科医だと主張したことはありませんが、彼が依存性と相互依存ファイルについて知っていることは信用してください。状況は次のとおりです。

SQL Server とやり取りする Microsoft ActiveX®コントロール vbsql.ocx は、Microsoft Visual Basic(r) のディストリビューション ウィザードがコントロールに必要なファイルを決定するときに使う依存ファイル(dep)と共に提供されません。しかし幸いなことに、依存情報を追加してこの不備を補うことができます。vbsql.ocx は DB ライブラリの機能をコントロールにカプセル化するので、必要な作業は、ディストリビューション ウィザードで DB ライブラリ ファイルを依存ファイルとして追加することです。

ディストリビューション ウィザードの「含まれるファイル」の手順で、次のファイルをパッケージに追加する必要があります。これらのファイルは SQL Server Programmers Toolkit(PTK)と共にあるか、\mssql\binn ディレクトリにあります。

NTWDBLIB.DLL'DB ライブラリ コンポーネント。

                              標準で VBSQL.OCX と共に含める。

DBMSSOCN.DLL' TCP/IP ソケット接続用。

DBNMPNTW.DLL' 名前付きパイプ接続用。

DBMSSPXN.DLL' IPX/SPX 接続用。

DBMSADSN.DLL' Apple Talk 接続用。

DBMSDECN.DLL' Dec-Net 接続用。

DBMSRPCN.DLL' マルチプロトコル接続用。

DBMSVINN.DLL' Banyan Vines 接続用

あなたが MAPI なら、私も MAPI

親愛なる Dr. GUI へ

私は MAPI を使って、少なくとも 3 種類のメール サーバーから電子メールを取り込む電子メール プログラムを書こうとしています。Visual Basic では、MAPI の SMTP と POP の設定を変えるにはどうすればよいのでしょうか。これはとても重要なので、できるだけ早く回答していただくようお願いいたします。

アドバイスに感謝します!

クリフォード オラベク

Dr. GUI の回答:

親愛なるクリフォードへ

相互依存の問題が解決したら、今度は MAPI の問題ですね。

あなたは Visual Basic を使っているようなので、Collaboration Data Objects(CDO)ライブラリを使っているものと仮定します。この問題を解決するには、メールボックス自体の SMTP と POP の設定を変更するのではなく、単に別々のサーバーおよびメールボックスにログオンするだけでいいのです。といっても、これにはかなりの手間がかかりそうです。もちろん「シンプソンズ」の再放送も見なければならないでしょうが、ここは少し我慢をして、以下をご覧ください。

コードの中で、サーバーとメールボックスの接続ごとに電子メール プロファイルを作成し、合わせて 3 つ用意します。次のように Session オブジェクトの Logon メソッドの ProfileInfo パラメータに特定のプロファイル情報を指定することによって、同じことを実現できます。

  objSession.Logon ProfileInfo:="<server A>" & vbLf & "<mailbox A>"

アプリケーションが 3 つの異なる操作を順番に実行する場合には、単独の MAPI Session オブジェクトに繰り返しログオンして処理を実行できます。もちろん、1 度ログオフしてから次のログオンを実行しなければなりません。また、ログオンのたびに違う ProfileInfo データを使わなければいけません。

次のコードを試してみてはどうでしょうか。

  Dim objSession as Mapi.Session
Set objSession = CreateObject("MAPI.Session")
'Logon to first server/mailbox
   objSession.Logon ProfileInfo:="<server A>" & vbLf & "<mailbox A>"
   '... do first task
   objSession.Logoff

'Logon to other server/mailbox
   objSession.Logon ProfileInfo:="<server B>" & vbLf & "<mailbox B>"
   '... do next task
   objSession.Logoff

'repeat as necessary
'release objects
   Set objSession = Nothing

アプリケーション全体で 1 つの Session オブジェクトをグローバルに使うことをお勧めします。ただし、どうしても同時に 3 つの異なる Session オブジェクトをアクティブにする必要がある場合は、個別の ProfileInfo 設定を使ってそれぞれにログオンします。

この手法が正しく動作するには、コードを実行するユーザーがメールボックスにログオンする権限を持っていなければなりません。詳細については、電子メール管理者に聞いてください。

次の Microsoft Knowledge Base の文書に、CDO に関する多くの有用な情報があるので参照してください。

  • Q171440: INFO: Where to Acquire the Collaboration Data Objects Libraries

  • Q172741: HOWTO: Write a VB Active Messaging Inbox Agent

MAPI 開発がうまくいきますように!

mutex を操れない

親愛なる Dr. GUI へ

私はフランス出身のエンジニアなので、英語に自信がありません。質問内容を理解してもらえるとよいのですが。

私は mutex を作成する DLL を持っています。問題は、私の DLL を使うすべてのプロセスの同期をとることができないことです。mutex は、次のようなコードを使って作成しています。

  ghMutexExe = CreateMutex(NULL, FALSE , "APPLICOM_IO_MUTEX");

リアルタイム優先順位で実行しているアプリケーションで私の DLL を使うと、同じ DLL を使う別のアプリケーションを実行できません。次の関数を使ったとします。

  ghMutexExe = CreateMutex(NULL, FALSE , "APPLICOM_IO_MUTEX");

すると、この関数は NULL(ghMutexExe = NULL)を返し、GetLastError は 5 を返します(アクセス拒否、ERROR_ACCESS_DENIED)。

助けてもらえますか。

ベルトラン ロレー

Dr. GUI の回答:

あなたの英語は申し分ありません(Dr, GUI はフランス語で回答を頼まれなくて本当によかったと思っています)。ほとんど知られていないのですが、名医はバイリンガルです。彼は英語と C++ を話します。

スレッドの優先順位が異なるために、スレッドを同期できないことは確かにありえます。しかし、mutex を作成するプロセスの優先順位クラスが原因で、既存の mutex に対するハンドルを取得しようとしたときに ERROR_ACCESS_DENIED を受け取ることはほとんどありません。

一般に、ERROR_ACCESS_DENIED は、呼び出した関数のセキュリティ上の問題によって実行が失敗したときに返されます。Microsoft Windows NT(r) または Windows 2000 でこれが起きる場合を説明します。

プロセス A がサービスとして実行されていたとします(リアルタイムで実行されているかは問いません)。このプロセスが、最初のパラメータとして NULL を渡して名前付き mutex を作成します。この NULL パラメータは、オブジェクトを標準のセキュリティで作成することを示します。

プロセス B がユーザーの対話により起動され、プロセス A の場合と似たような形式の CreateMutex への呼び出しによって、名前付き mutex に対するハンドルを取得しようとします。しかし、呼び出しは失敗し、ERROR_ACCESS_DENIED が返されます。この原因は、サービスの標準のセキュリティでは、オブジェクトの ALL_ACCESS セキュリティについて、ローカル システム以外のすべてが除外されるためです。このプロセスは、システム管理者として実行している場合でもアクセス権がありません。

この種の失敗は、次のいくつかの点が組み合わさって生じます。

ほとんどのサービスはローカル システム アカウントにインストールされ、結果として特別なセキュリティ権限のもとで実行されます。

ローカル システム アカウントで実行しているプロセスは、ローカル システムで実行しているほかのプロセスへの GENERIC_ALL アクセス権と、Administrator グループのメンバーへの READ_CONTROL、GENERIC_EXECUTE、GENERIC_READ の各アクセス権を許可します。ほかのユーザーまたはグループによるオブジェクトへのその他のアクセスは、すべて拒否されます。

CreateMutex に対する呼び出しはすべて対象オブジェクトに対する MUTEX_ALL_ACCESS アクセス権を暗黙に要求します。対話式に操作するユーザーは、結果としてローカル システムのセキュリティ コンテキストの中で作成されたオブジェクトへのハンドルを取得するのに必要な権限を持っていません。

この問題の解決方法はいくつかあります。

CreateMutex の呼び出しに指定するセキュリティ記述子に、「NULL の DACL」を含めることができます。NULL の DACL セキュリティが設定されたオブジェクトは、セキュリティ コンテキストにかかわらず、すべてのユーザーにすべてのアクセス権を許可します。この方法のマイナス面は、オブジェクトのセキュリティがまったく保証されなくなることです。実際、悪質なアプリケーションが、NULL の DACL を使って作成された名前付きオブジェクトに対するハンドルを取得し、ほかのプロセスがそのオブジェクトを使用できないようにセキュリティ アクセス権を変更し、事実上そのオブジェクトを壊すということができます。注意:Dr. GUI はこの「解決方法」をお勧めしません。

2 つ目の選択肢としては、組み込みのグループ、Everyone に対して必要な権限を明示的に与えるセキュリティ記述子を作成することです。mutex の場合、必要な権限は MUTEX_ALL_ACCESS です。悪質な(またはバグのある)ソフトウェアが、オブジェクトに対するほかのソフトウェアのアクセスに影響を与えることがないので、この方法をお勧めします。

また、そのオブジェクトを使う必要のあるユーザー アカウントへのアクセスを、明示的に許可することもできます。こうした明示的なセキュリティ方針はよいかもしれませんが、誰がアクセスする必要があるのかをオブジェクトの作成時に知っている必要があるという不都合な面もあります。

システムのユーザーが共同で利用するオブジェクトを作成する場合は、2 つ目の方法をお勧めします。どちらの方法を選んでも、作成するオブジェクトごとにセキュリティ記述子を適用するか、トークンの標準 DACL を変更してプロセスの標準セキュリティを変えるかを選択できます。標準セキュリティを変える場合は、オブジェクトの作成時にセキュリティ パラメータとして NULL を渡します。

Dr. GUI が思うに、質問のケースでは DLL を開発しており、プロセスのすべてのオブジェクトのセキュリティを変更したくないと考えられるので、特定のオブジェクトのセキュリティだけを設定します。

次の関数は、サービスの標準よりも緩いセキュリティを作成するその他の機能を使って、CreateMutex をくるみます。

  HANDLE ObtainAccessableMutex(BOOL bInitialOwner, LPTSTR szName )
{
    SID_IDENTIFIER_AUTHORITY siaWorld = SECURITY_WORLD_SID_AUTHORITY;
    PSID psidEveryone = NULL;
    HANDLE hMutex = NULL  ;
    int nSidSize ;
    int nAclSize ;
    PACL paclNewDacl = NULL;
    SECURITY_DESCRIPTOR sd ;
    SECURITY_ATTRIBUTES sa ;
   
    __try{
        // Create the everyone sid
        if (!AllocateAndInitializeSid(&siaWorld, 1, SECURITY_WORLD_RID, 0,
                                           0, 0, 0, 0, 0, 0, &psidEveryone))
        {           
            psidEveryone = NULL ;
            __leave;
        }

        nSidSize = GetLengthSid(psidEveryone) ;
        nAclSize = nSidSize * 2 + sizeof(ACCESS_ALLOWED_ACE) + sizeof(ACCESS_DENIED_ACE) + sizeof(ACL) ;
        paclNewDacl = (PACL) LocalAlloc(LPTR, nAclSize ) ;
        if(!paclNewDacl )
            __leave ;
        if(!InitializeAcl(paclNewDacl, nAclSize, ACL_REVISION ))
            __leave ;
        if(!AddAccessDeniedAce(paclNewDacl, ACL_REVISION, WRITE_DAC | WRITE_OWNER, psidEveryone ))
            __leave ;
        // I am using GENERIC_ALL here so that this very code can be applied to
        // other objects.  Specific access should be applied when possible.
        if(!AddAccessAllowedAce(paclNewDacl, ACL_REVISION, GENERIC_ALL, psidEveryone ))
            __leave ;
        if(!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION ))
            __leave ;
        if(!SetSecurityDescriptorDacl(&sd, TRUE, paclNewDacl, FALSE ))
            __leave ;
        sa.nLength = sizeof(sa ) ;
        sa.bInheritHandle = FALSE ;
        sa.lpSecurityDescriptor = &sd ;

        hMutex = CreateMutex(&sa, bInitialOwner, szName ) ;
        if(!hMutex )
            hMutex = OpenMutex(SYNCHRONIZE, FALSE, szName ) ;
    }__finally{
        if(!paclNewDacl )
            LocalFree(paclNewDacl ) ;
        if(!psidEveryone )
            FreeSid(psidEveryone ) ;

    }

    return hMutex ;
}

次の Microsoft Knowledge Base の文書に、これらのトピックの詳細な説明があるので参照してください。

  • Q193073: HOWTO: Modify Default DACL for Sharing Objects

  • Q106387: HOWTO: Share Objects with a Service

バニラを 2 さじと RAID をひとつまみ加えて ...

親愛なる Dr. GUI へ

私たちのアプリケーションは Internet Explorer の WebBrowser コントロールと MFC の CHtmlView を利用して、カスタム ステータス情報とユーザー定義のビューを追加しています。アプリケーションは、私たちがレシピと呼んでいるスクリプトを実行するプロセス監視プログラムです。これらのレシピの 1 つが連続的に別のレシピを呼び出すようになっていると、リソースの何らかの問題によってシステムが不安定になり、数時間以上実行を継続できません。

参考までに、CHtmlView を使う小さな MFC AppWizard アプリケーション(添付します)を書いてみました。これはしばらく実行すると現在アクティブな文書を閉じ、新しい文書を作成します。このアプリケーションを夜通し実行すると、システムはかなり不安定な状態になり、このアプリケーションもほかのアプリケーションも変なことをし始めます!

このアプリケーションを実行し、Windows NT のパフォーマンス モニタを使って何がリークを起こしているのか観察したところ、Pool Paged Bytes が相当な速さで消費されていることが主な問題のようです。私は何か間違ったことをしているのでしょうか。それともこれは Internet Explorer のバグなのでしょうか。バグなら、どのようにこれを報告して修正してもらえばよいですか。

私のシステムでは NT4 SP5 と IE5 を使用しており、RAM は 128MB あります。Windows 98 でも同じ問題を確認しましたが、Windows 95 ではテストしていません。

回答をお願いします。

マット ステファンズ

Dr. GUI の回答:

ありがとう。でも、今回はケーキを遠慮しておきます。ケーキを焼くのに相当時間がかかったので、食べられるとは思えないからです。でもすぐに、あなたのレシピを再度実行できるようにしてあげられるでしょう(しかし、これは名医が好む洗練された解決方法ではありません)。

事実、ここ MacArthur Park にいる博識なサポート エンジニアによると、あなたが説明した問題は、まさに Microsoft Internet Explorer の既知のバグだそうです。私たちは Internet Explorer の将来のバージョンかサービス パックでそのバグを修正しようと目下作業中です。今のところ、対処方法といえるほどのものはありません。最善の策は、定期的にアプリケーションを停止してリソースを解放することです。Dr. GUI としては、よい知らせをお伝えできなくて残念です。

次の Microsoft Knowledge Base の文書に、詳細な説明があるので参照してください。

  • Q241750: BUG: CHtmlView Leaks Memory by Not Releasing BSTRs in Several Methods

画面を開いて、バグを取り去ろう

親愛なる Dr. GUI へ

私は Internet Explorer 5.0 を使って表示する Web ページをデザインしています。このページの条件は、次のとおりです。

  • 別の Web ページから起動する(window.open を使う)。

  • 全画面表示を行う。

  • 垂直スクロールバーを表示しない。

けれども、ウィンドウを全画面で起動したときに、IE の垂直スクロールバーをなくすことができません。どんなアドバイスでもありがたいです。よろしくお願いします!!

ダン シャオ

Dr. GUI の回答:

私たちが話している間にも、Dr. GUI の直筆サイン入りハエたたきがあなたに向かって飛んでいっています(冗談ですよ!)。実際にはほかのみなさん同様、T シャツが送られるはずです。でも、何が言いたかったわかってもらえたと思います。

あなたは Internet Explorer 4.x と Internet Explorer 5 のバグを見つけたということです。全画面表示モードでウィンドウを開くと、スクロールバーの属性が逆の効果を持ってしまうのです(つまり、scrollbars=yes ではスクロールバーが表示されず、scrollbars=no ではスクロールバーが表示されます)。このバグのもう 1 つの特徴として、ページの内容が使用可能な画面領域に入りきらない場合、スクロールバーのプロパティに対する指定に関係なくスクロールバーが表示されます。

対処方法としては、子ページの BODY タグの scroll 属性を指定します。この指定は、window.open のスクロールバーの属性よりも優先されます。この属性は、子ウィンドウを通常モードで開けば正しく機能します。ページの内容が使用可能な画面領域に入りきらない場合も、スクロールバーは表示されません。

次のスクリプトのサンプルは対処方法を示します。

  main.html
<HTML>
<BODY><input type=button value="Open full screen without scrollbars" onclick="window.open('child.html', ", 'fullscreen=yes')"></BODY>
</HTML>
child.html
<HTML>
<BODY scroll=no>This is the child page</BODY>
</HTML>

正しい型を使う

親愛なる Dr. GUI へ

実行時に MAPI リソースを作成する必要があるのですが、正しい型の変数を使うほうがよいとマニュアルに書いてあります。

このコードはどうして動かないのでしょうか。

     Dim tempo(32)
   Dim map(32) as object
   Dim maptyped(32) as msmapi.mapisession

   Set tempo(1) = CreateObject("MSMAPI.MAPISession") ' ok
   Set map(1) = CreateObject("MSMAPI.MAPISession") ' ok
   Set maptyped(1) = CreateObject("MSMAPI.MAPISession") ' failed

よろしくお願いします。

ライオネル ピション

Dr. GUI の回答:

輸血をするときは血液型が正しく一致していないと、患者の容体が悪化してしまいます(死んでしまうことさえあります)。パラメータの型が一致しなくても人は死んだりしませんが、プロセスはしばしば一般保護違反(GPF)を起こして停止してしまいます。このため、パラメータの型を一致させることも重要です。

上記の CreateObject() を含む最初の 2 行は実行されますが、あなたが必要とするオブジェクトは作成されません。したがって、型を正確に指定する必要があります。上では、正確に指定したところでエラーが発覚しているわけです。

これは、Version など、Session オブジェクトのプロパティを取得することで検証できます。つまり、この処理は失敗します。正しいオブジェクト クラスは次に示すように MAPI.Session です。

     Dim map(32) as object         'example of late binding
   Dim maps(32) as MAPI.Session  'example of early binding
   Dim tempo(32)                 'late binding

   Set tempo(1) = CreateObject("MAPI.Session")   ' ok
   Set map(1) = CreateObject("MAPI.Session")   ' ok
   Set maps(1) = CreateObject("MAPI.Session")   'works too

これらのメソッドはすべて機能します。オブジェクトを宣言するときには、事前バインディングをすることにより、少なくともプログラムにとって致命的な型の不一致を防ぐことができます。

質問には、なぜ多数の Session オブジェクトを作成しているのか理由が書かれていませんでしたが、通常は 1 つの Session オブジェクトをアプリケーション全体でグローバルに使うことをお勧めします。

名医はこのアドバイスがあなたの MAPI 開発のお役に立つことをお祈りしています!

文字列の順序について

親愛なる Dr. GUI へ

Visual C++ 6.0 の CListBox の並べ替えがうまくいきません。AddString メンバ関数を使ってリスト ボックスに入れた文字列は、次のとおりです。

"a",  "b", "-a", "-b"

リスト ボックスには、これらの文字列が次の順序で表示されます。

"a", "-a", "b", "-b"

リスト ボックスの「ソート」プロパティはチェックされています。今度は、Visual C++ 1.52c から CListBox を使うと、同じ文字列が次の順序で表示されます。

"-a", "-b", "a", "b"

CListBox では上記の文字列はどのような順序で表示されるべきなのでしょう。

どうぞよろしく。

シャオピング スン

Dr. GUI の回答:

実のところ、Win16 と Win32 のソートのアルゴリズムはほとんど同じです。大まかに言うと、次のような順序になります。

  1. 英数字以外(句読点)の文字は ASCII または ANSI の順序

  2. 数字は数字順

  3. 英字はアルファベット順で大文字と小文字を区別しない

Win32 の場合も同じですが、ハイフンまたはマイナス(-)記号と、引用符またはアポストロフィ(' )の 2 つは例外です。これらの 2 つの文字は英単語に組み込まれるため、文字列のソートでは無視されます。たとえば、「its」と「it's」、「co-op」と「coop」などは区別されません。単語にこれらの文字が組み込まれていると、検索の仕組みよっては正しく検索できないことがあったのです。このため、これらの文字はテキスト文字列に組み込まれているほかの発音区別符と同様に処理されるように変更されたのです。つまり、無視されるようになったのです。

ありがとう ...

Dr. GUI は再び彼の素晴らしい手術助手たちの評判を広めたいと思っています。今回は、ジェフ ボーマン(2 つの栄誉)、ジェイソン クラーク(彼もまた 2 つの栄誉)、スティーブ ダイビング、ジェイソン ストレイヤー、リチャード バン フォサン、クスマ ベランキに賛辞を贈ります。また、このコラムをまとまるように縫合してくれたトム モランとペニー ライカーには、いくつ賛辞を贈っても足りません。

前回のこの記事で、どういうわけかチーム メンバーの 1 人にお礼を言っておりませんでした。ビドヤナンド ラジパサクにお詫びとお礼を申し上げます。