プロセス ID またはプロセス ハンドルを使用してジョブ オブジェクトを取得する方法
September 9, 2003
日本語版最終更新日 2003 年 11 月 4 日
インターネットや Windows ベースの開発にまつわる問題や疑問をお持ちの方がいらっしゃいましたら、ぜひ Dr. GUI (drgui@microsoft.com) に質問してみてください。彼は毎月 2 回 MSDN オンラインで、みなさんのために待機しています。名医は外科手術のスケジュールが詰まっているため、個別回答はできませんが、ここで可能な限りお答えしていく予定です。質問が採用された方には、オフィシャル Dr. GUI T シャツを進呈します。
要約: Microsoft Windows Management Instrumentation (WMI) API を使用して、プロセス ID またはプロセス ハンドルに関連付けられたジョブ オブジェクトを取得する方法を Dr. GUI が解説します。
編集注:
熱心な読者ならお気付きでしょうが、Dr. GUI.は事情があってしばらくご無沙汰していました。しばらく前、名医はいつもの仲間 (ペーパークリップのクリッピー、MSN バタフライ、そしてもちろんボブ) とゴルフをしていました。非常に調子良くプレーしていたのですが (恐らくこれまでで最高のプレーで、ボブが顔をしかめるほどでした)、16 番ティーで落雷を受けて倒れてしまいました。幸い、致命傷には至りませんでした。左足の爪を失い、上等のドライバーを溶かしただけで済みました。
この経験によって Dr. GUI は、自分の行っている医療事業の重要性を再認識しました。今は再び元気いっぱいで、皆さんのコーディング関連の緊急事態を解決すべく張り切っています。
Dr. GUI 様
いくつかの生産ラインのオートメーションで Windows 2000 と XP システムを使用して、複数のプロセスを実行しています。これらのプロセスはお互い、そしてラインを直接制御する PLC と交信しあっています。
1 つのプロセスが利用可能リソースすべてを使い切ってコンピュータに不具合を生じさせないように、1 つのプロセスが使用できるリソースを制限する必要があります。このため、各プロセスにジョブ オブジェクトを取り付け、SetInformationJobObject を使用して制限を設定しました。また、各プロセスが使用しているリソースを追跡するためのプロセス監視アプリケーションも用意しています。このプロセスで、プロセスに取り付けたジョブ (QueryInformationJobObject 使用) にも制限を設定できたら良いと考えます。
しかし、プロセス (その ID またはハンドルを使用) がジョブに関連付けられているかどうか、またどのジョブが関連付けられているかを特定する方法がわかりません。プラットフォーム SDK で、特定のジョブに関連付けられたプロセスをすべて列挙する機能を見つけました。またツール ヒントで、システムの全プロセスを列挙する機能も見つけました。しかし、関連プロセスのプロセス ID またはハンドルを使用してジョブ オブジェクトの名前またはハンドルを調べる機能や、システムの全ジョブ オブジェクトを列挙する機能が見つかりません。
そこで質問です。
- プロセスが関連付けられているジョブ (名前またはハンドル) を知ることはできますか?
- システムにある全ジョブ オブジェクトを列挙することはできますか?
ご回答、よろしくお願いします。 イゴール デ ルーク
Dr. GUI の回答:
ご質問どうもありがとう。ご存知のように、ジョブ オブジェクトは Dr. GUI.にとって特別な重要性があります。この数年でいくつかを扱ってきましたし、友人とワインを飲んだ後、ジョブ オブジェクトを列挙するのが楽しみの 1 つになっています。セミコロンをタイプできなくなった C プログラマの治療法を探す、なんてこともありました。唯一の治療法はもちろん... 話が脱線したようです。本日の質問に戻りましょう。
名医は、あなたの抱える問題をよく理解しています。Platform SDK API や Win32 API で解決するのは簡単ではないですね。しかし、Microsoft® Windows® Management Instrumentation (WMI) API を使用すればかなりの可能性があります。
WMI は、エンタープライズ環境で管理情報およびコントロールを提供する Windows オペレーティング システムのコンポーネントです。WMI を使用することにより、マネージャは、デスクトップ システム、アプリケーション、ネットワーク、およびその他のエンタープライズ コンポーネントの情報を業界標準を使用してクエリを行い、設定することができます。開発者は WMI を使用して、重要なイベントが発生した時にユーザーに通知するイベント監視アプリケーションを作成できます。
WMI の IWbemServices::CreateInstanceEnum メソッドを使用すれば、オペレーティング システムで使用されているジョブ オブジェクトを列挙できます。IWbemServices::CreateInstanceEnum メソッドは、ユーザーが設定した選択条件に基づき、指定クラスのインスタンスを返す列挙子を作成します。クラス名を Win32_NamedJobObject と指定して、ジョブ オブジェクトを列挙します。Win32_NamedJobObject WMI クラスは、ジョブ オブジェクト内のプロセスの寿命とリソースを制御するため、プロセスをグループ化する際に使用されるカーネル オブジェクトを示します。
Win32_NamedJobObjectProcess WMI クラスは、ジョブ オブジェクトとジョブ オブジェクトに含まれるプロセスを関連付けます。IWbemServices::CreateInstanceEnum メソッドを使用し、Win32_NamedJobObjectProcess のインスタンスを列挙することができます。これにより、特定のジョブ オブジェクトに関連するプロセスを知ることができます。
以下に、クエリを行うための完全なコードを示します。 #define _WIN32_DCOM
#include <iostream>
using namespace std;
#include <iomanip>
#include <windows.h>
#include <chstring.h>
#include <chstrarr.h>
#include <assert.h>
#include <comdef.h>
#include <wbemcli.h>
#include <winbase.h>
#include <ocidl.h>
#include <Wbemidl.h>
#define SAFE_INTERFACE_RELEASE (interfacepointer) \
if ((interfacepointer)! = NULL) \
{ \
(interfacepointer)->Release (); \
(interfacepointer) = NULL; \
} \
1
int wmain (int argc, wchar_t **argv)
{
int retVal = 0;
HRESULT hres;
// COM を初期化します。
hres = CoInitializeEx(0, COINIT_MULTITHREADED);
if (FAILED(hres))
{
cout << "COM ライブラリの初期化に失敗しました。Error code = 0x"
<< hex << hres << endl;
return 1;
}
hres = CoInitializeSecurity(NULL, -1, NULL, NULL,
RPC_C_AUTHN_LEVEL_CONNECT,
RPC_C_IMP_LEVEL_IDENTIFY,
NULL, EOAC_NONE, 0
);
if (FAILED(hres))
{
cout << "セキュリティの初期化に失敗しました。Error code = 0x"
<< hex << hres << endl;
retVal = 1;
goto CleanUp;
}
IWbemLocator *pLoc = 0;
hres = CoCreateInstance(CLSID_WbemLocator, 0,
CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID *) &pLoc);
if (FAILED(hres))
{
cout << "IWbemLocator オブジェクトの作成に失敗しました。Err code = 0x"
<< hex << hres << endl;
retVal = 1;
goto CleanUp;
}
IWbemServices *pServices = NULL;
// root\cimv2 名前空間を現在のユーザーに接続。
hres = pLoc->ConnectServer(
_bstr_t(L"ROOT\\CIMV2"),
NULL,
NULL,
0,
NULL,
0,
0,
&pServices
);
if (FAILED(hres))
{
cout << "接続できませんでした。Error code = 0x"
<< hex << hres << endl;
retVal = 1;
goto CleanUp;
}
SAFE_INTERFACE_RELEASE (pLoc);
// クライアントの偽装を発生させるようにプロキシを設定。
hres = CoSetProxyBlanket(pServices,
RPC_C_AUTHN_WINNT,
RPC_C_AUTHZ_NONE,
NULL,
RPC_C_AUTHN_LEVEL_CALL,
RPC_C_IMP_LEVEL_IMPERSONATE,
NULL,
EOAC_NONE
);
if (FAILED(hres))
{
cout << "プロキシ ブランケットを設定できませんでした。Error code = 0x"
<< hex << hres << endl;
retVal = 1;
goto CleanUp;
}
// Win32_NamedJobObject クラスのインスタンスを列挙
IEnumWbemClassObject *pEnumWbemClsObj;
BSTR bstrClsName = SysAllocString (L"Win32_NamedJobObject");;
hres = pServices->CreateInstanceEnum (bstrClsName,
WBEM_FLAG_RETURN_IMMEDIATELY | WBEM_FLAG_FORWARD_ONLY,
NULL, &pEnumWbemClsObj);
SysFreeString (bstrClsName);
if (FAILED (hres))
{
cout << "列挙できませんでした = 0x"
<< hex << hres << endl;
retVal = 1;
goto CleanUp;
}
IWbemClassObject *pclsObj;
ULONG uReturn = 0;
cout << "システムで利用可能なジョブ オブジェクト:" << endl;
while (pEnumWbemClsObj)
{
HRESULT hr = pEnumWbemClsObj->Next(WBEM_INFINITE, 1,
&pclsObj, &uReturn);
if(0 == uReturn)
{
break;
}
VARIANT vtProp;
VariantInit(&vtProp);
hr = pclsObj->Get(L"CollectionID",0,&vtProp,0,0);
CHString str(vtProp.bstrVal);
wcout << "Name : " << (LPCWSTR)str.Mid(1,str.GetLength())<< endl;
VariantClear(&vtProp);
}
// ジョブ オブジェクト Win32_NamedJobObjectProcess
// に関連付けられたすべてのプロセスを列挙
SAFE_INTERFACE_RELEASE(pEnumWbemClsObj);
bstrClsName = SysAllocString(L"Win32_NamedJobObjectProcess");;
hres = pServices->CreateInstanceEnum(bstrClsName,
WBEM_FLAG_RETURN_IMMEDIATELY | WBEM_FLAG_FORWARD_ONLY,
NULL, &pEnumWbemClsObj);
SysFreeString(bstrClsName);
if (FAILED(hres))
{
cout << "列挙できませんでした = 0x"
<< hex << hres << endl;
retVal = 1;
goto CleanUp;
}
SAFE_INTERFACE_RELEASE(pclsObj);
uReturn = 0;
cout << endl;
while(pEnumWbemClsObj)
{
HRESULT hr = pEnumWbemClsObj->Next(WBEM_INFINITE,1,&pclsObj,&uReturn);
if(0 == uReturn)
{
goto CleanUp;
}
VARIANT vtProp;
VariantInit(&vtProp);
hr = pclsObj->Get(L"Member",0,&vtProp,0,0);
CHString str(vtProp.bstrVal);
int i = str.GetLength()-1;
str = str.Right(i - str.Find(L"\""));
str = str.Mid(0,(str.GetLength()-1));
if(str.Compare(argv[1])==0)
{
VariantClear(&vtProp);
hr = pclsObj->Get(L"Collection",0,&vtProp,0,0);
str = vtProp.bstrVal;
int i = str.GetLength()-1;
str = str.Right(i - (str.Find(L"\"")+2));
i = str.GetLength()-1;
str = str.Left(i);
VariantClear(&vtProp);
wcout << "Process ID : " << argv[1] <<
" 以下のジョブ ID と関連: " << (LPCWSTR)str << endl;
}
VariantClear(&vtProp);
}
// クリーンアップ
CleanUp:
SAFE_INTERFACE_RELEASE(pLoc);
SAFE_INTERFACE_RELEASE(pServices);
CoUninitialize();
return retVal; // プログラムは正常に終了しました。
}
C++ を使用して WMI 用のアプリケーションを作成することは、簡単ではありません。COM を初期化し (WMI は COM テクノロジをベースとしています)、WMI プロトコルにアクセスして設定 (CoInitializeEx 関数および CoInitializeSecurity 関数を呼び出して WMI にアクセス) して、そして終了後に手動でクリーンアップを実行します (常に整理整頓を心がけましょう)。
以下に WMI への接続方法を示します。
- CoInitializeEx を呼び出して COM を初期化します。以下のコード例は、CoInitializeEx 呼び出し方法を示したものです:
HRESULT hres;
hres = CoInitializeEx (0, COINIT_MULTITHREADED); // COM を初期化します。
if (FAILED (hres))
{
cout << "COM ライブラリの初期化に失敗しました。Error code = 0x"
<< hex << hres << endl;
return 1;
}
- CoInitializeSecurity インターフェイスを呼び出して全般 COM セキュリティ レベルを設定します。
CoInitializeEx と同様、CoInitializeSecurity は、COM インターフェイスをセットアップするときに呼び出す必要のある標準関数です。CoInitializeSecurity の呼び出し方法を示します。 hres = CoInitializeSecurity (NULL, -1, NULL,
NULL, RPC_C_AUTHN_LEVEL_CONNECT,
RPC_C_IMP_LEVEL_IDENTIFY, NULL,
EOAC_NONE, 0);
if (FAILED (hres))
{
cout << "セキュリティの初期化に失敗しました。Error code = 0x"
<< hex << hres << endl;
retVal = 1;
goto CleanUp;
}
COM への標準呼び出しの終了後、IWbemLocator::ConnectServer メソッドを呼び出して WMI に接続する必要があります。ConnectServer メソッドが IWbemServices インターフェイスのプロキシを返します。
WMI 名前空間への接続を作成するため、CoCreateInstance への呼び出しにより、IWbemLocator インターフェイスを初期化します。以下のコードは、IWbemLocator 初期化方法を示したものです。 IWbemLocator *pLoc = 0;
hres = CoCreateInstance (CLSID_WbemLocator, 0,
CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID *) &pLoc);
if (FAILED (hres))
{
cout <<
"IWbemLocator オブジェクトの作成に失敗しました。Err code = 0x"
<< hex << hres << endl;
retVal = 1;
goto CleanUp;
}
- IWbemLocator::ConnectServer メソッドへの呼び出しにより、WMI に接続します。
ConnectServer メソッドは、IWbemServices インターフェイスにプロキシを返します。これは、ConnectServer への呼び出しで指定された WMI 名前空間 (ローカルまたはリモート) へのアクセスに使用できます。次のように ConnectServer を呼び出します。 IWbemServices *pServices = NULL;
// root\cimv2 名前空間を現在のユーザーに接続。
hres = pLoc->ConnectServer (
_bstr_t(L"ROOT\\CIMV2"),
NULL,
NULL,
0,
NULL,
0,
0,
&pServices
);
if (FAILED(hres))
{
cout << "接続できませんでした。Error code = 0x"
<< hex << hres << endl;
retVal = 1;
goto CleanUp;
}
IWbemServices プロキシへのポインタを取得した後、このプロキシで WMI にアクセスするには、プロキシにセキュリティを設定する必要があります。IWbemServices プロキシでは、プロセス外オブジェクトへのアクセスが許可されるため、セキュリティの設定が必要です。一般的に COM セキュリティでは、セキュリティ プロパティを適切に設定しないと、プロセスは別のプロセスにアクセスできません。
WMI 接続でセキュリティを設定するには、CoSetProxyBlanket への呼び出しで IWbemServices プロキシにセキュリティ レベルを設定する必要があります。CoSetProxyBlanket を呼び出す一般的な方法を示します。 // クライアントの偽装を発生させるようにプロキシを設定。
hres = CoSetProxyBlanket(pServices,
RPC_C_AUTHN_WINNT,
RPC_C_AUTHZ_NONE,
NULL,
RPC_C_AUTHN_LEVEL_CALL,
RPC_C_IMP_LEVEL_IMPERSONATE,
NULL,
EOAC_NONE
);
if (FAILED(hres))
{
cout << "プロキシ ブランケットを設定できませんでした。Error code = 0x"
<< hex << hres << endl;
retVal = 1;
goto CleanUp;
}
システムにあるすべてのジョブ オブジェクトを列挙するには、Dr. GUI は通常このコードを使用します。 // Win32_NamedJobObject クラスのインスタンスを列挙
IEnumWbemClassObject *pEnumWbemClsObj;
BSTR bstrClsName = SysAllocString (L"Win32_NamedJobObject");
Hres = pServices->CreateInstanceEnum(bstrClsName,
WBEM_FLAG_RETURN_IMMEDIATELY | WBEM_FLAG_FORWARD_ONLY,
NULL, &pEnumWbemClsObj);
SysFreeString(bstrClsName);
if (FAILED(hres))
{
cout << "列挙できませんでした = 0x"
<< hex << hres << endl;
retVal = 1;
goto CleanUp;
}
IWbemClassObject *pclsObj;
ULONG uReturn = 0;
cout << "システムで利用可能なジョブ オブジェクト:" << endl;
while(pEnumWbemClsObj)
{
HRESULT hr = pEnumWbemClsObj->Next (WBEM_INFINITE,
1, &pclsObj, &uReturn);
if (0 == uReturn)
{
break;
}
VARIANT vtProp;
VariantInit(&vtProp);
hr = pclsObj->Get (L"CollectionID", 0, &vtProp, 0, 0);
CHString str(vtProp.bstrVal);
wcout << "Name: " << (LPCWSTR) str.Mid (1, str.GetLength ()) << endl;
VariantClear (&vtProp);
}
IWbemServices::CreateInstanceEnum メソッドは、ユーザーが設定した選択条件に基づき、指定クラスのインスタンスを返す列挙子を作成します。クラス名を Win32_NamedJobObject と指定して、ジョブ オブジェクトを列挙します。Win32_NamedJobObject WMI クラスは、ジョブ オブジェクト内のプロセスの寿命とリソースを制御するため、プロセスをグループ化する際に使用されるカーネル オブジェクトを示します。
プロセスが関連付けられているジョブ オブジェクト (名前またはハンドル) を知る方法について説明します。以下のコードにより、プロセス ID からジョブ オブジェクト名またはジョブ オブジェクト ハンドルを得ることができます。 // ジョブ オブジェクト Win32_NamedJobObjectProcess
// に関連付けられている
// すべてのプロセスを列挙
SAFE_INTERFACE_RELEASE (pEnumWbemClsObj);
bstrClsName = SysAllocString (L"Win32_NamedJobObjectProcess");
hres = pServices->CreateInstanceEnum (bstrClsName,
WBEM_FLAG_RETURN_IMMEDIATELY | WBEM_FLAG_FORWARD_ONLY,
NULL, &pEnumWbemClsObj);
SysFreeString (bstrClsName);
if (FAILED(hres))
{
cout << "列挙できませんでした = 0x"
<< hex << hres << endl;
retVal = 1;
goto CleanUp;
}
SAFE_INTERFACE_RELEASE (pclsObj);
uReturn = 0;
cout << endl;
while (pEnumWbemClsObj)
{
HRESULT hr = pEnumWbemClsObj->Next (WBEM_INFINITE,
1, &pclsObj, &uReturn);
if (0 == uReturn)
{
goto CleanUp;
}
VARIANT vtProp;
VariantInit (&vtProp);
hr = pclsObj->Get (L"Member", 0, &vtProp, 0, 0);
CHString str (vtProp.bstrVal);
int i = str.GetLength()-1;
str = str.Right (i - str.Find (L"\""));
str = str.Mid (0, (str.GetLength () - 1));
// argv[1] は、検索対象のジョブ オブジェクトに関連付けられたプロセス ID
// です。
if (str.Compare (argv[1]) == 0) //str.Compare("Process ID")
{
VariantClear (&vtProp);
hr = pclsObj->Get (L"Collection", 0, &vtProp, 0, 0);
str = vtProp.bstrVal;
int i = str.GetLength ()-1;
str = str.Right (i - (str.Find (L"\"") + 2));
i = str.GetLength ()-1;
str = str.Left (i);
VariantClear (&vtProp);
wcout << "プロセス ID: " << argv[1] <<
" is attached with Job ID: " << (LPCWSTR)str << endl;
}
VariantClear (&vtProp);
}
これで解決です。Win32_NamedJobObjectProcess WMI クラスは、ジョブ オブジェクトとジョブ オブジェクトに含まれるプロセスを関連付けます。プログラムを実行中、プロセス ID をコマンド ライン引数としてパスします。実行可能プログラムがシステムのジョブ オブジェクトをすべて一覧化し、パラメータとして渡されたプロセス ID に関連付けられた ジョブ オブジェクト名を提供します。
ジョブ (オブジェクト) を探すお役に立てたでしょうか。さて、名医は新しい趣味でも探そうと思います。落雷の危険がないやつをね。ボッチ (芝生でするイタリアのボウリング) なんかいいかもね。
ありがとう!
Dr. GUI はこの場を借りて、ウマ マヘシュワー、アクヒル ゴクヘールをはじめとする彼のすばらしい専門医チーム、および看護師のマウラ ボーグマンに感謝を表したいと思います。彼らのサポートなしでは、診断もうまくいかなかったでしょう。
Ask Dr. GUI
|