Share via


Ask Dr. GUI #46

1999 年 5 月 / 6 月

未来は明るい:夏時間と新しいデベロッパー グループ

日が長く明るくなるにつれて、Dr. GUI の気分も明るく陽気になってきています。名医はすてきな春と夏を楽しむ計画を練っています。彼は特に 4 月(と 3 月と 2 月と…)の夕立がもたらす美しい花々だけでなく、答えがいのあるたくさんの新しい質問を見れるので幸せです。

また Dr. GUI は Microsoft の新しい組織替え、特に新しい(新しくなった)デベロッパー部門に大喜びです。名医はみなさんにもご満足いただけたらと思っています。みなさまのニーズに応えることに専念する一部門が Microsoft にできたことは、みなさまにとってもすばらしいこと以外の何ものでもありません。

Windows 2000、Office 2000、Internet Explorer 5

Dr. GUI は嬉しい。実に嬉しい。ついに Microsoft® Windows® 2000(Beta 3)が使えるのです。Windows 2000 の最もいいところは、Windows NT® Version 4.0 よりかなり速いところです。Dr. GUI のほとんどのソフトウェアとハードウェアは、次の 2 つ例外を除けば快調に動いています。マウス ドライバが名医の Microsoft Intellimouse® Trackball のクールな機能(クリック アンド ホールドによるドラッグやホイールを使ったページ スクロールなど)をまだサポートしていません。また、名医の SyQuest SparQ パラレル ポート ディスク ドライブ用のドライバも、前のドライバは NT 3.51 と 4.0 では動いていましたが、Windows 2000 で動くドライバがまだありません(これはよい移植プロジェクトになるかもしれません。Dr. GUI がデバイス ドライバや同様の患者さんの複雑な手術を手がけたのはすいぶん前のことですからね)。

Dr. GUI が特に気に入っているのは、Windows 2000 が Dr. GUI のイライラの原因の 1 つ、作業中のアプリケーションから別のアプリケーションがフォーカスを奪ってしまう点に対処していることです(長年の読者は、過去に Dr. GUI がこれに関して強い不満をもらしていたことを覚えているでしょう)。

アプリケーションがフォーカスを奪おうとするのは可能ですが、Windows 2000 がそうさせません。その代わり、タスクバーにあるアプリケーションのボタンが点滅し始め、フォアグラウンドのアプリケーションとは別のアプリケーションが注目を求めていることを知らせます。

これは見事です。おかげで、Windows 2000 では前のバージョンよりもさらに生産性が高まります。

また Dr. GUI は Microsoft Office 2000 にもアップグレードしました。ちょっとしたところが便利になっています。特に便利なのは、Word で作成した絵を保存するときに [Web ページとして保存] を選べば、.gif ファイルとして自動的に保存できることです。

最後に大事なことを言い忘れていましたが、Dr. GUI は多くの評論家たちと同じく Internet Explorer 5.0 には大変満足しています。IE 5.0 は速く、使いやすく、めったにクラッシュしません。これ以上望めるものはありません。

これらのアップグレードを手にすれば、新しいコンピュータを手に入れたこととほとんど同じです。「ほとんど」ですよ。

デベロッパー向けのページで以下の製品に関する情報もご覧ください。Windows 2000 の準備プログラムは http://msdnisv.microsoft.com/msdnisv/win2000/ です。Office 2000 のデベロッパー向けのホームページは https://www.microsoft.com/office/developer/ です。Internet Explorer 5 のデベロッパー情報は www.microsoft.com/Windows/ie/Developers/ です。

では、みなさんからの質問に移りましょう

アイデンティティの喪失

親愛なる Dr. GUI へ

私は ASP ページからアクセスできる COM オブジェクトを持つ DLL(first.dll)を VB 6.0 で開発しています。次に first.dll のいくつかのオブジェクトを使う新しい DLL(second.dll)を作成します。すべてうまくいっているように見えます。問題が起こったのは、first.dll を少々変更して再ビルドしたときです。同じバージョン番号を使って first.dll を再登録しましたが、second.dll が first.dll を認識してくれません。second.dll のプロジェクトを開くと、first.dll への参照がないというメッセージが出ます。再び first.dll を参照するようにして second.dll を再コンパイルしなければなりませんでした。私は何かいけないことをしてしまったのでしょうか?また first.dll を再ビルドするたびに second.dll も再ビルドしなければならないのでしょうか?

クラウディオ フェルナンデス

Dr. GUI の回答:

バイナリ非互換の問題のようですね。ご心配には及びません。これは深刻な問題ではありません。これは主に Microsoft Visual Basic® を使って作った Microsoft ActiveX® コンポーネントに影響を与えているようです。

最初の DLL を再ビルドするたびに 2 番目の DLL も再ビルドするという方法でもいいでしょう。しかし今日の医療費支出の増加傾向を考えると、Dr. GUI としては必要でない限り手術はしたくありません。この問題を解決するには、オブジェクトのバージョン互換性を「プロジェクト互換(Project Compatibility)」に設定する必要があります。これは[プロジェクト プロパティProject Properties)]ダイアログ ボックスの[コンポーネントComponent)]タブで設定します。次の選択肢があります。

  • 互換なし(No Compatibility):コンパイルするたびに新しい GUID のセットが作成されます。このオプションを選ぶと上位互換性がなくなるので、関係をきれいさっぱりなくしたいときにだけ使います。

  • プロジェクト互換(Project Compatibility):コンパイルするたびに、タイプ ライブラリ識別子と、前のバージョンのすべてのクラス ID が維持されます。そして前のバージョンとバイナリ互換のないクラスのインターフェイス ID だけが変更されます。このオプションは新規開発とデバッグする際に選択します。

  • バイナリ互換(Binary Compatibility):[プロジェクト互換]と同じですが、バイナリ レベルの互換性がなくなる違いが検出されると警告を発します。このオプションは既存のコンポーネントの新しいバージョンを作成したいときに選択します。

リリース版のファイルのファイル バージョン番号を上げることを忘れないようにしてください。そうすることにより、Setup は新しいバージョンのファイルを識別できます。

次のオフィスでのパーティで、バージョン間の互換性の話題についていけるようにしたい方は、以下に目を通しておきましょう。MSDN Library、「Maintaining Version Compatibility.txt」の Visual Basic Component Tools Guide の「Version Compatibility in ActiveX Components"(https://msdn.microsoft.com/vbasic/downloads/download.asp?ID=012) と、Microsoft Press 出版の Ted Pattison 著の『Programming Distributed Applications with COM and Microsoft Visual Basic 6.0』 (http://mspress.microsoft.com/books/2137.htm) の第 5 章「COM Servers」。

ちょっとした TLI の調査

親愛なる Dr. GUI へ

私は VB の TLI(type library information)オブジェクトを使って、コンポーネント インターフェイスを調べています。あなたに助けていただけそうな問題に遭遇しました。

  1. Get プロパティ名がわかっているときに、そのプロパティがオブジェクトを返すかどうか知りたいのです。オブジェクト以外のプロパティのデータ型を取得するのには成功しているのですが、オブジェクト プロパティのデータ型を尋ねるといつも 0(VT_EMPTY)が返されます。VT_DISPATCH などが返ってくるものと考えていたのですが。

  2. Get プロパティがオブジェクトを返すのがわかれば、当該オブジェクトが特定のインターフェイスを実装しているかどうかを知りたいのです。

よいお考えは?

ケン エルストン

Dr. GUI の回答:

よい質問です。Dr. GUI は調査が得意です。それがたとえタイプ ライブラリの場合でもです。TLI(type library information)オブジェクト モデルは、タイプ ライブラリを参照する手段をいくつか提供しています(TLI オブジェクトは tlbinf32.dll に実装されています。tlbinf32.dll は Microsoft Visual Studio® version 6.0 と Visual Basic 6.0 の CD-ROM にあります)。質問の内容に基づいて、名医はあなたが Get プロパティの MemberInfo オブジェクトをすでに取得しているものと仮定します。この質問に限れば、Members.ItemTypeInfo.GetMember、あるいは TypeLibInfo.GetMemberInfo を使って MemberInfo を取得しているかどうかは関係ありません。

難しいのは、MemberInfo.ReturnType プロパティが返す VarTypeInfo オブジェクトの解釈です(MemberInfo.Parameters(n).VarTypeInfo から VarTypeInfo を取得することもできます)。VarTypeInfo は任意の型を表す役目を果たします。単純なバリアント型(VT_I4VT_DISPATCH など)の場合は、VarType プロパティ(VarTypeInfo オブジェクトの標準の値)を見て型を判断できます。しかし、VarType が 0(VT_EMPTY)として返された場合は、単純なバリアント型ではなく、タイプ ライブラリで定義された型を対象にしていることになります。このような型の型情報は、VarTypeInfo.TypeInfo プロパティによって返される TypeInfo オブジェクトから取得できます。配列が対象の場合には、問題は少し複雑になります。この場合、long の配列ならば VarTypeVT_ARRAY | VT_I4 になりますが、TypeInfo の配列ならば VT_ARRAY だけとなります。通常、VarTypeInfo オブジェクトを調べるときには、TLI.ResolveAliases を False に設定してから調べましょう。

TypeInfo オブジェクトの NameGUIDTypeKind プロパティから、その型に関する情報を得ることができます。また、Parent プロパティから、その型を含んでいるタイプ ライブラリに関する情報を持っている TypeLibInfo オブジェクトを取得できます。

2 番目の質問ですが、TypeKind プロパティが TKIND_COCLASS ならば、そのオブジェクトが他のインターフェイスを実装している可能性があります。TypeInfo.Interfaces コレクションを調べるだけで該当するインターフェイスを検索できます。GUID プロパティ、または NameParent.Name の組み合わせを使ってインターフェイスを特定できます。実装されているインターフェイスを検索する際には、Interfaces コレクションにある項目のうち、AttributeMaskIMPLTYPEFLAG_FSOURCE が含まれている項目は無視しましょう。関数は次のようになります。

  Function IsInterfaceSupported(VTInfo As TLI.VarTypeInfo, strGUID As String) As Boolean
Dim VTBase As Integer
Dim TI As TypeInfo
With VTInfo
   VTBase = .VarType And Not (VT_ARRAY Or VT_VECTOR)
   If VTBase = VT_EMPTY Then
      With .TypeInfo
            Select Case .TypeKind
               Case TKIND_INTERFACE , TKIND_DISPATCH
                  If .Guid = strGUID Then
                        IsInterfaceSupported = True
                  End If
               Case TKIND_COCLASS
                  For Each TI In .Interfaces
                        If 0 = (TI.AttributeMask And IMPLTYPEFLAG_FSOURCE) Then
                           If TI.Guid = strGUID Then
                              IsInterfaceSupported = True
                              Exit For
                           End If
                        End If
                  Next
               Case TKIND_ALIAS
                  'See help file.This won't happen
                  'if TLI.ResolveAliases was True when
                  'VTInfo was retrieved.
            End Select
      End With
   End If
End With
End Function

https://msdn.microsoft.com/vbasic/downloads/addon.asp からダウンロードできるヘルプ ファイルを読んで、PrototypeMember 関数について学ぶことをお勧めします。(Enterprise Development Tools Partner Downloads セクションの「Visual Studio 6.0 Typelib Information Object Library HTML Help file」のリンクを参照してください)。

Visual Basic からの SQL パッケージの実行

親愛なる Dr. GUI へ

VB のコードから SQL 7 DTS のパッケージを実行するにはどうすればよいのでしょうか。このパッケージはすでに定義済みで、「dtsrun」と Enterprise Manager を使ってエラーなしで実行します。サンプル コードをいくつか見つけましたが、それにはステップとタスクなどが必要でした。私はパッケージを実行したいだけなのです。「シェル」コマンドを使えばうまくいきますが、私はもっとエレガントに実行したいのです。

ありがとう。

- ランディ ギーター

Dr. GUI の回答:

刑の執行に立ち会うのは Dr. GUI のスタイルに反しますが、この執行では誰も犠牲にはならないようなので、今回はやむを得ず参加します。Visual Basic アプリケーションをインストールする予定のマシンに Microsoft SQL Server™ 7.0 のクライアント コンポーネントをインストールしている場合は、DTSPackage オブジェクト ライブラリを利用します。Visual Basic のアプリケーションから「Microsoft DTSPackage Object Library」を参照するだけで、次のような簡単なコードを使用できます。

     Set dtsPackage = New DTS.Package
   dtsPackage.LoadFromSQLServer ServerName:="MyServer", _
                                 ServerUserName:="MyUserID", _
                                 ServerPassword:="MyPassword", _
                                 PackageName:="MyPackage"
   dtsPackage.Execute

DTSPackage オブジェクト ライブラリは非常に強力です。このライブラリを使えば、SQL Server、リポジトリ、ファイルに格納されたパッケージを実行できるだけでなく、パッケージの作成や変更もできます。

SQL Server クライアント コンポーネントをインストールするには、[Install SQL Server 7.0 Components]を選択して、[Standard]または[Desktop Edition]を選択します。次に[Select Components]画面で[Client Components]を選択します。

SQL Server 7.0 クライアント コンポーネントをインストールするつもりのない場合でも、次のように xp_cmdshell ストアド プロシージャを使ってサーバーの DTS パッケージを実行できます。

     strShell = "dtsrun /S MyServerName /U MyUserID /P MyPassword /N MyPackage"
   strSQL = "{CALL xp_cmdshell('" & strShell & "', 'no_output')}"
   cnSQLServer.Execute strSQL, , adCmdText + adExecuteNoRecords

size_of の問題

親愛なる Dr. GUI へ

size_is 修飾子で問題が起きています。size_is が正しく機能していないと思います。

クライアントからローカル サーバーに可変長のバイト配列を渡そうとしているのですが、うまくいきません。サーバーへ転送されるのは最初の 1 バイトだけなのです。

サーバーの IDL ではメソッドを次のように宣言しています。

  HRESULT Transfer([in] long cbSize,
      [in, size_is(cbSize)] unsigned char cBuffer[])

クライアント側のコードは次のとおりです。

  ITargetObj * pITargeObj ;
HRESULT hRC = ::CoCreateInstance( CLSID_TargetObj, NULL, CLSCTX_SERVER,
IID_ITargetObj, (void **)&pITargetObj ) ;
If FAILED( hRC )
{
   AfxMessageBox( "Failed to make target object!" ) ;
   return FALSE ;
}

BYTE * pBuffer = (BYTE *)::CoTaskMemAlloc( 15 ) ;
CopyMemory( pBuffer, "HELLO WORLD!!!!", 15 ) ;
pITargetObj->Transfer( 15, pBuffer ) ;
::CoTaskMemFree( pBuffer ) ;
pITargetObj ->Release() ;

助けてくれてありがとう。

敬具

- デビッド リンデマン

Dr. GUI の回答:

Dr. GUI はときどきサイズが問題になるかどうか質問されます(それほど頻繁にではありませんよ。Dr. GUI あくまでも医者であって、洋服屋ではないのでね)。あなたのコードは size_of が問題になることを反証しています。これが正しく動作しないとクラッシュします。

名医はあなたのコードをテストしました。Active Template Library(ATL)ローカル サーバーにおいて、(プロキシ / スタブをビルドして登録した)カスタム インターフェイスをサポートするオブジェクトでは問題なく動作しているようです。唯一考えられる説明は、あなたがデュアル インターフェイスをサポートする ATL オブジェクトを使用したということでしょう。この場合、Microsoft Interface Definition Language(MIDL)は次のような警告を出すはずです。

  Target.idl(18) :warning MIDL2039 :interface does not conform to [oleautomation] attribute :[ Parameter 'cBuffer' of Procedure 'Transfer' ( Interface 'ITargetObj' ) ]

問題は、size_is 属性が dual 属性と oleautomation 属性と互換性のないことです。それでも MIDL は警告を無視して先に進みタイプ ライブラリを作成します。OLE COM オブジェクト ビューアを使ってタイプ ライブラリを調べると、Transfer のプロトタイプが次のとおりだということがわかります。

  HRESULT Transfer([in] long cbSize, [in] unsigned char* cBuffer);

size_is 属性が省かれているので(オートメーションでサポートされていません)、クライアントからサーバーには最初の文字しか転送されません。これはインターフェイスをマーシャリングするのにタイプ ライブラリが使用されるからです。明らかに、これではうまくいきません。話は変わりますが、デュアル インターフェイスでもディスパッチ インターフェイスでも問題なく配列を渡すには、SAFEARRAY を使うという方法があります。

優先順位を守れない問題

親愛なる Dr. GUI へ

 VB3 で書かれた通信プログラムがいくつかあるのですが、Win3.1 と Win95 システムで問題なく動いていたこれらのプログラムが WinNT で動作しません。NTVDM.EXE の優先順位を「High」から「Normal」に設定すれば問題が解決することはわかりました。問題はこの設定が永久ではないことです。NT を再起動するたびにこの優先順位を再設定しなくてはなりません。NTにこの優先順位を標準の設定として記憶させる方法はないのでしょうか。それが無理だとしたら、プログラムの中からこの優先順位の設定を変更する方法はないのでしょうか。Start コマンドで試しましたがうまくいきませんでした。

ありがとう。

- ビル スー

Dr. GUI の回答:

Dr. GUI の努力にもかかわらず、いまだにこうした心理についての問い合わせがあるようですね。いや、ちょっと待ってください。これはオペレーティング システムの優先順位の問題であって、個人的な優先順位の問題ではないのですね。それならだいじょうです。さっそくお答えしましょう。

アプリケーションに標準の優先順位レベルを設定する方法はありません。しかし、ご推察どおり、これはプログラムの中から設定できます。SetPriorityClass を呼び出して第 2 パラメータとして HIGH_PRIORITY_CLASS だけです。難しいのは、第 1 パラメータの取得で、これは ntvdm.exe プロセスのハンドルです。Windows NT では Process Status API(PSAPI)を使って、システムで実行されているプロセスを列挙することができます。次の手順で ntvdm.exe プロセスを見つけることができます。

  1. EnumProcesses を呼び出します。この PSAPI 関数はシステムのプロセス ID の配列を返します。

  2. 返された 3 番目のパラメータは返されたバイト数です。このバイト数を sizeof(DWORD)で割って実行中のプロセス数を算出します。

  3. 各プロセス ID ごとに次のようにします。

    1. OpenProcess を呼び出してプロセスのハンドルを取得します。

    2. これが成功したら、EnumProcessModules を呼び出して(次の手順で必要となる)モジュール ハンドルの配列を取得します。

    3. GetModuleBaseName を呼び出して、プロセスのハンドルと最初のモジュール ハンドルを渡します。これによりプロセス名を取得できます。

    4. これと ntvdm.exe を比較します。比較が成功したら、そのプロセス ハンドルを使って SetPriorityClass を呼び出します。

    5. 成功しない場合は、ハンドルを閉じて手順 1 に戻ります。

注意 複数の ntvdm.exe が存在する場合もあります。前記の手順はそのすべてに影響します。

コンボ メッセージの取得

親愛なる Dr. GUI へ

CContainedWindow を使って標準ウィンドウのコントロールをラップする ATL コントロールを書こうとしています。この標準のウィンドウはたまたま CBS_DROPDOWNLIST ComboBox なのですが、問題は次の点にあります。オーナー描画であるという点です [CBS_OWNERDRAWFIXED, CBS_HASSTRINGS, CBS_SORT]。

コントロールは一見正しく描画しているようでした。「edit」コントロールとドロップダウン矢印が表示されます。そのリストをドロップダウン表示すると、オーナー描画コールバックが実行され、項目が期待通りに描画されます。しかしリストから項目を選択すると「edit」コントロールは選択した項目を描画しません。OnDrawItem コールバックの中で ODS_COMBOBOXEDIT itemState フラグを検出するようにしているのですが検出できないのです。

「edit」コントロールに DrawItem コールバック関数を呼ばせるためには、何をインターセプトして CContainedWindow に渡す必要があるのでしょうか。これはコンボ ボックス(リストと編集コントロールとの間のやり取り)固有の問題なのでしょうか、それとも ActiveX コントロールで一般的なオーナー描画の問題なのでしょうか。

- ケビン ルートリー

Dr. GUI の回答:

正しいメッセージを正しいウィンドウに渡すのには工夫が必要です。ウィンドウのドロップダウン リスト コンボ コントロールをベースにしてウィザードを使って作成した ATL ActiveX コントロールを使う今回のケースでは、ウィンドウのレベルが 3 つあります。1 つ目は ATL コントロール ウィンドウで、コンボ ボックスの親です。2 つ目はコンボ ボックス自身で、選択されたテキストを表示する静的な部分で構成されています。3 つ目は子リスト ボックスで、ドロップダウン矢印を選択するとドロップタウン表示されます。

WM_DRAWITEM メッセージのセットは、リスト ボックスからコンボ ボックスに送られるものと、コンボ ボックスから ATL ウィンドウに送られるものと 2 つあります。WM_DRAWITEM メッセージはオーナー描画コントロールによってオーナーに送信されます。リスト ボックスからコンボ ボックス ウィンドウに送られたメッセージは、そこで処理されなければ、コントロールのタイプを ODT_LISTBOX から ODT_COMBOBOX に変更された後で、コンボ ボックスから ATL ウィンドウに送られます。あなたの説明を読む限り、リスト ボックスから親のコンボ ボックスに送信された WM_DRAWITEM をそこで処理しているようのなので、ODS_COMBOBOXEDIT ステータス通知を検出できないのはおそらくそれが原因でしょう。ATL ウィンドウでメッセージを処理する方がずっと簡単です。次の、ウィザードによって生成された ATL コントロール プロジェクトのメッセージ マップは、WM_DRAWITEM のハンドラの置き場所としてふさわしい場所を 2 つ示します。

  BEGIN_MSG_MAP(CODCombo)
   // Correct place for WM_DRAWITEM handler
   MESSAGE_HANDLER(WM_DRAWITEM, OnDrawItemFromCombo)
   CHAIN_MSG_MAP(CComControl<CODCombo>)
ALT_MSG_MAP(1)
   // Replace this with message map entries for superclassed ComboBox
   // handler here for WM_DRAWITEM is premature, so we comment out
   //MESSAGE_HANDLER(WM_DRAWITEM, OnDrawItemFromListBox)
END_MSG_MAP()

これで単独のハンドラを正しい位置に置けたので、今度はテキストの選択とフォーカスを正しく処理するコードを、Microsoft Visual C++®に付属の SDK サンプルである OWNCOMBO から拝借できます。具体的には、owncombo.c の OwnerComboBoxExample() 関数の中での WM_DRAWITEM の処理を参照してください。

プロパティ シートのウォーターマーク

親愛なる Dr. GUI へ

私は新しい IE 5.0 コモン コントロールを使ってウィザードを作成しようとしています。しかし、MFC 6.0(CPropertySheet)は AFX_OLDPROPSHEETHEADER という古いバージョンの Property Sheet Header を使っているので、ウォーターマーク プロパティをうまく設定できません。MFC を使って新しいウィザード 97 の機能を使う方法、または ATL でこれを行う別の方法はありますか?また私はこのウィザードをスタンドアロン形の EXE にする必要があります。

どんな助けでも感謝します。

- バーブ レグニア

Dr. GUI の回答:

たいてい人は自分の持ち物についている商品マークをとろうと考えます。家事情報でおなじみのマーシャ スチュワートならそうしたマークを消す方法を教えてくれるかもしれませんが、Dr. GUI はウォーターマークを付けることならお手伝いできます。

MFC version 6.0 を使って新しい Wizard 97 の機能を利用するのは実に簡単です。MFC は新しい PROPSHEETHEADER を使う CPropertySheetEx を提供します。また新しい PROPPAGEHEADER を使う対応する CPropertyPageEx クラスもあります。しかし、ClassWizard はこれらのクラスから派生するための方法を提供していません。

でも心配しないでください。必要なコードはそれほど多くありません。CPropertySheetCpropertyPage からクラスを派生させます。しかしここであなたの CWizardSheetCPropertySheet から派生させたクラスをこう呼ぶことにしましょう)で、コンストラクタのパラメータを CPropertySheetEx のパラメータで置き換え、コードの中に現れるすべての CPropertySheetCpropertySheetEx に置き換えます。CPropertySheetEx クラスの m_psh.dwFlags メンバに、PSH_WIZARD の代わりに PSH_WIZARD97 フラグを追加するのを忘れないでください。

CPropertyPageCPropertyPageEx の標準のコンストラクタは同じパラメータを使うので、CPropertyPageEx(標準のコンストラクタを使うと想定して)ではもっと簡単です。コードの中に現れるすべての CPropertyPageCPropertyPageEx に置き換えるだけです。

CPropertySheetEx コンストラクタは、プロパティ ページのウォーターマークとして使いたいビットマップのハンドルを受け取ります(第 4 パラメータ)。また CPropertySheetEx コンストラクタは HPALETTE も受け取ります。標準のパレットを使ってウォーターマークを描画したい場合は、HPALETTE を NULL として渡します(このウォーターマークは通常表示されません。これについては後で説明します)。最後に、プロパティ ページのヘッダを描画したいときに指定できる HBITMAP パラメータがあります。そうなのです。CPropertyPageEx ではすべてのページの先頭にヘッダを持つことができるのです。ヘッダを表示するようにした場合は、このビットマップがヘッダを描画するのに使われます。これが NULL だと、ウォーターマークのビットマップのハンドル(第 4 パラメータ)がヘッダを描画するのに使われます。

ほかにもオプションがいくつかあります。プロパティ ページのタイトルとサブタイトルを渡すこともできます。CPropertyPageEx コンストラクタを見てください。タイトルはページのキャプションとして表示され、サブタイトルはヘッダのビットマップの上に表示されます。ヘッダを隠すこともできます。それには PSP_HIDEHEADER フラグを CPropertyPageExm_psp.dwFlags メンバに追加します。この場合、ウォーターマークがプロパティ ページに表示されます。

その他はすべて CPropertySheetCPropertyPage クラスの場合と同じです。

順序、順序!

親愛なる Dr. GUI へ

私は Win32 API を使って動的にダイアログを作成しています。DialogBoxIndirect() で使う基本テンプレートも用意してあります。WM_INITDIALOG の中で追加のコントロールを動的に作成しています。

問題は、動的に作成したコントロールに WS_TABSTOP スタイルを含めていても、タブ移動の順序に入らないことです。

何がいけないのでしょうか。また、どうしたら実行時にタブ移動順序を変えることができるのでしょうか。

ありがとう!

- デイブ

Dr. GUI の回答:

Dr. GUI がこうした問題を目にしてからだいぶ経ちました。最後に「勘定書きについて問答(訳注:tab order in a dialog:ダイアログの中のタブ移動順序とも訳せます)」したのは、レストランで誰がいくら払うのかを相談したときでした。

あなたの抱えている問題を理解するために、キーボード ナビゲーションがダイアログ ボックスでどのように行われるのかについて光を当ててみましょう。モーダルとモードレス ダイアログ ボックスのどちらの場合でも、キーボード ナビゲーション機能は IsDialogMessage という関数によって提供されます。この関数は、ダイアログにおいて膨大な量のキーボード ナビゲーション機能を無料で提供します(またこれは Windows アプリケーション間でのダイアログの動作に一貫性を持たせます)。

ユーザーが TAB キーを押すと、IsDialogMessageGetNextDlgTabItem API を呼び出して、次の移動先にできる項目を取得してそれにフォーカスを設定します。その項目がスタティック、不可視または無効なコントロールであったり、WS_TABSTOP スタイルが設定されていなければ、タブ順序には含まれません。。しかしあなたは WS_TABSTOP を使っているので、これはあなたの問題にはあてはまりません。

コントロールがフォーカスを取得できない別の理由として考えられるのは、動的に作成されたコントロールが、WS_EX_CONTROLPARENT スタイルが設定されていないダイアログ ボックス内の親ウィンドウにある場合です。最後に、モーダル ダイアログ ボックス(DialogBox()DialogBoxIndirect())では IsDialogMessage がメッセージ ループ内にありますが、モードレス ダイアログ ボックス(CreateDialog()CreateDialogIndirect())ではメイン メッセージ ループの中で明示的に IsDialogMessage を呼び出さなければならない点に注意しましょう。これらの点を除けば、動的に作成されるコントロールはタブ移動の順序に含まれるはずです。

実行時にタブ移動の順序を変更するには、SetWindowPos API を呼び出す必要があります。Microsoft Windows はシステムのすべてのウィンドウをリストで管理します。Microsoft Windows はこのリストを使ってウィンドウの描画や Z 軸方向の順序の管理などをします。このリストの中での子コントロールの位置、そして結果としてそのタブ移動の順序は、(ダイアログ ボックスの中で)これらのコントロールが作成された順番によって決まります。リストの中でのウィンドウの位置を変えることで、タブ移動の順序を変更できます。ウィンドウ リストの順序を変えるには、SetWindowPos を呼び出して、関係するウィンドウのハンドルを第 1 パラメータ(順序に挿入するウィンドウのハンドル)と第 2 パラメータ(挿入位置の 1 つ手前のウィンドウのハンドル)として渡します。タブ移動の順序では、第 1 パラメータで指定したウィンドウ ハンドルが、第 2 パラメータに指定したウィンドウの後になります。

グラフィックスのバッファリング:腹痛にはよいかも?

親愛なる Dr. GUI へ

私は最近 DirectX のプロジェクトを始めたのですが、プログラムの中でフレームごとにバック バッファ全体のロックとアンロックをする必要のあるところで、パフォーマンスがきわめて悪いという問題で悩んでいます。

バック バッファを、現在のビデオ メモリにではなく、システム メモリに置いて、フレームごとにすべてのデータを 2 回コピーする(ビデオ メモリからのコピーと、そこへのコピー)必要をなくせば、相当パフォーマンスが改善すると考えています。Microsoft Press から出ている DirectX の本では、これをできることがほのめかされていましたが、実際にそれを行う方法を示す例も見つからず、途方にくれています。

私の人生をもっと耐えられるものにしてくれるアイディアはありませんか。

ありがとう

- サイモン デザリッジ

Dr. GUI の回答:

Dr. GUI はグラフィックスをバッファリングすることで腹痛が和らぐかどうかは知りませんが、バッファをうまく使えばパフォーマンスを大幅に改善できることは知っています。

あなたの現在のレンダリング システムが複雑な切り替えチェーン(プライマリ バッファにバック バッファがアタッチされている)を使っている場合、明示的にビデオ メモリにプライマリ バッファを作成して、システム メモリにバック バッファを置くということはできません。これができるのはビデオ メモリが不足しているときだけです。1 つのサーフェイスがアタッチされた複雑なサーフェイスを作成して、バック バッファがビデオ メモリに入りきらない場合、バック バッファはシステム メモリに確保されます。この場合、Flip の呼び出しは Blt でエミュレートされます。

あなたの現在のレンダリング システムがプライマリ サーフェイスと、バック バッファとしてオフスクリーン サーフェイスを使用している場合には、システム メモリにバック バッファを作成できます。これを行うには、DDSCAPS_SYSTEMMEMORY フラグが必要です。たとえば次のようにします。

     ZeroMemory(&ddsd, sizeof(ddsd));
   ddsd.dwSize  = sizeof(ddsd);
   ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
   ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_SYSTEMMEMORY;
   ddsd.dwHeight = 100;
   ddsd.dwWidth = 100;

システム メモリにバック バッファを作成するほかに、ハードウェアがシステム メモリとの間で blit 処理できることを確認する必要があります。IDirectDraw4::GetCaps を使って次のフラグを確認します。

  • DDCAPS.dwCaps 構造体メンバの DDCAPS_CANBLTSYSTEM

  • DDCAPS.dwSVBCaps 構造体メンバの DDCAPS_BLT(システムからビデオへ)

  • DDCAPS.dwVSBCaps 構造体メンバの DDCAPS_BLT(ビデオからシステムへ)

Win16 mutex を避けるためにビデオ メモリの代わりにシステム メモリにバック バッファを作成しても、パフォーマンス向上につながるとは限りません。あなたのレンダリング システムが Blt または BltFast を使ってバック バッファをプライマリ バッファにコピーしている場合は、ハードウェア支援のビデオ メモリ間のメモリ コピーを犠牲にして、Direct Memory Access(DMA)メモリ コピーのあるなしにかかわらず、バスを介したシステムとビデオとの間のメモリ コピーを行っていることになります。

ベストなソリューションを決めるためには、様々なビデオ システムでそれぞれの手法をプロファイリングする必要があることを心に留めておきましょう。

Dr. GUI ひっかかる!それとも違うのか?

親愛なる Dr. GUI へ

キット ルパーレルの CIM の発音についての質問の回答の中で、CIM は Microsoft で使用されている 2,050,467,329 番目の TLA(Three Letter Acronym:3 文字の頭字語)だとありました。そのときに TLA の数が GUID や宇宙の粒子よりも多いとは言わないと言っていましたね。しかし、重複するすべての TLA の意味を解決する方法については触れませんでした。英語のアルファベットで 26 個の大文字の組み合わせは 17,576 しかありません。すなわち、1 つの 3 文字の組み合わせが、平均でおよそ 100,000 個の TLA で使われていることになります。大文字と小文字を区別すれば、問題は少し軽減されます(140,608 の組み合わせになり、重複する数は平均 15,000 以下になります)。しかし、これはありえそうもないことです。Microsoft は少なくとも MS-DOS 1.0 以降も大 / 小文字の区別に関してはほとんど気にしていませんでしたからね(ところで、これについて司法省か EEOC かほかの誰かが取り上げるつもりはないのでしょうかね?)。

では、重複する TLA をどのように区別すればいいのでしょうか。C++ のように名前を汚しますか(いや、修飾しますか)?どう考えても、これは役に立つとは言えません。

心配する市民より

- ジェッセ ペルトン

Dr. GUI の回答:

あなたの心配は賞賛に値しますね。おそらく名医は誇張したのか、詩的に過ぎたのでしょう。ですが、名医は Unicode の世界に住んでいることをお忘れなく。そこでは文字ごとに何万という可能性があり、20 億の頭字語を考えつくのも難しくないのです。

ありがとう!

Dr. GUI はこの場を借りて彼の専門医チーム、ランガ カルヤナサンダラム、ブライアン コームズ、イズラエル バーマン、デビッド シェッパ、ジャガナサン サンガベル(2 問も答えてくれました!)、カルロス ロペス、アルン クマール、ハン H. グエン、トム モーラン、ありがとう。名医はトム モーランが専門医のコーディネータも務めたことにも感謝しています。