Windows_10_システム_プログラミング_2、オブジェクトとハンドル

オブジェクトとハンドル

Windows はオブジェクト ベースのオペレーティング システムであり、Windows の機能の大部分を提供するさまざまな種類のオブジェクト (通常はカーネル オブジェクトと呼ばれます) を公開します。オブジェクト タイプの例としては、プロセス、スレッド、ファイルなどがあります。この章では、特定のオブジェクト タイプについてはあまり詳しく説明せずに、カーネル オブジェクトに関連する一般理論について説明します。次の章では、これらのタイプの多くについて詳しく説明します。

Windows は、Windows のほとんどの機能を提供するさまざまな種類のオブジェクト (カーネル オブジェクトと呼ばれることが多い) を公開するオブジェクト ベースのオペレーティング システムです。オブジェクト タイプの例としては、プロセス、スレッド、ファイルなどがあります。この章では、特定のオブジェクト タイプについてはあまり詳しく説明せずに、カーネル オブジェクトに関連する一般理論について説明します。次のセクションでは、これらのタイプの多くについて詳しく説明します。

この章では:

ここに画像の説明を挿入

カーネルオブジェクト

Windows カーネルでサポートされているオブジェクトの種類は数多くあります。ピークを取得するには、Sysinternals (昇格) から WinObj ツールを実行し、ObjectTypes ディレクトリを見つけます。図 2-1 は、これがどのようになるかを示しています。これらのタイプは、可視性と使用法に基づいてカタログ化できます。

Windows カーネルは、かなりの数のオブジェクト タイプをサポートしています。確認するには、Sysinternals (管理者特権) から WinObj ツールを実行し、ObjectTypes ディレクトリを見つけます。図 2-1 にその様子を示します。これらのタイプは、可視性と目的に応じて分類できます。

ここに画像の説明を挿入

カーネル オブジェクトの主な属性を図 2-2 に示します。

カーネル オブジェクトの主な属性を図 2-2 に示します。

ここに画像の説明を挿入
カーネル オブジェクトはシステム空間に存在するため、ユーザー モードから直接アクセスすることはできません。

アプリケーションは、ハンドルと呼ばれるカーネル オブジェクトにアクセスするために間接的なメカニズムを使用する必要があります。

カーネル オブジェクトはシステム空間に存在するため、ユーザー モードから直接アクセスすることはできません。

アプリケーションは、ハンドルと呼ばれるカーネル オブジェクトにアクセスするために間接的なメカニズムを使用する必要があります。

カーネル オブジェクトは参照カウントされます。オブジェクト マネージャーはハンドル カウントとポインター カウントを維持し、その合計がオブジェクトの合計参照カウントになります (直接ポインターはカーネル モードから取得できます)。ユーザー モード クライアントによって使用されるオブジェクトが不要になったら、クライアント コードは CloseHandle を呼び出して、オブジェクトへのアクセスに使用されるハンドルを閉じる必要があります。その時点から、コードはハンドルが無効であると見なす必要があります。閉じられたハンドルを介してオブジェクトにアクセスしようとすると失敗し、GetLastError は ERROR_INVALID_HANDLE (6) を返します。通常、クライアントはオブジェクトが破棄されたかどうかを知りません。オブジェクト マネージャーは、オブジェクトの参照がゼロになるとそのオブジェクトを削除します。

内核对象是引用计数的。对象管理器维护一个句柄计数和一个指针计数,它们的总和就是对象的总引用计数(直接指针可以从内核模式获得)。一旦不再需要用户模式客户端使用的对象,客户端代码应通过调用 CloseHandle 关闭用于访问该对象的句柄。从那时起,代码应将句柄视为无效。尝试通过关闭的句柄访问对象将失败,GetLastError 返回 ERROR_INVALID_HANDLE (6)。在一般情况下,客户不知道对象是否已被销毁。如果对象的引用降为0,对象管理器将删除该对象。

Handle values are multiples of 4, where the first valid handle is 4; Zero is never a valid handle value. This scheme does not change on 64 bit systems.

句柄值是4的倍数,其中第一个有效句柄为4;0永远不是有效的句柄值。这个方案在 64 位系统上不会改变。

ハンドルは論理的には、プロセスごとに維持されるハンドル テーブル内のエントリの配列へのインデックスであり、システム空間に存在するカーネル オブジェクトを論理的に指します。オブジェクトを作成したり開いたり、これらのオブジェクトへのハンドルを取得したりするためのさまざまな Create* および Open* 関数があります。オブジェクトを作成できない、または開くことができない場合、返されるハンドルはほとんどの場合 NULL (0) です。このルールの注目すべき例外の 1 つは、失敗した場合に INVALID_HANDLE_VALUE (-1) を返す CreateFile 関数です。

ハンドルは論理的には、プロセス ベースで維持されるハンドル テーブル内のエントリの配列へのインデックスであり、論理的にはシステム空間に存在するカーネル オブジェクトを指します。オブジェクトを作成したり開いたり、それらのオブジェクトへのリターン ハンドルを取得したりするために使用できる、さまざまな Create* および Open* 関数があります。オブジェクトを作成または開くことができなかった場合、返されるハンドルはほとんどの場合 NULL (0) です。このルールの注目すべき例外は CreateFile 関数で、失敗すると INVALID_HANDLE_VALUE (-1) を返します。

たとえば、CreateMutex 関数を使用すると、(その名前のミューテックスが存在するかどうかに応じて) 新しいミューテックスを作成したり、名前でミューテックスを開くことができます。成功すると、関数はミューテックスへのハンドルを返します。戻り値 0 は、無効なハンドル (および関数呼び出しの失敗) を意味します。一方、OpenMutex 関数は、名前付きミューテックスへのハンドルを開こうとします。その名前のミューテックスが存在しない場合、関数は失敗します。

たとえば、CreateMutex 関数を使用すると、(その名前のミューテックスが存在するかどうかに応じて) 新しいミューテックスを作成したり、名前でミューテックスを開くことができます。成功すると、関数はミューテックスへのハンドルを返します。戻り値 0 は、ハンドルが無効であること (および関数呼び出しが失敗したこと) を意味します。一方、OpenMutex 関数は、名前付きミューテックスへのハンドルを開こうとします。その名前のミューテックスが存在しない場合、関数は失敗します。

If the function succeeds and a name was provided, the returned handle can be to a new mutex or to an existing mutex with that name. The code can check this by calling GetLastError and comparing the result to ERROR_ALREADY_EXISTS. If it is, then it’s not a new object, but rather another handle to an existing object. This is one of those rare cases where GetLastError can be called even if the API in question succeeded.

如果函数成功并且提供了一个名字,则返回的句柄可以是一个新的mutex,也可以是一个有这个名字的现有mutex。代码可以通过调用 GetLastError 并将结果与​​ ERROR_ALREADY_EXISTS 进行比较来检查这一点。如果是,那么它就不是一个新的对象,而是现有对象的另一个句柄。这是一种罕见的情况,即使有关的API成功了,GetLastError也可以被调用。

Running a Single Instance Process

ERROR_ALREADY_EXIST ケースのよく知られた使用法の 1 つは、実行可能ファイルが単一のプロセス インスタンスを持つように制限することです。通常、エクスプローラーで実行可能ファイルをダブルクリックすると、その実行可能ファイルに基づいて新しいプロセスが生成されます。この操作を繰り返すと、同じ実行可能ファイルに基づいて別のプロセスが作成されます。2 番目のプロセスの起動を阻止するか、少なくとも同じ実行可能ファイルが既に実行されている別のプロセス インスタンスを検出した場合にはシャットダウンしたい場合はどうすればよいでしょうか。

ERROR_ALREADY_EXIST 条件のよく知られた使用法は、実行可能ファイルが単一のプロセス インスタンスを持つように制限することです。通常、エクスプローラーで実行可能ファイルをダブルクリックすると、その実行可能ファイルに基づいて新しいプロセスが生成されます。これが繰り返されると、同じ実行可能ファイルに基づいて別のプロセスが作成されます。2 番目のプロセスの起動を阻止したい場合、または同じ実行可能ファイルを持つ別のプロセス インスタンスが既に実行されていることが検出された場合には少なくともシャットダウンしたい場合はどうすればよいでしょうか。

The trick is using some named kernel object (a mutex is usually employed, although any named object type can be used instead), where an object with a particular name is created.
If the object already exists, there must be another instance already running, so the process can shut down (possibly notifying its sibling of that fact).

诀窍是使用一些命名的内核对象(通常使用mutex,尽管可以使用任何命名的对象类型),其中一个具有特定名称的对象被创建。 如果该对象已经存在,一定有另一个实例已经运行,所以进程可以关闭(可能通知其兄弟姐妹这一事实)。

The SingleInstance demo application demonstrates how this can be achieved. It’s a dialogbased application built with WTL. Figure 2-3 shows what this application looks like running.
If you try launching more instances of this application, you’ll find that the first window logs messages coming from the new process instance that then exits.

SingleInstance のデモは、これを実現する方法を示しています。これは、WTL で構築されたダイアログ ベースのアプリケーションです。図 2-3 は、動作中のアプリケーションを示しています。
このアプリのさらにインスタンスを開始しようとすると、最初のウィンドウが新しいプロセス インスタンスからの情報をログに記録してから終了することがわかります。

ここに画像の説明を挿入

WinMain 関数では、最初にミューテックスを作成します。これが失敗した場合は、何かが非常に間違っているため、救済されます。

WinMain 関数では、最初にミューテックス オブジェクトを作成します。それが失敗した場合は、何かが非常に間違っていることになり、それを修正するための措置を講じます。

ここに画像の説明を挿入

ミューテックスの作成に失敗することは非常にまれです。最も可能性の高い失敗のシナリオは、同じ名前の別のカーネル オブジェクト (ミューテックスではない) がすでに存在することです。

ミューテックスの作成が失敗することは非常にまれです。最も失敗する可能性が高いのは、同じ名前の別のカーネル オブジェクト (ミューテックスではない) がすでに存在する場合です。

ミューテックスへの適切なハンドルを取得したので、唯一の問題は、ミューテックスが実際に作成されたのか、それとも既存のミューテックス (おそらくこの実行可能ファイルの前のインスタンスによって作成された) への別のハンドルを受け取ったのかということです。

正しいミューテックス ハンドルを取得したので、唯一の問題は、ミューテックスが実際に作成されたのか、それとも既存のミューテックスへの別のハンドルを (おそらく作成された実行可能ファイルの前のインスタンスによって) 受け取ったのかということです。

ここに画像の説明を挿入

If the object existed prior to the CreateMutex call, then we call a helper function that sends some message to the existing instances and exits. Here is NotifyOtherInstance:

如果对象在CreateMutex调用之前就存在,那么我们调用一个助手函数,该函数会向现有实例发送一些消息并退出。以下是NotifyOtherInstance:

ここに画像の説明を挿入

The function searches for the existing window with the FindWindow function and uses the window caption as the search criteria. This is not ideal in the general case, but it’s good enough for this sample.

该函数使用FindWindow函数搜索现有窗口,并使用窗口标题作为搜索条件。这在一般情况下并不理想,但对于这个样本来说已经足够好了。

Once the window is located, we send a custom message to the window with the current process ID as an argument. This shows up in the dialog’s list box.

一旦找到窗口,我们就会向窗口发送一条自定义消息,并将当前进程ID作为参数。这将显示在对话框的列表框中。

パズルの最後のピースは、ダイアログによる WM_NOTIFY_INSTANCE メッセージの処理です。
WTL では、ウィンドウ メッセージはマクロを使用して関数にマップされます。MainDlg.h のダイアログ クラス (CMainDlg) のメッセージ マップがここで繰り返されます。

パズルの最後のピースは、ダイアログを介して WM_NOTIFY_INSTANCE メッセージを処理することです。
WTL では、ウィンドウ メッセージはマクロを使用して関数にマップされます。MainDlg.h のダイアログ クラス (CMainDlg) のメッセージ マップがここで繰り返されます。

ここに画像の説明を挿入

カスタム メッセージは OnNotifyInstance メンバー関数にマップされ、次のように実装されます。

カスタム メッセージは OnNotifyInstance メンバー関数にマップされ、実装方法は次のとおりです。

ここに画像の説明を挿入

プロセス ID が wParam パラメーターから抽出され、AddText ヘルパー関数を使用してリスト ボックスにテキストが追加されます。

プロセス ID は wParam パラメーターから抽出され、AddText ヘルパー関数を使用してリストボックスにテキストが追加されます。

ここに画像の説明を挿入

m_List は、Windows リスト ボックス コントロールの WTL ラッパーである CListBox 型です。

m_List は CListBox 型で、Windows リスト ボックス コントロールの WTL ラッパーです。

ハンドル

前のセクションで説明したように、ハンドルは、そのハンドルに関するいくつかの情報を保持するカーネル空間内の小さなデータ構造を間接的に指します。図 2-4 は、32 ビット システムと 64 ビット システムのデータ構造を示しています。

前のセクションで説明したように、ハンドルは、ハンドルに関する情報を含むカーネル空間内の小さなデータ構造を間接的に指します。図 2-4 は、32 ビット システムと 64 ビット システムのデータ構造を示しています。

ここに画像の説明を挿入

32 ビット システムでは、このハンドル エントリのサイズは 8 バイトで、64 ビット システムでは 16 バイトです (技術的には 12 バイトで十分ですが、調整の目的で 16 バイトに拡張されています)。各エントリには次の要素が含まれます。

32 ビット システムでは、このハンドル エントリのサイズは 8 バイトです (上図に示す 32 ビット システムのハンドル エントリ、1 行が 32 ビット、2 行の合計が 64 ビット、つまり 8 バイト) 、64 ビット システムでは 16 バイトです (上図の 64 ビット システムのハンドル エントリのように、技術的には 12 バイトで十分ですが、位置合わせの目的で 16 バイトに拡張されています)。各エントリには次のコンポーネントがあります。

• 実際のオブジェクトへのポインタ。下位ビットはフラグに使用され、アドレス調整によって CPU アクセス時間を改善するために使用されるため、オブジェクトのアドレスは 32 ビット システムでは 8 の倍数、64 ビット システムでは 16 の倍数になります。

• アクセス マスク。このハンドルで何ができるかを示します。つまり、アクセスマスクはハンドルの力です。

• 3 つのフラグ: 継承、クローズからの保護、およびクローズ時の監査 (後で説明します)。

実際のオブジェクトへのポインタ下位ビット (下位 3 ビット) はフラグに使用され、アドレス アライメントによって CPU アクセス時間が向上するため、オブジェクトのアドレスは 32 ビット システムでは 8 の倍数、64 ビット システムでは 16 の倍数になります。

このハンドルで何ができるかを示すアクセス マスク。つまり、アクセスマスクはハンドルの力です。

3 つのフラグ: 継承、guard-on-close、audit-on-close (後述)。

The access mask is a bitmask, where each “1” bit indicating a certain operation that can be carried using that handle. The access mask is set when the handle is created by creating an object or opening an existing object. If the object is created, then the caller typically has full access to the object. But if the object is opened, the caller needs to specify the required access mask, which it may or may not get.

访问掩码是一个位掩码,其中每个“1”位表示可以使用该句柄进行的特定操作。访问掩码在通过创建一个对象或打开一个现有对象来创建句柄时被设置。如果对象是创建的,那么调用者通常具有对该对象的完全访问权限。但是如果对象被打开,调用者需要指定所需的访问掩码,它可能得到也可能得不到。

たとえば、アプリケーションが特定のプロセスを終了したい場合、最初に OpenProcess 関数を呼び出して、(少なくとも) PROCESS_TERMINATE のアクセス マスクで必要なプロセスへのハンドルを取得する必要があります。そうでない場合、次のコマンドを使用してプロセスを終了する方法はありません。そのハンドル。呼び出しが成功すると、TerminateProcess の呼び出しも必ず成功します。

たとえば、アプリケーションがプロセスを終了したい場合、最初に OpenProcess 関数を呼び出して、少なくとも PROCESS_TERMINATE のアクセス マスクを持つ目的のプロセスへのハンドルを取得する必要があります。そうしないと、そのハンドルを使用してプロセスを終了できません。呼び出しが成功した場合は、TerminateProcess の呼び出しも成功する必要があります。

以下は、プロセス ID を指定してプロセスを終了する例です。

プロセス ID に基づいてプロセスを強制終了する例を次に示します。

ここに画像の説明を挿入

OpenProcess 関数には次のプロトタイプがあります。

OpenProcess 関数には次のプロトタイプがあります。

ここに画像の説明を挿入

これは Open 操作であり、問​​題のオブジェクトはすでに存在しているため、クライアントはオブジェクトにアクセスするために必要なアクセス マスクを指定する必要があります。アクセス マスクには、汎用と固有の 2 種類のアクセス ビットがあります。これらの詳細については、第 16 章 (「セキュリティ」) で説明します。プロセスの特定のアクセス ビットの 1 つは、上記の例で使用されている PROCESS_TERMINATE です。他のビットには、PROCESS_QUERY_INFORMATION、PROCESS_VM_OPERATION などが含まれます。完全なリストを確認するには、OpenProcess のドキュメントを参照してください。

これは Open 操作であるため、問題のオブジェクトはすでに存在しており、クライアントはオブジェクトへのアクセスに必要なアクセス マスクを指定する必要があります。アクセス マスクには、一般と特定の 2 種類のアクセス ビットがあります。これらの詳細については、第 16 章 (「セキュリティ」) で説明します。プロセス固有のアクセス ビットの 1 つは、上記の例で使用されている PROCESS_TERMINATE です。他のビットには、PROCESS_QUERY_INFORMATION、PROCESS_VM_OPERATION などが含まれます。完全なリストについては、OpenProcess のドキュメントを参照してください。

What access mask should be used by client code? Generally, it should reflect the operations the client code intends to perform with the object. Asking for more than needed may fail, and asking less is obviously not good enough.

客户端代码应该使用什么访问掩码?通常,它应该反映客户端代码打算对对象执行的操作。要求超过需要的可能会失败,要求少的显然不够好。

The flags associated with each handle are the following:

与每个句柄相关联的标志如下:

• Inheritance - this flag is used for handle inheritance - a mechanism that allows sharing an object between cooperating processes. We’ll discuss handle inheritance in chapter 3.

• Audit on close - this flag indicates whether an audit entry in the security log should be written when that handle is closed. This flag is rarely used and is off by default.

• 继承——这个标志用于句柄继承——一种允许在协作进程之间共享对象的机制。我们将在第 3 章讨论句柄继承。

• Audit on close——该标志指示当该句柄关闭时是否应在安全日志中写入审计条目。此标志很少使用,默认情况下处于关闭状态。

• 閉じないように保護 - このフラグを設定すると、ハンドルが閉じられなくなります。CloseHandle を呼び出すと FALSE が返され、GetLastError は ERROR_INVALID_HANLDLE (6) を返します。プロセスがデバッガーで実行されている場合、次のメッセージを含む例外が発生します:「0xC0000235: NtClose は、NtSetInformationObject によるクローズから保護されたハンドルで呼び出されました。」このフラグが役立つことはほとんどありません。

• 閉じないように保護 - このフラグを設定すると、ハンドルが閉じられなくなります。CloseHandle を呼び出すと FALSE が返され、GetLastError は ERROR_INVALID_HANLDLE (6) を返します。プロセスがデバッガーで実行されている場合は、次のメッセージを含む例外がスローされます:「0xC0000235: NtSetInformationObject によって閉じられないハンドルで NtClose が呼び出されました。」このフラグが役立つことはほとんどありません。
(NtClose は、NtSetInformationObject によって閉じられないように保護されたハンドルに対して呼び出されます。)

継承フラグと保護フラグの変更は、次のように定義された SetHandleInformation 関数で行うことができます。

継承フラグと保護フラグは、次のように SetHandleInformation 関数を使用して変更できます。

ここに画像の説明を挿入

最初のパラメータはハンドル自体です。2 番目のパラメータは、どのフラグを操作するかを示すビット マスクです。最後のパラメータは、これらのフラグの実際の値です。たとえば、ハンドルに「クローズから保護」ビットを設定するには、次のコードを使用できます。

最初のパラメータはハンドル自体です。2 番目の引数は、どのフラグを操作するかを示すビットマスクです。最後のパラメータは、これらのフラグの実際の値です。たとえば、ハンドルに「クローズから保護」ビットを設定するには、次のコードを使用できます。

ここに画像の説明を挿入

逆に、次のコード スニペットはこれと同じビットを削除します。

代わりに、次のコード スニペットはこのビットを削除します。

ここに画像の説明を挿入

これらのフラグを読み取る逆の関数も存在します。

これらのフラグを読み取るための逆の関数も存在します。

ここに画像の説明を挿入

特定のプロセスから開かれたハンドルは、Sysinternals の Process Explorer ツールを使用して表示できます。関心のあるプロセスに移動し、下部ペインが表示されていることを確認します ([表示] メニュー、[下部ペインを表示])。下部ペインには 2 つのビューのうちの 1 つが表示されます - ハンドル ビュー ([表示] メニュー、下部ペイン ビュー、ハンドル) に切り替えます。図 2-5 は、Explorer プロセスで開いているハンドルを示すツールのスクリーンショットです。デフォルトで表示される列は、タイプと名前のみです。ヘッダー領域を右クリックし、[列の選択] をクリックして次の列を追加しました: ハンドル、オブジェクト アドレス、アクセス、およびデコードされたアクセス。

Sysinternals の Process Explorer ツールを使用すると、特定のプロセスから開かれたハンドルを表示できます。関心のあるプロセスに移動し、下部ペインが表示されていることを確認します ([表示] メニュー、[下部ペインを表示])。下部ペインには 2 つのビューのうちの 1 つが表示されます - ハンドル ビュー ([表示] メニュー、下部ペイン ビュー、ハンドル) に切り替えます。図 2-5 は、Explorer プロセスで開いているハンドルを示すツールのスクリーンショットです。デフォルトで表示される列は、タイプと名前のみです。ヘッダー領域を右クリックし、[列の選択] をクリックして、次の列を追加しました: ハンドル、オブジェクト アドレス、アクセス、およびデコードされたアクセス。

ここに画像の説明を挿入

以下に列の簡単な説明を示します。

以下に列の簡単な説明を示します。

• ハンドル - これはハンドル値そのものであり、このプロセスにのみ関連します。同じハンドル値が異なる意味を持つ可能性があります。つまり、異なるオブジェクトを指したり、場合によっては空のインデックスを指すこともあります。

• Type - オブジェクトのタイプ名。これは、図 2-1 に示す WinObj の Object Types ディレクトリに対応します。

• ハンドル - これはハンドル値そのものであり、このプロセスにのみ関連付けられています。同じハンドル値が異なる意味を持つ可能性があります。つまり、異なるオブジェクト、場合によっては空のインデックスを指すこともあります。

• タイプ – オブジェクトのタイプ名。これは、図 2-1 に示す WinObj の Object Types ディレクトリに対応します。

オブジェクト アドレス - これは、実際のオブジェクト構造が存在するカーネル アドレスです。
これらのアドレスは、64 ビットでは 16 進数のゼロで終わることに注意してください (32 ビット システムでは、アドレスは「8」または「0」で終わります)。この情報を含むユーザー モード コードはありませんが、デバッグの目的で使用できます。オブジェクトへのハンドルが 2 つあり、それらが同じオブジェクトを指しているかどうかを知りたい場合は、オブジェクト アドレスを比較できます。それらが同じであれば、それは同じオブジェクトです。それ以外の場合、ハンドルは別のオブジェクトを指します。

オブジェクト アドレス - これは、実際のオブジェクト構造が存在するカーネル アドレスです。
これらのアドレスは、64 ビットでは 16 進数の 0 (32 ビット システムでは「8」または「0」) で終わることに注意してください。この情報にはユーザー モード コードは含まれていませんが、デバッグの目的には役立ちます。オブジェクトへのハンドルが 2 つあり、それらが同じオブジェクトを指しているかどうかを知りたい場合は、オブジェクト アドレスを比較できます。それらは同じであり、同じオブジェクトです。それ以外の場合、ハンドルは別のオブジェクトを指します。

• アクセス - これは、上で説明したアクセス マスクです。この 16 進値に格納されているビットを解釈するには、ドキュメントでアクセス マスク ビットを見つける必要があります。これを軽減するには、「デコードされたアクセス」列を使用します。

• デコードされたアクセス - 一般的なオブジェクト タイプのアクセス マスク ビットの文字列表現を提供します。これにより、ドキュメントを詳しく調べなくても、アクセス マスク ビットを解釈することが容易になります。

• アクセス - これは、上で説明したアクセス マスクです。この 16 進値に格納されているビットを解釈するには、ドキュメントでアクセス マスク ビットを見つける必要があります。これを軽減するには (そのマスクの意味を説明するため)、decode を使用して列にアクセスします

• デコードされたアクセス – 一般的なオブジェクト タイプのアクセス マスク ビットの文字列表現を提供します。これにより、ドキュメントを深く掘り下げることなく、アクセス マスク ビットを簡単に説明できるようになります。

Process Explorer のハンドル ビューには、デフォルトで名前付きオブジェクトのハンドルのみが表示されます。すべてのハンドルを表示するには、「表示」メニューから「名前のないハンドルとマッピングを表示」オプションを有効にします。図 2-6 は、このオプションをオンにしたときにビューがどのように変化するかを示しています。

Process Explorer のハンドル ビューには、デフォルトで名前付きオブジェクトのハンドルのみが表示されます。すべてのハンドルを表示するには、[表示] メニューの [名前のないハンドルとマッピングを表示] オプションを有効にします。図 2-6 は、このオプションを選択したときにビューがどのように変化するかを示しています。

ここに画像の説明を挿入

「名前」という用語は思っているよりも難しいです。Process Explorer が名前付きオブジェクトと見なすものは、必ずしも実際の名前であるとは限りませんが、場合によっては便利な名前である場合もあります。たとえば、プロセスとスレッドに文字列ベースの名前を付けることはできませんが、プロセスとスレッドのハンドルは図 2-5 に示されています。名前ではない「名前」を持つオブジェクト タイプは他にもあります。最も混乱を招くのはファイルとキーです。この「奇妙さ」については、この章の後半の「オブジェクト名」セクションで説明します。

「名前」という用語は、見た目よりも複雑です。Process Explorer は、名前付きオブジェクトを必ずしも実際の名前ではなく、場合によっては便利な名前とみなします。たとえば、プロセスとスレッドのハンドルは図 2-5 に示されていますが、プロセスとスレッドは文字列ベースの名前を持つことができません。「名前」が名前ではないオブジェクト タイプもいくつかありますが、最も混乱を招くのはファイルとキーです。この「癖」については、この章の「オブジェクト名」セクションで後ほど説明します。

プロセスのハンドル テーブル内のハンドルの合計数は、プロセス エクスプローラーとタスク マネージャーの列として利用できます。図 2-7 は、タスク マネージャーに追加されたこの列を示しています。

プロセス ハンドル テーブルのハンドルの合計数は、プロセス エクスプローラーとタスク マネージャーの列として表示されます。図 2-7 は、タスク マネージャーに追加されたこの列を示しています。

ここに画像の説明を挿入

表示されている数値はオブジェクト数ではなくハンドル数であることに注意してください。これは、同じオブジェクトを参照するハンドルが複数存在する可能性があるためです。

表示されている数値はハンドル数であり、オブジェクト数ではないことに注意してください。これは、同じオブジェクトを参照する複数のハンドルが存在する可能性があるためです。

Process Explorer でハンドル エントリをダブルクリックすると、オブジェクト (ハンドルではない) のいくつかのプロパティを表示するダイアログが開きます。図 2-8 は、このようなダイアログのスクリーンショットです。

Process Explorer でハンドル エントリをダブルクリックすると、オブジェクト (ハンドルではない) のいくつかのプロパティを示すダイアログが開きます。図 2-8 は、このようなダイアログのスクリーンショットです。

ここに画像の説明を挿入

The basic object information is repeated from the handle entry (name, type, and address).
This particular object (a mutex) has 3 open handles. The references number is misleading and does not reflect the actual object reference count. For some types of objects (such as mutexes), extra information is shown. In this particular case, it’s whether the mutex is currently held and whether it’s abandoned. (we’ll discuss mutexes in detail in chapter 8).

对象的基本信息从句柄条目中重复出来(名称、类型和地址)。
这个特定的对象(一个mutex)有3个打开的句柄。引用数具有误导性,并不反映实际的对象引用数。对于某些类型的对象(如互斥),会显示额外的信息。在这个特殊的例子中,它是指互斥体当前是否被持有以及是否被放弃。(我们将在第8章详细讨论互斥)。

To get a sense of the number of objects and handles in the system at a given moment, you can run the KernelObjectView tool from my Github repository at https://github.com/zodiacon/ AllTools. Figure 2-9 shows a screenshot of the tool. The total number of objects (per object type) is shown along with the total number of handles. You can sort by any column; which object types have the most objects? The most handles?

要了解给定时刻系统中对象和句柄的数量,可以从我在https://github.com/zodiacon/ AllTools的Github库中运行KernelObjectView工具。图2-9显示了该工具的屏幕截图。对象总数(每个对象类型)与句柄总数一起显示。您可以按任何列排序;哪些对象类型拥有最多的对象?最多的句柄?

ここに画像の説明を挿入

Pseudo Handles

Some handles have special values and are not closable. These are known as pseudo-handles, although they are used just like any other handle when needed. Calling CloseHandle on pseudo-handles always fails. Here are the functions returning pseudo-handles:

一些句柄具有特殊值并且不可关闭。这些被称为伪句柄,尽管它们在需要时可以像其他句柄一样使用。在伪句柄上调用 CloseHandle 总是失败。以下是返回伪句柄的函数:

• GetCurrentProcess (-1) - return a pseudo-handle to the calling process
• GetCurrentThread (-2) - return a pseudo-handle to the calling thread
• GetCurrentProcessToken (-4) - return a pseudo-handle to the token of the calling process
• GetCurrentThreadToken (-5) - return a pseudo-handle to the token of the calling thread
• GetCurrentThreadEffectiveToken (-6) - return a pseudo-handle to the effective token of the calling thread (if the thread has its own token - it’s used, otherwise its process token is used)

• GetCurrentProcess (-1) - 呼び出しプロセスに疑似ハンドルを返します。
• GetCurrentThread (-2) - 呼び出しスレッドに疑似ハンドルを返します。
• GetCurrentProcessToken (-4) - 呼び出しプロセス トークンに疑似ハンドルを返します。
• GetCurrentThreadToken (- 5) - 呼び出しスレッドのトークンへの疑似ハンドルを返します
GetCurrentThreadEffectiveToken (-6) - 呼び出しスレッドの有効なトークンへの疑似ハンドルを返します (スレッドに独自のトークンがある場合はそれを使用し、そうでない場合はそのプロセス トークンを使用します)。

最後の 3 つの疑似ハンドル (トークン ハンドル) は Windows 8 以降でのみサポートされており、それらのアクセス マスクは TOKEN_QUERY と TOKEN_QUERY_SOURCE のみです。

最後の 3 つの疑似ハンドル (トークン ハンドル) は Windows 8 以降でのみサポートされており、それらのアクセス マスクは TOKEN_QUERY と TOKEN_QUERY_SOURCE のみです。

ハンドル用RAII

ハンドルが不要になったら、ハンドルを閉じることが重要です。これを適切に実行できないアプリケーションでは、「ハンドル リーク」が発生する可能性があります。つまり、アプリケーションがハンドルを開いたにもかかわらずハンドルを閉じるのを「忘れた」場合、ハンドルの数が制御不能に増加します。明らかに、これは悪いことです。

ハンドルが不要になったら、ハンドルを閉じることが重要です。これを適切に実行できないアプリケーションでは、「ハンドル リーク」が発生する可能性があります。アプリケーションがハンドルを開いたものの、ハンドルを閉じるのを「忘れた」場合、ハンドルの数が制御不能に増加する可能性があります。明らかに、これは悪いことです。

ハンドルを閉じ忘れることなくコードでハンドルを管理できるようにする 1 つの方法は、C++ を使用して、リソース取得は初期化 (RAII) と呼ばれるよく知られた慣用句を実装することです。名前はあまり良くありませんが、熟語はそうです。このアイデアは、ラッパー オブジェクトが破棄されたときにハンドルが確実に閉じられるように、型でラップされたハンドルにデストラクターを使用することです。

ハンドルを閉じ忘れることなくコードでハンドルを管理できるようにする 1 つの方法は、C++ を使用して、リソース取得は初期化 (RAII) と呼ばれるよく知られた慣用句を実装することです。名前は良くないですが、熟語は良いです。このアイデアは、型でラップされたハンドルにデストラクターを使用して、ラップされたオブジェクトが破棄されるときにハンドルが確実に閉じられるようにすることです。

以下は、ハンドルの単純な RAII ラッパーです (便宜上インライン実装されています)。

以下は、ハンドル用の単純な RAII ラッパーです (便宜上インライン実装されています)。

ここに画像の説明を挿入

ここに画像の説明を挿入

The Handle type provides the basic operations expected from a RAII HANDLE wrapper. The copy constructor and copy assignment operators are removed, as copying a handle that may have multiple owners does not make sense (causing CloseHandle to be called twice for the same handle). It is possible to implement these copy operations by duplicating the handle (see “Sharing Kernel Objects” later in this chapter), but it’s a non-trivial operation best avoided in implicit copy scenarios. A bool operator returns true if the current handle held is valid; it considers zero and INVALID_HANDLE_VALUE (-1) as invalid handles. The Close function closes the handle and is normally called from the destructor. Finally, the Get function returns the underlying handle.

Handle类型提供了RAII HANDLE包装器所期望的基本操作。复制构造函数(copy constructor)和复制赋值操作符(copy assignment operators)被移除,因为复制一个可能有多个所有者的句柄是没有意义的(导致对同一个句柄调用CloseHandle两次)。可以通过复制句柄(duplicating the handle)来实现这些复制操作(these copy operations)(见本章后面的 “共享内核对象”),但这是一个非平凡(非常重要)的操作,最好在隐式复制场景中避免。如果当前持有的句柄是有效的,则bool操作符返回true;它认为0和INVALID_HANDLE_VALUE(-1)是无效的句柄。Close函数关闭句柄,通常由析构函数调用。最后,Get函数返回底层句柄。

It’s possible to add an implicit conversion operator to HANDLE, removing the need to call Get.

可以添加一个隐式转换操作符来处理,而不需要调用Get。

Here is some example code using the above wrapper:

下面是使用上述包装器的一些示例代码:

ここに画像の説明を挿入

Although writing such a RAII wrapper is possible, it’s usually best to use an existing library that provides this (and other similar) functionality. For example, although CloseHandle is the most common closing handle function, there are other types of handles that require a different closing function. One such library that is used by Microsoft in Windows code is the Windows Implementation Library (WIL). This library has been released on Github and is available as a Nuget package.

虽然编写这样一个RAII包装器是可能的,但通常最好使用提供这种(和其他类似)功能的现有库。例如,虽然CloseHandle是最常见的关闭句柄函数,但还有其他类型的句柄需要不同的关闭函数。微软在Windows代码中使用的一个这样的库是Windows实现库(WIL)。这个库已经发布在Github上,可以作为一个Nuget包获得。

Using WIL

プロジェクトへの WIL の追加は、他の Nuget パッケージと同様に行われます。Visual Studio プロジェクトの References ノードを右クリックし、[Nuget パッケージの管理…] を選択します。[参照] タブの検索テキスト ボックスに「wil」と入力すると、WIL をすばやく検索できます。パッケージの完全な名前は、図 2-10 に示すように「Microsoft.Windows.ImplementationLibrary」です。

プロジェクトへの WIL の追加は、他の Nuget パッケージと同様です。Visual Studio プロジェクトの References ノードを右クリックし、[Nuget パッケージの管理...] を選択します。 [参照] タブの検索テキスト ボックスに「wil」と入力すると、WIL をすばやく検索できます。図 2-10 に示すように、パッケージの完全な名前は「Microsoft.Windows.ImplementationLibrary」です。

ここに画像の説明を挿入

RAII ハンドル ラッパーはヘッダー ファイルにあります。
WIL を使用した同じコードを次に示します。

RAII ハンドル ラッパーは、<wil\resource.h> ヘッダー ファイルにあります。
WIL を使用した同じコードを次に示します。

ここに画像の説明を挿入

wil::unique_handle は、破棄時に CloseHandle を呼び出す HANDLE ラッパーです。これは主に C++ std::unique_ptr<> 型に基づいてモデル化されています。内部ハンドルの取得は get() を呼び出すことで行われることに注意してください。unique_handle 内の値を置き換える (そして古い値を閉じる) には、reset 関数を使用します。引数なしでリセットを呼び出すと、基になるハンドルが閉じられるだけで、ラッパー オブジェクトが空のシェルになります。

wil::unique_handle は、破棄時に CloseHandle を呼び出す HANDLE ラッパーです。これは主に C++ std::unique_ptr<> 型を模倣しています。内部ハンドルの取得は get() を呼び出すことで行われることに注意してください。unique_handle の値を置き換える (古い値を閉じる) には、reset 関数を使用します。引数なしでリセットを呼び出すと、基礎となるハンドルが閉じられるだけで、ラッパー オブジェクトが空のシェルになります。

このコードは、 using namespace wil; を追加することで多少簡略化できます。そのため、WIL のすべての型の前に wil:: を追加する必要はありません。また、場合によっては auto を使用してコードを簡素化できることにも注意してください。

通过添加 using namespace wil 可以稍微简化代码;所以 wil:: 不需要为 WIL 中的每种类型添加前缀。另外,注意在某些情况下,auto可以用来简化代码。

The code samples in this book use WIL in some cases, but not all. From a learning perspective, it’s sometimes better to use the raw types to make things simpler to understand.

本书中的代码示例在某些情况下使用 WIL,但并非全部。从学习的角度来看,有时最好使用原始类型来使事情更容易理解。

Creating Objects

ここに画像の説明を挿入

ここに画像の説明を挿入

Object Names

The name provided to a Create function is not the final name of the object. In classic (desktop) processes, it’s prepended with \Sessions\x\BaseNamedObjects\ where x is the session ID of the caller. If the session is zero, the name is prepended with \BaseNamedObjects\ only. If the caller happens to be running in an AppContainer (typically a Universal Windows Platform process), then the prepended string is more complex and consists of the unique AppContainer SID: \Sessions\x\AppContainerNamedObjects.

図 2-11 は、WinObj のセッション 1 の名前付きオブジェクトを示しています。

Create 関数に指定された名前は、オブジェクトの最終的な名前ではありません。従来の (デスクトップ) プロセスでは、\Sessions\x\BaseNamedObjects\ というプレフィックスが付けられます。ここで、x は呼び出し元のセッション ID です。セッションが 0 の場合、名前には単に \BaseNamedObjects\ が接頭辞として付けられます。呼び出し元が AppContainer (通常はユニバーサル Windows プラットフォーム プロセス) で実行されている場合、プレフィックス文字列はより複雑になり、一意の AppContainer SID: \Sessions\x\AppContainerNamedObjects\ が含まれます。

図 2-11 は、WinObj のセッション 1 の名前付きオブジェクトを示しています。

上記のすべての意味は、オブジェクト名がセッション相対 (AppContainer の場合はパッケージ相対) であるということです。オブジェクトをセッション間で共有する必要がある場合は、オブジェクト名の前に Global を付加することで、セッション 0 でオブジェクトを作成できます。たとえば、CreateMutex 関数を使用して Global\MyMutex という名前のミューテックスを作成すると、\BaseNamedObjects の下にミューテックスが作成されます。
AppContainers にはセッション 0 のオブジェクト名前空間を使用する権限がないことに注意してください。

上記のすべては、オブジェクトの名前がセッションに対して相対的であることを意味します (AppContainer の場合はパッケージに対して相対的です)。オブジェクトを異なるセッション間で共有する必要がある場合は、オブジェクト名に Global\ という接頭辞を付けることで、セッション 0 でオブジェクトを作成できます。たとえば、 CreateMutex 関数を使用して Global\
MyMutex という名前のミューテックスを作成すると、そのオブジェクトは \BaseNamedObjects の下に作成されます。
AppContainer はセッション 0 オブジェクト名前空間にアクセスできないことに注意してください。

オブジェクト マネージャーの名前空間階層全体を WinObj で表示できます。この構造全体はメモリに保持され、必要に応じてオブジェクト マネージャーによって操作されます。名前のないオブジェクトはこの構造の一部ではないことに注意してください。つまり、WinObj で表示されるオブジェクトはすべての既存のオブジェクトを構成するのではなく、名前付きで作成されたすべてのオブジェクトを構成します。

オブジェクト マネージャーの名前空間階層全体は、WinObj を使用して表示できます。この構造全体はメモリ内に保持され、オブジェクト マネージャーによって必要に応じて操作されます。名前のないオブジェクトはこの構造の一部ではないことに注意してください。つまり、WinObj で表示されるオブジェクトには、既存のすべてのオブジェクトが含まれるのではなく、その名前で作成されたすべてのオブジェクトが含まれます。

WinObj に表示される「ディレクトリ」は、実際にはディレクトリ オブジェクトであり、論理コンテナとして機能するカーネル オブジェクトの一種にすぎません。

WinObj に表示される「ディレクトリ」は実際にはディレクトリ オブジェクトであり、論理コンテナとして機能するカーネル オブジェクトの一種にすぎません。

Process Explorer のハンドル ビューに戻ると、デフォルトで「名前付き」オブジェクトが表示されます。

ここでの「名前付き」とは、名前を付けることができるオブジェクトだけでなく、その他のオブジェクトも含みます。名前を付けることができるオブジェクトは、ミューテックス (ミュータント)、セマフォ、イベント、セクション、ALPC ポート、ジョブ、タイマー、およびその他のあまり使用されていないオブジェクト タイプです。さらに、実際の名前付きオブジェクトとは異なる意味を持つ名前を持つオブジェクトも表示されます。

Process Explorer のハンドル ビューに戻ります。デフォルトでは、「名前付き」オブジェクトが表示されます。

ここでの「ネーミング」とは、名前を付けることができるオブジェクトだけでなく、他のオブジェクトも指します。名前を付けることができるオブジェクトには、ミューテックス (ミューテーション)、セマフォ、イベント、セクション、ALPC ポート、ジョブ、タイマー、およびその他のあまり使用されないオブジェクト タイプが含まれます。実際の名前付きオブジェクトとは異なる意味を持つ表示名もいくつかあります。

• プロセス オブジェクトとスレッド オブジェクト - 名前は一意の ID として表示されます。

• プロセスおよびスレッド オブジェクト - 名前は一意の ID として表示されます。

• For File objects it shows the file name (or device name) pointed to by the file object.
It’s not the same as an object’s name, as there is no way to get a handle to a file object given the file name - only a new file object may be created that accesses the same underlying file or device (assuming sharing settings for the original file object allow it).

•对于文件对象,它显示文件对象指向的文件名(或设备名)。
它与对象的名称不同,因为在给定文件名的情况下,无法获得文件对象的句柄——只能创建一个访问相同底层文件或设备的新文件对象(假设原始文件对象的共享设置允许)。

• (Registry) Key object names are shown with the path to the registry key. This is not a name, for the same reasoning as for file objects.

•(注册表)注册表项对象名称与注册表项的路径一起显示。这不是一个名称,原因与文件对象相同。

• Directory objects show its logical path, rather than being a true object name. A Directory is not a file system object, but rather an object manager directory.

• ディレクトリ オブジェクトには、実際のオブジェクト名ではなく論理パスが表示されます。ディレクトリはファイル システム オブジェクトではなく、オブジェクト マネージャー ディレクトリです。

• トークン オブジェクト名は、トークンに格納されているユーザー名とともに表示されます。

• トークン オブジェクト名は、トークンに保存されているユーザー名とともに表示されます。

カーネルオブジェクトの共有

これまで見てきたように、カーネル オブジェクトへのハンドルはプロセスに対してプライベートです。場合によっては、プロセスがカーネル オブジェクトを別のプロセスと共有したい場合があります。このようなプロセスは、他のプロセスのハンドル テーブルでハンドル値が別のオブジェクトを指しているか空である可能性があるため、何らかの方法でハンドルの値を他のプロセスに単純に渡すことはできません。

このような共有を可能にするためには、何らかのメカニズムを導入する必要があることは明らかです。実際には、次の 3 つがあります。

これまで見てきたように、カーネル オブジェクトへのハンドルはプロセスにとってプライベートです。場合によっては、プロセスがカーネル オブジェクトを別のプロセスと共有したい場合があります。このようなプロセスは、ハンドル値が別のオブジェクトを指しているか、他のプロセスのハンドル テーブルで空である可能性があるため、何らかの方法でハンドルの値を他のプロセスに単純に渡すことはできません。

この共有を可能にするためには、何らかのメカニズムを導入する必要があることは明らかです。実際には、
• 名前による共有
• ハンドル継承による共有
• ハンドルの複製による共有の 3 つがあります。

名前による共有

利用可能な場合、これが最も簡単なオプションです。ここでの「利用可能」とは、問題のオブジェクトが名前を持つことができ、実際に名前を持つことを意味します。一般的なシナリオは、連携するプロセス (2 つ以上) が同じオブジェクト名で適切な Create 関数を呼び出すというものです。呼び出しを行う最初のプロセスがオブジェクトを作成し、他のプロセスからの後続の呼び出しにより、同じオブジェクトへの追加のハンドルが開かれます。

サンプル BasicSharing は、Memory Mapped File オブジェクトとの名前による共有の使用例を示しています。このオブジェクトは、プロセス間でメモリを共有するために使用できます (通常、各プロセスは自分のアドレス空間のみを参照できます)。アプリケーションの 2 つ (またはそれ以上) のインスタンス (図 2-13 を参照) を実行すると、これらのプロセス間でテキスト データを共有できます。

利用可能な場合は、これが最も簡単なオプションです。ここでの「利用可能」とは、問題のオブジェクトが名前を持つことができ、実際に名前を持つことを意味します。一般的なシナリオは、連携するプロセス (2 つ以上) が同じオブジェクト名で対応する Create 関数を呼び出すことです。最初に呼び出したプロセスによってオブジェクトが作成され、他のプロセスによる後続の呼び出しによって同じオブジェクトへの追加のハンドルが開かれます。

BasicSharing の例では、メモリ マップト ファイル オブジェクトの名前を使用した共有の例を示します。このオブジェクトは、プロセス間でメモリを共有するために使用できます (通常、各プロセスは自身のアドレス空間のみを認識します)。実行中のアプリケーションの 2 つ (またはそれ以上) のインスタンス (図 2-13 を参照) は、これらのプロセス間でテキスト データを共有できます。

ここに画像の説明を挿入

テストするには、編集ボックスに何かを入力し、[書き込み] をクリックします。次に、別のインスタンスに切り替えて、「読み取り」をクリックするだけです。入力したテキストは、他のアプリケーションの編集ボックスに表示されるはずです。
もちろん役割を交換することも可能です。別のインスタンスを起動する場合は、「Read」をクリックすると、最後のテキストも表示されます。これは、これらすべてのプロセスが同じ (共有) メモリに対して読み取りと書き込みを行っているためです。

テストするには、編集ボックスに何かを入力し、[書き込み] をクリックします。次に、別のインスタンスに切り替えて、「読み取り」をクリックするだけです。入力したテキストは、他のアプリケーションの編集ボックスに表示されるはずです。
もちろん、役割を交換することもできます。別のインスタンスを開始する場合は、「読み取り」をクリックすると、最後のテキストも表示されます。これは、これらすべてのプロセスが同じ (共有) メモリ (つまり、メモリ マップされたファイル オブジェクト) に対して読み書きを行っているためです。

ちなみに、これらは同じ実行可能ファイルに基づくプロセスである必要はありません。これはここでは便宜上使用されているだけです。決定要因はオブジェクトの名前です。

ちなみに、これらは必ずしも同じ実行可能ファイルに基づくプロセスであるとは限りません。これは単に便宜上のものです。決定要因はオブジェクトの名前です。

コードを見る前に、これが Process Explorer でどのように見えるかを見てみましょう。実行可能ファイルの 2 つのインスタンスを実行し、Process Explorer を開いて 2 つのプロセスを見つけます。下部ペインに (DLL ではなく) ハンドルが表示されていることを確認します。探すオブジェクト タイプはセクション (メモリ マップド ファイルのカーネル名) です。図 2-14 に示すように、「MySharedMemory」というセクションを見つけます (もちろんセッションベースのプレフィックスが付きます)。

コードを見る前に、Process Explorer でコードがどのように見えるかを見てみましょう。実行可能ファイルの 2 つのインスタンスを実行し、Process Explorer を開いて 2 つのプロセスを見つけます。下部ペインに (DLL ではなく) ハンドルが表示されていることを確認します。検索するオブジェクトのタイプはセクション (メモリ マップト ファイルのカーネル名) です。図 2-14 に示すように、「MySharedMemory」というセクションを見つけます (もちろんセッションベースのプレフィックスが付いています)。

ここに画像の説明を挿入

If you double-click the handle, you should see the properties of the section object as shown in figure 2-15.

如果双击句柄,应该会看到section对象的属性,如图2-15所示。

ここに画像の説明を挿入

Notice there are two open handles to the object. Presumably, these are coming from the two processes holding handles to that object. Notice the shared memory’s size: 4 KB - we’ll see this reflected in the code.

请注意,该对象有两个打开的句柄。据推测,这些来自持有该对象句柄的两个进程。请注意共享内存的大小:4KB——我们将在代码中看到这一点。

このオブジェクトを使用して 2 番目のプロセスを見つけると (図 2-16 を参照)、ハンドルをダブルクリックしたときに表示されるのと同じ情報が表示されるはずです。これらが同じオブジェクトを指していることをどうやって確認できるでしょうか? 「オブジェクトアドレス」列を見てください。アドレスが同一であれば、これは同じオブジェクトです (逆も同様です)。ハンドル値が同じではないことにも注意してください (通常の場合)。図 2-14 ではハンドル値は 0x14c (PID 22384)、図 2-16 では 0x16c (PID 27864) です。それでも、それらはまったく同じオブジェクトを参照します。

このオブジェクトを使用して 2 番目のプロセスを見つける場合 (図 2-16 を参照)、ハンドルをダブルクリックすると同じ情報が表示されます。これらが同じオブジェクトを指していることをどうやって確認できるでしょうか? [オブジェクト アドレス] 列を確認します。アドレスが同じであれば、これは同じオブジェクトです (逆も同様です)。また、ハンドル値は(通常は)同じではないことに注意してください。図 2-14 では、ハンドル値は 0x14c (PID 22384) であり、図 2-16 では、ハンドル値は 0x16c (PID 27864) です。それにもかかわらず、これらはまったく同じオブジェクトを参照します。

ここに画像の説明を挿入

インスタンスの 1 つを閉じたらどうなりますか? 1 つのハンドルが閉じますが、オブジェクトは生きたままになります。つまり、まったく新しいインスタンスを起動して「読み取り」をクリックすると、最新のテキストが表示されます。連携しているアプリケーションをすべて閉じてから、1 つのインスタンスを再度起動するとどうなるでしょうか。「Read」をクリックすると何が表示されるでしょうか? なぜそうなるのか自分に説明してみてください。

インスタンスの 1 つをシャットダウンするとどうなりますか? ハンドルは閉じますが、オブジェクトはアクティブのままです。つまり、新しいインスタンスを開始し、[読み取り] をクリックすると最新のテキストが表示されます。連携しているアプリケーションをすべて閉じて、インスタンスを再度起動するとどうなるでしょうか。「読む」をクリックすると何が表示されるでしょうか? なぜそうなるのか自分自身に説明してみてください。

次に、コードに注目してみましょう。

次に、コードに注目してみましょう。

BasicApplication は、WTL ダイアログベースのプロジェクトです。ダイアログ ボックス クラス (CMainDlg) は、メモリ マップト ファイルへのハンドルである単一の対象メンバーを保持します。

BasicApplication は、WTL ダイアログ ベースのプロジェクトです。ダイアログ クラス (CMainDlg) には、メモリ マップト ファイルへのハンドルである対象のメンバーが 1 つ含まれています。

ここに画像の説明を挿入

ダイアログが作成されると、WM_INITDIALOG メッセージ ハンドラーでファイル マッピング オブジェクトを作成し、それに名前を付けます。

ダイアログが作成されると、WM_INITDIALOG メッセージ ハンドラーでファイル マッピング オブジェクトを作成し、名前を付けます。

ここに画像の説明を挿入

CreateFileMapping は、ファイル マッピング オブジェクトを作成する (または開く) ために使用されます。パラメータの正確な詳細については、第 14 章 (パート 2) で説明します。ここでは、特に 1 つのパラメータ (最後のパラメータ)、つまりオブジェクトの名前に注目します。これは、Process Explorer で確認した名前です (標準のセッション関連のプレフィックスが付いています)。これがオブジェクトの作成を試みる最初のプロセスである場合、オブジェクトは作成されます。後続の呼び出しでは、同じオブジェクトへの追加のハンドルが生成されます (GetLastError を呼び出すと ERROR_ALREADY_EXISTS が返されます)。この場合、この呼び出しが最初であるかどうかは関係ありません。同じカーネル オブジェクトへのハンドルが必要なので、その「関数」が複数のプロセスから利用できるようにするだけです。

CreateFileMapping は、ファイル マッピング オブジェクトを作成する (または開く) ために使用されます。第 14 章 (パート 2) では、パラメータの正確な詳細について説明します。ここでは、1 つのパラメータ (最後のパラメータ)、つまりオブジェクトの名前に特に注目します。これは、Process Explorer に表示される名前です (標準のセッション相対プレフィックスが付いています)。これがオブジェクトを作成しようとする最初のプロセスである場合、オブジェクトは作成されます。後続の呼び出しでは、同じオブジェクトへの追加のハンドルが生成されます (GetLastError を呼び出すと ERROR_ALREADY_EXISTS が返されます)。この場合、この呼び出しが最初の呼び出しであるかどうかは気にしません。同じカーネル オブジェクトへのハンドルが必要なだけで、その「関数」を複数のプロセスから使用できるようになります。

最後から 2 番目の引数ペア (0 と 1 << 12) は、共有メモリのサイズを 64 ビット値として決定します。この場合、4 KB (1 << 12) に設定されています。何らかの理由で呼び出しが失敗した場合は、単純なメッセージを出力してダイアログを閉じ、プロセス自体を終了します。

最後から 2 番目のパラメータ ペア (0 および 1<<12) は、共有メモリのサイズを 64 ビット値として決定します (これら 2 つのパラメータは、メモリ マップト ファイルの最大サイズをシステムに伝えます) (サポートされている最大ファイル サイズは、 Windows は 64 ビット整数表現で使用できるため、ここでは 2 つの 32 ビット値を使用する必要があります)。この場合、4 KB (1<<12) (つまり、2 の 12 乗) に設定されます。何らかの理由で呼び出しが失敗した場合は、単純なメッセージを出力してダイアログを閉じ、プロセス自体を終了します。

ダイアログを閉じるときは、ハンドルを閉じることをお勧めします。厳密に言えば、この特定のケースではこれを行う必要はありません。ダイアログが閉じられるとプロセスは終了し、終了したプロセスのすべてのハンドルが適切に閉じられるようにカーネルが保証するからです。
それでも、これは良い習慣です (ハンドルの RAII ラッパーがそれを実行しない限り)。
完全を期すために、ダイアログの WM_DESTROY メッセージを処理するときにハンドルを閉じる呼び出しを次に示します。

ダイアログを閉じるときはハンドルを閉じることをお勧めします。厳密に言えば、この特定のケースではこれを行う必要はありません。ダイアログが閉じられるとプロセスは終了し、終了したプロセスのすべてのハンドルが適切に閉じられていることをカーネルが確認するからです。それでも、これは良い習慣です (ハンドルの RAII ラッパーが自動的に実行してくれる場合を除く)。
完全を期すために、ダイアログの WM_DESTROY メッセージを処理するときにハンドルを閉じる呼び出しを次に示します。

ここに画像の説明を挿入

次に、書き込み部分と読み取り部分です。共有メモリへのアクセスは、MapViewOfFile を呼び出すことによって行われ、その結果、共有メモリへのポインタが生成されます (正確な詳細は第 12 章にあります)。あとは、マップされたメモリにテキストをコピーするだけです。

次に、書き込みと読み取りの部分です。共有メモリへのアクセスは、MapViewOfFile を呼び出すことで実行され、共有メモリへのポインタが生成されます (詳細については、第 12 章を参照)。次に、テキストをマップされたメモリにコピーするだけです。

ここに画像の説明を挿入

コピーは、マップされたメモリへの wcscpy_s を使用して行われます。次に、UnmapViewOfFile を使用してメモリのマッピングを解除します。

データの読み取りも非常に似ています。アクセス マスクが FILE_MAP_WRITE ではなく FILE_MAP_READ に変更され、メモリが逆方向に編集ボックスに直接コピーされます。

wcscpy_s をマップされたメモリにコピーします。次に、UnmapViewOfFile を使用してメモリのマップを解除します。

データの読み取りも非常に似ています。アクセス マスクは FILE_MAP_WRITE ではなく FILE_MAP_READ に変更され、メモリは反対方向から編集ボックスに直接コピーされます。

ここに画像の説明を挿入

ハンドル複製による共有

Sharing kernel objects by name is certainly simple. What about objects that don’t (or can’t have a name)? Handle duplication may be the answer. Handle duplication has no inherent restrictions (except security) - it can work on almost any kernel object, named or unnamed and it works at any point in time (in chapter 3 we’ll see that handle inheritance is only available when a process creates a child process). There is a dent, however; this is the most difficult way of sharing in practice, as we shall soon see.

按名称共享内核对象当然很简单。那么,那些没有(或不能有名字的)的对象怎么办?处理复制(Handle duplication)可能就是答案。句柄复制没有固有的限制(除了安全性)-它几乎可以在任何内核对象上工作,不管是有名字的还是没有名字的,而且它在任何时间点上都可以工作(在第3章中,我们将看到句柄继承仅在进程创建子进程时可用)。然而,有一个凹痕(缺陷);这是实践中最困难的分享方式,我们很快就会看到。

A Duplicated I/O completion port handle does not work in the target process.

重复的I/O完成端口句柄在目标进程中不起作用。

ここに画像の説明を挿入

ハンドルを複製するには、ソース プロセス、ソース ハンドル、およびターゲット プロセスが必要です。成功すると、新しいハンドル エントリがターゲット プロセス ハンドル テーブルに書き込まれ、ソース ハンドルと同じオブジェクトを指します。重複の「前」と「後」をそれぞれ図 2-17 と 2-18 に示します。

コピー ハンドルには、ソース プロセス、ソース ハンドル、およびターゲット プロセスが必要です。成功すると、新しいハンドル エントリがターゲット プロセス ハンドル テーブルに書き込まれ、ソース ハンドルと同じオブジェクトを指します。複製の「前」と「後」をそれぞれ図 2-17 と 2-18 に示します。

ここに画像の説明を挿入

ここに画像の説明を挿入

技術的には、DuplicateHandle は適切なハンドルを取得できる任意の 2 つのプロセスで動作しますが、一般的なシナリオは、呼び出し元のハンドルの 1 つを別のプロセスのハンドル テーブルに複製することです。また、ソースプロセスとターゲットプロセスが同じである場合もあります。DuplicateHandle のパラメーターを詳しく見てみましょう。

技術的には、DuplicateHandle は適切なハンドルを取得できる任意の 2 つのプロセスで動作しますが、一般的な状況では、呼び出し元の 1 つのハンドルをもう 1 つのプロセスのハンドル テーブルにコピーします。また、ソースプロセスとターゲットプロセスが同じであっても構いません。DuplicateHandle のパラメータを詳しく理解してみましょう。

• hSourceProcessHandle - これはソース プロセスへのハンドルです。このハンドルには PROCESS_DUP_HANDLE アクセス マスクが必要です。ソースが呼び出し元のプロセスの場合は、GetCurrentProcess を渡すとうまくいきます (そして常に完全なアクセス権を持ちます)。

• hSourceHandle - 複製するソース ハンドル。このハンドルは、ソース プロセスのコンテキストで有効である必要があります。

• hTargetProcessHandle - ターゲット プロセス ハンドル。通常、このようなハンドルを取得するには、OpenProcess への呼び出しを使用する必要があります。ソースプロセスと同様に、PROCESS_DUP_HANDLE アクセスマスクが必要です。

• lpTargetHandle - これは、ターゲット プロセスの観点から有効な結果のハンドルです。図 2-18 では、呼び出し元に返されたハンドルは 72 でした。この値はプロセス B に関するものです (呼び出し元はプロセス A であると想定されています)。

• dwDesiredAccess - 複製されたハンドルに必要なアクセス マスク。dwOptions パラメータにフラグ DUPLICATE_SAME_ACCESS がある場合、このアクセス マスクは無視されます。それ以外の場合、これは新しいハンドルを要求するアクセス マスクです。

• hSourceProcessHandle - これはソース プロセスのハンドルです。このハンドルには PROCESS_DUP_handle アクセス マスクが必要です。ソースが呼び出し元のプロセスの場合、GetCurrentProcess を渡すと機能します (常に完全なアクセス権を持ちます)。

• hSourceHandle - コピーするソース ハンドル。このハンドルは、ソース プロセスのコンテキストで有効である必要があります。

• hTargetProcessHandle - ターゲット プロセス ハンドル。通常、このようなハンドルは、OpenProcess への呼び出しを使用して取得する必要があります。ソースプロセスと同様に、process_DUP_HANDLE アクセスマスクも必要です。

• lpTargetHandle - これは、ターゲット プロセスの観点から有効な結果ハンドルです。図 2-18 では、呼び出し元に返される結果ハンドルは 72 です。この値はプロセス B のものです (呼び出し元がプロセス A であると仮定します)。

• dwDesiredAccess - コピー ハンドルに必要なアクセス マスク。dwOptions パラメータにフラグ DUPLICATE_SAME_ACCESS がある場合、このアクセス マスクは無視されます。それ以外の場合、これはアクセス マスクです。

以下は、アクセス マスクを削減しながら、同じプロセス内でジョブ オブジェクトを作成し、そのハンドルを複製する簡単な例です (エラー処理は省略)。

同じプロセスでジョブ オブジェクトを作成し、そのハンドルを複製しながら、アクセス マスクを削減する (エラー処理を省略する) 簡単な例を次に示します。

ここに画像の説明を挿入

ソースプロセスとターゲットプロセスは現在のプロセスです。このコード部分を実行し、Process Explorer でハンドルを確認すると、違いがわかります (図 2-19)。

ソース プロセスとターゲット プロセスは現在のプロセスです。このコードを実行し、Process Explorer でハンドルを確認すると、違いがわかります (図 2-19)。

ここに画像の説明を挿入

1 つのハンドル (0xac) はジョブ オブジェクトへの完全なアクセス権を持ち、もう 1 つの (複製された) ハンドル (0xb0) は指定された必要なアクセス マスクのみを持ちます。

1 つのハンドル (0xac) にはジョブ オブジェクトへの完全なアクセス権があり、もう 1 つの (重複) ハンドル (0xb0) には必要なアクセス マスクのみが指定されています。

より一般的なケースでは、現在のプロセスのハンドルがターゲットの連携プロセスに複製されます。次の関数は、現在のプロセスからターゲット プロセスにソース ハンドルを複製します。

より一般的なケースでは、現在のプロセスのハンドルがターゲットの連携プロセスにコピーされます。次の関数は、ソース ハンドルを現在のプロセスからターゲット プロセスにコピーします。

ここに画像の説明を挿入

This is the case where handle duplication becomes non-trivial. It’s not the act of duplication itself - that’s rather simple - a single function call. The problem is how to convey the information to the target process. Two pieces of information must be conveyed to the target process:

在这种情况下,句柄的重复变得非同小可。问题不在于重复行为本身–那是相当简单的–一个单一的函数调用。问题在于如何将信息传达给目标进程(因为目标进程不知道它现在能访问一个新的内核对象了,我们必须使用窗口消息或者其他进程间通信IPC机制;继承内核对象句柄的时候也有这样的问题)。必须向目标进程传递两条信息:

• When the handle has been duplicated.

•当句柄被复制时。

• What is the duplicated handle value?

•重复句柄的值是多少?

呼び出し元は作成されたハンドル値を知っていますが、ターゲット プロセスは知らないことに注意してください。
呼び出し側プロセスが必要な情報をターゲット プロセスに渡すことを可能にする、他の形式のプロセス間通信が必要です (これらは同じシステムの一部であり、問​​題のカーネル オブジェクトを共有することで連携する必要があるため)。

呼び出し元は作成されたハンドルの値を知っていますが、ターゲット プロセスは知らないことに注意してください。
呼び出し側プロセスが必要な情報をターゲット プロセスに渡すことを可能にする、他の形式のプロセス間通信が必要です (これらは同じシステムの一部であり、関連するカーネル オブジェクトを共有することで連携する必要があるため)。

プライベートオブジェクト名前空間

一部の種類のカーネル オブジェクトは文字列ベースの名前を持つことができることがわかりました。また、これがプロセス間でそのようなオブジェクトを共有する (便利な) 方法の 1 つであることもわかりました。ただし、名前付きオブジェクトにはいくつかの欠点があります。

特定の種類のカーネル オブジェクトは文字列ベースの名前を持つことができることがわかりました。また、これがプロセス間でそのようなオブジェクトを共有する (便利な) 方法であることもわかりました。ただし、名前付きオブジェクトにはいくつかの欠点もあります。

• Some other, unrelated process, may create an object with the same name, that can cause failure when creating the object later (if the object types differ), or worse, the creation “succeeds” because it’s the same object type and the code gets back a handle to an existing object. The result is a mess, where processes use the same object that they don’t expect.

•其他一些不相关的过程可能会创建具有相同名称的对象,这可能会导致以后创建对象时失败(如果对象类型不同),或者更糟的是,创建“成功”,因为它是相同的对象类型,并且代码会返回现有对象的句柄。结果是一团糟,进程使用了他们意想不到的相同对象。

• これは、強調するために上記の箇条書きの特殊なケースです。名前は表示されるため (ツール内で表示されますが、プログラムで取得することもできます)、別のプロセスがオブジェクトを「ハイジャック」したり、オブジェクトの使用を妨害したりする可能性があります。セキュリティの観点から、問題のオブジェクトは目立ちすぎます。特定のオブジェクトが何に使用されるかを推測する良い方法がないため、名前のないオブジェクトははるかにステルスです。

• 強調するために、これは上記の箇条書きの特殊なケースです。名前は表示されるため (ツール内で表示されますが、プログラムで取得することもできます)、別のプロセスがオブジェクトを「ハイジャック」したり、オブジェクトの使用を妨害したりする可能性があります。セキュリティの観点から、問題のオブジェクトは目立ちすぎます。名前のないオブジェクトは、特定のオブジェクトの用途を推測する良い方法がないため、よりステルス性が高くなります。

プロセス間で名前付きオブジェクトを共有し(簡単なので)、他のプロセスからは見えないようにする方法はありますか? Windows Vista 以降では、連携するプロセスだけが知っているプラ​​イベート オブジェクト名前空間を作成する方法が用意されています。ツールや API を使用しても、その完全な名前は明らかにされません。

The PrivateSharing sample application is an enhanced version of BasicSharing, where the memory-mapped file object’s name is now under a private object namespace and is not visible to all. Looking at this object with Process Explorer shows a partial name only (figure 2-20).

有没有一种方法可以让进程共享命名对象(因为这很容易),但对其他进程不可见?从WindowsVista开始,有一种方法可以创建一个只有协作进程才知道的私有对象命名空间。使用工具或API不会显示其全名。

PrivateSharing示例应用程序是BasicSharing的增强版本,其中内存映射文件对象的名称现在位于私有对象命名空间下,并且对所有人都不可见。使用Process Explorer查看此对象时,仅显示部分名称(图2-20)。

ここに画像の説明を挿入

If some random code tries to locate an object named “MySharedMem”, it would fail to do so, since this not the object’s true name.

如果一些随机代码试图定位名为“MySharedMem”的对象,它将无法定位,因为这不是对象的真实名称。

Creating a private namespace is a two-step process. First, a helper object called a Boundary Descriptor must be created. This descriptor allows adding certain Security IDs (SIDs) that would be able to use private namespaces created based on that boundary descriptor. This can help tighten security on the private namespace(s). To create a boundary descriptor, use CreateBoundaryDescriptor:

创建私有命名空间需要两个步骤。首先,必须创建一个名为边界描述符的辅助对象。该描述符允许添加某些安全ID(SID),这些ID将能够使用基于该边界描述符创建的私有命名空间。这有助于加强私有命名空间的安全性。要创建边界描述符,请使用CreateBoundaryDescriptor:

ここに画像の説明を挿入

Once a boundary descriptor exists, two functions can be used to restrict access to any private namespace created through that descriptor: AddSIDToBoundaryDescriptor and AddIntegrityLabelToBoundaryDescriptor (the latter available starting from Windows 7):

境界記述子が存在すると、AddSIDToBoundaryDescriptor と AddIntegrityLabelToBoundary 記述子という 2 つの関数を使用して、その記述子を通じて作成されたプライベート名前空間へのアクセスを制限できます (後者は Windows 7 以降で利用可能です)。

ここに画像の説明を挿入

どちらも境界記述子のハンドルのアドレスと SID を受け入れます。AddSIDToBoundaryDescriptor を使用すると、通常、SID はグループの SID となり、そのグループ内のすべてのユーザーがプライベート名前空間にアクセスできるようになります。AddIntegrityLabelToBoundaryDescriptor を使用すると、この境界記述子によって管理されるプライベート名前空間内のオブジェクトを開くことを希望するプロセスの最小整合性レベルを設定できます。

どちらも境界記述子ハンドルのアドレスと SID を受け入れます。AddSIDToBoundaryDescriptor の場合、SID は通常、グループ内のすべてのユーザーがプライベート名前空間にアクセスできるようにするグループの SID です。AddIntegrityLabelToBoundaryDescriptor を使用すると、この境界記述子によって管理されるプライベート名前空間内のオブジェクトを開くことを希望するプロセスの最小整合性レベルを設定できます。

SID と整合性レベルについては、第 16 章で説明します。

SID と整合性レベルについては、第 16 章で説明します。

境界記述子が設定されたら、次のステップは、CreatePrivateNamespace を使用して実際のプライベート名前空間を作成することです。

境界記述子を設定したら、次のステップは、CreatePrivateNamespace を使用して実際のプライベート名前空間を作成することです。

ここに画像の説明を挿入

紛らわしいことに、境界記述子のタイプは HANDLE ではなく void* です。これは API の誤りですが、HANDLE は void* として定義されているため、これで問題なく動作します。この事故は、境界記述子が HANDLE を返したとしても、境界記述子がカーネル オブジェクトではないことも示唆しています。独自の閉じる関数、DeleteBoundaryDescriptor があります。

オブジェクト名前空間も、真のカーネル オブジェクトではありません。名前空間がすでに存在する場合、関数は失敗するため、代わりに OpenPrivateNamespace を使用する必要があります。独自の close 関数 (ClosePrivateNamespace) もあります。

紛らわしいことに、境界記述子のタイプは HANDLE ではなく void* です。これは API のバグですが、HANDLE は void* として定義されているため、これは問題ありません。このエラーは、境界記述子が HANDLE を返しても、境界記述子がカーネル オブジェクトではなく、独自のシャットダウン関数 DeleteBoundaryDescriptor を持っていることも意味します。

オブジェクト名前空間も実際にはカーネル オブジェクトではありません。名前空間がすでに存在する場合、関数は失敗するため、代わりに OpenPrivateNamespace を使用する必要があります。独自の close 関数 (ClosePrivateNamespace) もあります。

ここに画像の説明を挿入

もう 1 つのスリップは、関数 ClosePrivateNamespace が標準の BOOL の代わりに BOOLEAN (型定義が BYTE) を返すことです。

もう 1 つの間違いは、ClosePrivateNamespace 関数が標準の BOOL ではなく BOOLEAN (型定義は BYTE) を返すことです。

ネームスペースが作成または開かれると、通常は、alias\name の形式の名前で名前付きオブジェクトを作成できます。ここで、「alias」は、ネームスペースの作成またはオープンの lpAliasPrefix パラメータです。

ネームスペースが作成または開かれると、名前付きオブジェクトを通常どおりに作成できます。名前の形式は alias\name です。ここで、「alias」はネームスペースを作成または開くときの lpAliasPrefix パラメータです。

PrivateSharing アプリケーションの具体的なコードを見てみましょう。
ダイアログ クラスには 3 つのメンバーが含まれるようになりました。

PrivateSharing アプリケーションの具体的なコードを見てみましょう。
ダイアログ クラスには 3 つのメンバーが含まれるようになりました。

ここに画像の説明を挿入

The code uses the WIL unique_handle RAII wrapper for the memory-mapped file’s handle, but the boundary descriptor and the namespace are managed as raw handles.

该代码使用WIL unique_handle RAII包装作为内存映射文件的句柄,但边界描述符和命名空间作为原始句柄进行管理。

When the dialog box is created, the same memory-mapped file is created as in BasicSharing, but this time under a private namespace (error handling omitted for clarity):

创建对话框时,会创建与BasicSharing中相同的内存映射文件,但这次是在专用命名空间下(为清晰起见,省略了错误处理):

ここに画像の説明を挿入
In this example, a single SID was added to the boundary descriptor. This SID is for all standard users. It’s possible to add something more strict, such as the Administrators group, so that processes running under standard user rights would not be able to tap into this boundary descriptor. The SID is created based on a well-known SID for the users group by calling CreateWellKnownSid. Then AddSIDToBoundaryDescriptor is called to attach the SID to the boundary descriptor.

在本例中,将一个SID添加到边界描述符中。此SID适用于所有标准用户。可以添加一些更严格的内容,例如Administrators组,这样在标准用户权限下运行的进程就无法使用此边界描述符。SID是基于用户组的已知SID通过调用CreateWellKnownSid创建的。然后调用AddSIDToBoundaryDescriptor将SID附加到边界描述符。

Don’t worry about these SIDs and other security terms. They are described in detail in chapter 16.

不要担心这些SID和其他安全条款。第16章对它们进行了详细描述。

Once the boundary descriptor is set, CreatePrivateNamespace or OpenPrivateNamespace is called with the alias “MyNamespace”. This is used as the prefix for the memory-mapped file object created with CreateFileMapping.

一旦设置了边界描述符,就会使用别名“MyNamespace”调用CreatePrivateNamespace或OpenPrivateNamespace。这被用作使用CreateFileMapping创建的内存映射文件对象的前缀。

Finally, the WM_DESTROY message handler for the dialog deletes the namespace and boundary descriptor:

最后,对话框的WM_DESTROY消息处理程序删除名称空间和边界描述符:

ここに画像の説明を挿入

Bonus: WIL Wrappers for Private Namespaces

The WIL library has many wrappers for various types of handles and pointers. Unfortunately, it doesn’t have a boundary descriptor and private namespace wrappers. Fortunately, it’s not too difficult to create ones. Here is one way to do it:

WIL库有许多用于各种类型的句柄和指针的包装器。不幸的是,它没有边界描述符和私有命名空间包装器。幸运的是,创建一个并不太难。以下是一种方法:

ここに画像の説明を挿入

上記の宣言の詳細については説明しません。これは、C++ 11 の decltype とテンプレートの使用について十分に理解している必要があるためです。

上記の宣言の詳細については、C++11 decltype、およびテンプレートの使用方法についての知識が必要であるため、説明しません。

PrivateSharing2 プロジェクトは PrivateSharing と同じですが、WIL ラッパー (上記の追加機能を追加) を使用して、すべてのハンドルと、MapViewOfFile から返されたポインターを管理します。

PrivateSharing2 プロジェクトは PrivateSharing と同じですが、WIL ラッパー (上記を追加) を使用して、MapViewOfFile から返されたポインターを含むすべてのハンドルを管理します。

Read 関数の例を次に示します。

読み取り関数の例を次に示します。

ここに画像の説明を挿入

その他のオブジェクトとハンドル

カーネル オブジェクトは、システム プログラミングのコンテキストにおいて興味深いものであり、本書の焦点です。Windows では他にも、ユーザー オブジェクトと GDI オブジェクトという一般的なオブジェクトが使用されます。以下は、これらのオブジェクトとそのオブジェクトへのハンドルの簡単な説明です。

内核对象在系统编程的上下文中很有趣,也是本书的重点。Windows中还使用其他常见对象,即用户对象和GDI对象。以下是对这些对象和这些对象的句柄的简要描述。

Task Manager can show the number of such objects for each process by adding the User Objects and GDI Objects columns, as shown in figure 2-21.

任务管理器可以通过添加用户对象和GDI对象列来显示每个进程的此类对象的数量,如图2-21所示。

ここに画像の説明を挿入

User Objects

User objects are Windows (HWND), Menus (HMENU) and hooks (HHOOK). Handles to these objects have the following attributes:

用户对象是Windows(HWND)、Menus(HMENU)和hook(HHOOK)。这些对象的句柄具有以下属性:

• No reference counting. The first caller that destroys a user object - it’s gone.

•无引用计数。破坏用户对象的第一个调用程序——它已经不存在了。

• Handle values are scoped under a Window Station. A Window Station contains a clipboard, desktops and atom table. This means handles to these objects can be passed freely among all applications sharing a desktop, for instance.

• ハンドル値の範囲は、Window Station (Window ワークステーション) の下にあります。Window Station にはクリップボード、デスクトップ、アトム テーブルが含まれています。これは、たとえば、これらのオブジェクトへのハンドルをデスクトップを共有するすべてのアプリケーション間で自由に受け渡しできることを意味します。

GDI オブジェクト

グラフィック デバイス インターフェイス (GDI) は Windows のオリジナルのグラフィック API であり、より豊富で優れた API (Direct2D など) があるにもかかわらず、現在でも使用されています。GDI オブジェクトの例: デバイス コンテキスト (HDC)、ペン (HPEN)、ブラシ (HBRUSH)、ビットマップ (HBITMAP) など。

グラフィック デバイス インターフェイス (GDI) は、Windows のオリジナルのグラフィック API であり、現在でも使用されていますが、より豊富で優れた API (Direct2D など) もあります。GDI オブジェクトの例は、デバイス コンテキスト (HDC)、ペン (HPEN)、ブラシ (HBRUSH)、ビットマップ (HBITMAP) などです。

彼らの属性は次のとおりです。

彼らの特徴は次のとおりです。

• 参照カウントはありません。

• 参照カウントはありません。

• ハンドルは、ハンドルが作成されたプロセス内でのみ有効です。

• ハンドルは、ハンドルが作成されている間のみ有効です。

• プロセス間で共有することはできません。

• プロセス間で共有することはできません。

おすすめ

転載: blog.csdn.net/zhaopeng01zp/article/details/131053588