EternalWindows
共有管理 / コンピュータの列挙

これまで取り上げてきたNetwork Share Management関数は、共有リソースへの接続やセッションを列挙するなど、 あくまで共有リソースを公開する側にとって有用となる関数で構成されていました。 今回から取り上げるWindows Networking (WNet)関数は、 共有リソースを必要とする側が呼び出す関数で構成されており、 共有リソースへの接続や列挙などを行うことができます。 リモートコンピュータ上の共有リソースを列挙したい場合は、 WNetOpenEnumで列挙に必要なハンドルを取得します。

DWORD WNetOpenEnum(
  DWORD dwScope,
  DWORD dwType,
  DWORD dwUsage,
  LPNETRESOURCE lpNetResource,
  LPHANDLE lphEnum
);

dwScopeは、列挙する範囲を表す定数を指定します。 RESOURCE_GLOBALNETを指定すると、可視である全てのネットワークが対象になります。 dwTypeは、列挙するリソースの種類を表す定数を指定します。 全てのリソースを取得したい場合は、RESOURCETYPE_ANYを指定します。 dwUsageは、特定の用途のリソースを列挙したい場合に指定します。 用途を問わない場合は、0を指定します。 lpNetResourceは、リモート情報を格納したNETRESOURCE構造体のアドレスを指定します。 lphEnumは、リソースの列挙に使用するハンドルを指定します。

NETRESOURCE構造体は、次のように定義されています。

typedef struct _NETRESOURCE {
  DWORD  dwScope;
  DWORD  dwType;
  DWORD  dwDisplayType;
  DWORD  dwUsage;
  LPTSTR lpLocalName;
  LPTSTR lpRemoteName;
  LPTSTR lpComment;
  LPTSTR lpProvider;
}NETRESOURCE;

dwScopeは、WNetOpenEnumの第1引数と同じ意味を持ちます。 dwTypeは、WNetOpenEnumの第2引数と同じ意味を持ちます。 dwDisplayTypeは、 dwUsageは、WNetOpenEnumの第3引数と同じ意味を持ちます。 lpLocalNameは、リソースをローカルドライブにマッピングする場合に指定します。 lpRemoteNameは、リソースを公開しているリモートコンピュータの名前などを指定します。 WNetEnumResourceは、このメンバに列挙したリソース名を格納します。 lpCommentは、ネットワークプロバイダのコメントを指定します。 lpProviderは、使用するネットワークプロバイダの名前を指定します。 NULLを指定した場合は、適切なネットワークプロバイダが関数によって決定されます。

ネットワークプロバイダとは、ネットワークを認識するDLLです。 実際にネットワークへアクセスするのはWNet関数ではなくこのネットワークプロバイダであり、 WNet関数を実装するmpr.dllの目的は、関数の呼び出しを適切なネットワークプロバイダへ中継することです。 たとえば、アプリケーションがWNetOpenEnumを呼び出すと、 適切なネットワークプロバイダのNPOpenEnumという関数が呼ばれる仕組みになっています。 MPRとは、Multiple Provider Routerの略ですが、 正にこの名前が示す通り、ネットワークプロバイダへの中継や伝達を担当しているわけです。 なお、一概にネットワークプロバイダといっても、 レジストリにおける登録の場所によっては、認証情報や接続の通知を受け取るDLLにもなります。

WNetOpenEnumでハンドルを取得すれば、WNetEnumResourceでリモートコンピュータ上のリソースを列挙できるようになります。

DWORD WNetEnumResource(
  HANDLE hEnum,
  LPDWORD lpcCount,
  LPVOID lpBuffer,
  LPDWORD lpBufferSize
);

hEnumは、WNetOpenEnumが返したハンドルを指定します。 lpcCountは、列挙されたリソースの数を受け取る変数のアドレスを指定します。 できるだけ多くのリソースを列挙したい場合は、-1を格納しておきます。 lpBufferは、列挙されたリソースを受け取るバッファを指定します。 NULLを指定して、バッファのサイズを取得する方法は使用できません。 lpBufferSizeは、バッファのサイズを格納した変数のアドレスを指定します。

WNetOpenEnumで取得したハンドルが不要になった場合は、WNetCloseEnumで閉じることになります。

DWORD WNetCloseEnum(
  HANDLE hEnum
);

hEnumは、WNetOpenEnumで取得したハンドルになります。

WNet関数は、関数が成功した場合にNO_ERRORを返します。 一方、関数が失敗した場合にはERROR_INVALID_PARAMETERなどの一般的なエラー値を返しますが、 一部の関数ではERROR_EXTENDED_ERRORという値を返すことがあります。 これは、ネットワークプロバイダ内で問題が発生したことを意味し、 WNetGetLastErrorを呼び出して詳細を取得することができます。

DWORD WNetGetLastError(
  LPDWORD lpError,
  LPTSTR lpErrorBuf,
  DWORD nErrorBufSize,
  LPTSTR lpNameBuf,
  DWORD nNameBufSize
);

lpErrorは、ネットワークプロバイダから返されるエラー値を受け取る変数のアドレスを指定します。 lpErrorBufは、エラーの内容を表す文字列を受け取るバッファを指定します。 nErrorBufSizeは、lpErrorBufのサイズを指定します。 lpNameBufは、エラーを発生させたネットワークプロバイダの名前を受け取るバッファを指定します。 nNameBufSizeは、lpNameBufのサイズを指定します。

今回のプログラムは、特定のドメインまたはワークルグループのメンバであるコンピュータを列挙します。 WNet関数を呼び出すには、mpr.libへのリンクが必要になります。

#include <windows.h>

#pragma comment (lib, "mpr.lib")

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HWND hwndListBox = NULL;

	switch (uMsg) {

	case WM_CREATE: {
		BYTE          buffer[1024];
		DWORD         i;
		DWORD         dwSize = sizeof(buffer);
		DWORD         dwCount = (DWORD)-1;
		DWORD         dwError;
		HANDLE        hEnum;
		NETRESOURCE   netResource;
		LPNETRESOURCE lpNetResource = (LPNETRESOURCE)buffer;
		
		hwndListBox = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		
		netResource.dwScope       = RESOURCE_GLOBALNET;
		netResource.dwType        = RESOURCETYPE_ANY;
		netResource.dwDisplayType = RESOURCEDISPLAYTYPE_DOMAIN;
		netResource.dwUsage       = 0;
		netResource.lpLocalName   = 0;
		netResource.lpRemoteName  = TEXT("WORKGROP");
		netResource.lpComment     = NULL;
		netResource.lpProvider    = NULL;
		
		dwError = WNetOpenEnum(RESOURCE_GLOBALNET, RESOURCETYPE_ANY, 0, &netResource, &hEnum);
		if (dwError != NO_ERROR) {
			TCHAR szBuf[256];
			TCHAR szName[256];
			
			if (dwError == ERROR_EXTENDED_ERROR)
				WNetGetLastError(&dwError, szBuf, sizeof(szBuf) / sizeof(TCHAR), szName, sizeof(szName) / sizeof(TCHAR));
			else
				wsprintf(szBuf, TEXT("%d"), dwError);
			MessageBox(NULL, szBuf, NULL, MB_ICONWARNING);
			return -1;
		}

		WNetEnumResource(hEnum, &dwCount, buffer, &dwSize);
		WNetCloseEnum(hEnum);

		for (i = 0; i < dwCount; i++)
			SendMessageW(hwndListBox, LB_ADDSTRING, 0, (LPARAM)lpNetResource[i].lpRemoteName);

		return 0;
	}

	case WM_SIZE:
		MoveWindow(hwndListBox, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

WNetOpenEnumを呼び出すには、NETRESOURCE構造体を事前に初期化しておく必要があります。 このとき、dwScope、dwType、dwUsageについては、 WNetOpenEnumの第1引数から第3引数までと同一の値にしておきます。 dwDisplayTypeに指定しているRESOURCEDISPLAYTYPE_SERVERは、サーバーを列挙することを意味しており、 このときにlpRemoteNameにドメイン名またはワークグループ名を指定すると、 メンバであるコンピュータが列挙されることになります。 残りのメンバについては、0及びNULLで問題ありません。 WNetEnumResourceでは、列挙された情報を受け取れるだけのバッファを第3引数に指定しています。

今回はコンピュータの列挙にWNetOpenEnumを呼び出していますが、 この関数にはそれ以外の目的にも使用することができます。 次に、共有リソースを列挙する場合の例を示します。 簡単のため、不要なメンバの初期化は省略しています。

ZeroMemory(&netResource, sizeof(NETRESOURCE));

netResource.dwScope       = RESOURCE_GLOBALNET;
netResource.dwType        = RESOURCETYPE_DISK;
netResource.dwDisplayType = RESOURCEDISPLAYTYPE_SHARE;
netResource.lpRemoteName  = L"\\\\ComputerName";

dwDisplayTypeにRESOURCEDISPLAYTYPE_SHAREを指定し、 lpRemoteNameにコンピュータ名を指定すれば、 対象のコンピュータ上に存在する共有リソースが列挙されることになります。 ただし、NetShareEnumと異なり、管理共有は列挙されません。 次に、システムで構成されているネットワークを列挙する例を示します。

ZeroMemory(&netResource, sizeof(NETRESOURCE));

netResource.dwScope       = RESOURCE_GLOBALNET;
netResource.dwType        = RESOURCETYPE_ANY;
netResource.dwDisplayType = RESOURCEDISPLAYTYPE_NETWORK;
netResource.lpRemoteName  = NULL;

dwDisplayTypeにRESOURCEDISPLAYTYPE_NETWORKを指定し、 lpRemoteNameにNULLを指定すれば、ネットワークが列挙されることになります。 次に、特定のネットワーク上に存在するドメインまたはワークグループを列挙する例を示します。

ZeroMemory(&netResource, sizeof(NETRESOURCE));

netResource.dwScope       = RESOURCE_GLOBALNET;
netResource.dwType        = RESOURCETYPE_ANY;
netResource.dwDisplayType = RESOURCEDISPLAYTYPE_DOMAIN;
netResource.lpRemoteName  = NULL;
netResource.lpProvider    = TEXT("Microsoft Windows Network");

lpProviderに目的のネットワーク名を指定し、 dwDisplayTypeにRESOURCEDISPLAYTYPE_DOMAIN、lpRemoteNameにNULLを指定すれば、 そのネットワーク上に存在するドメインまたはワークグループが列挙されます。


戻る