EternalWindows
COMアプリケーション / COMサーバーサンプル

カスタムインターフェースを使用したアウトプロセスサーバーのサンプルを示します。 まず、IDLファイルを示します。

import "oaidl.idl";
import "ocidl.idl";

[
	object,
	uuid(084163AA-B996-4e4d-9A01-E8F6CD07DC02)
]
interface IWindowControl : IUnknown
{
	HRESULT Show([in] BOOL bShow); 
	HRESULT GetTitle([out] LPWSTR *lplp);
}

[
	uuid(BEB05848-960C-47b5-8BC9-05AC52AAA549)
]
coclass MyServer
{
	[default] interface IWindowControl;
}

cpp_quote("DEFINE_GUID(CLSID_MyServer, 0xbeb05848, 0x960c, 0x47b5, 0x8b, 0xc9, 0x5, 0xac, 0x52, 0xaa, 0xa5, 0x49);")

このIDLファイルは、今回のサーバーだけでなく、クライアントもプロキシ/スタブDLLも使用することになります。 IDLファイルに使われる属性の意味については、COMサーバーの章を参照してください。 次にヘッダーファイルを示します。

#include "sample_h.h"

class CMyServer : public IWindowControl
{
public:
	STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject);
	STDMETHODIMP_(ULONG) AddRef();
	STDMETHODIMP_(ULONG) Release();

	STDMETHODIMP Show(BOOL bShow);
	STDMETHODIMP GetTitle(LPWSTR *lplp);
	
	CMyServer();
	~CMyServer();
	
private:
	LONG m_cRef;
};

class CClassFactory : public IClassFactory
{
public:
	STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject);
	STDMETHODIMP_(ULONG) AddRef();
	STDMETHODIMP_(ULONG) Release();
	
	STDMETHODIMP CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppvObject);
	STDMETHODIMP LockServer(BOOL fLock);
};

extern BOOL RegisterServer();
extern BOOL UnregisterServer();
extern BOOL CreateRegistryKey(HKEY hKeyRoot, LPTSTR lpszKey, LPTSTR lpszValue, LPTSTR lpszData);

extern HWND g_hwnd;

IDLファイルがsample.idlという名前なら、それをコンパイルすることでsample_h.hというファイルが作成されます。 このヘッダーファイルにはインターフェースの定義が含まれているため、必ずインクルードするようにします。 次にソースファイル(sample.cpp)を示します。

#include <windows.h>
#include <shlwapi.h>
#include <shlobj.h>
#include "sample.h"

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

const TCHAR g_szClsid[] = TEXT("{BEB05848-960C-47b5-8BC9-05AC52AAA549}");
const TCHAR g_szProgid[] = TEXT("Sample.LocalServer");

HWND g_hwnd = NULL;

BOOL RegisterAndCreateWindow(HINSTANCE hinst, int nCmdShow);
int MessageLoop();
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	HRESULT       hr;
	DWORD         dwRegister;
	CClassFactory classFactory;
	int           nResult;

	if ((lstrcmpA(lpszCmdLine, "-RegServer") == 0) || (lstrcmpA(lpszCmdLine, "/RegServer") == 0)) {
		if (RegisterServer())
			MessageBox(NULL, TEXT("登録に成功しました。"), TEXT("OK"), MB_OK);
		else
			MessageBox(NULL, TEXT("登録に失敗しました。"), NULL, MB_ICONWARNING);
		return 0;
	}
	else if ((lstrcmpA(lpszCmdLine, "-UnregServer") == 0) || (lstrcmpA(lpszCmdLine, "/UnregServer") == 0)) {
		UnregisterServer();
		MessageBox(NULL, TEXT("登録を解除しました。"), TEXT("OK"), MB_OK);
		return 0;
	}
	else
		;

	CoInitializeEx(NULL, COINIT_MULTITHREADED);
	
	RegisterAndCreateWindow(hinst, nCmdShow);
	
	hr = CoRegisterClassObject(CLSID_MyServer, static_cast<IClassFactory *>(&classFactory), CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &dwRegister);
	if (FAILED(hr)) {
		CoUninitialize();
		return 0;
	}
	
	nResult = MessageLoop();
	
	CoRevokeClassObject(dwRegister);
	CoUninitialize();

	return nResult;
}

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

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

BOOL RegisterAndCreateWindow(HINSTANCE hinst, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	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 FALSE;

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

	g_hwnd = hwnd;

	return TRUE;
}

int MessageLoop()
{
	MSG msg;
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}



// CClassFactory


STDMETHODIMP CClassFactory::QueryInterface(REFIID riid, void **ppvObject)
{
	*ppvObject = NULL;

	if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IClassFactory))
		*ppvObject = static_cast<IClassFactory *>(this);
	else
		return E_NOINTERFACE;

	AddRef();
	
	return S_OK;
}

STDMETHODIMP_(ULONG) CClassFactory::AddRef()
{
	return 2;
}

STDMETHODIMP_(ULONG) CClassFactory::Release()
{
	return 1;
}

STDMETHODIMP CClassFactory::CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppvObject)
{
	CMyServer *p;
	HRESULT   hr;

	*ppvObject = NULL;

	if (pUnkOuter != NULL)
		return CLASS_E_NOAGGREGATION;

	p = new CMyServer();
	if (p == NULL)
		return E_OUTOFMEMORY;

	hr = p->QueryInterface(riid, ppvObject);
	p->Release();

	return hr;
}

STDMETHODIMP CClassFactory::LockServer(BOOL fLock)
{	
	return S_OK;
}


// Function


BOOL RegisterServer()
{
	TCHAR szModulePath[MAX_PATH];
	TCHAR szKey[256];

	wsprintf(szKey, TEXT("CLSID\\%s"), g_szClsid);
	if (!CreateRegistryKey(HKEY_CLASSES_ROOT, szKey, NULL, TEXT("COM Server Sample")))
		return FALSE;

	GetModuleFileName(NULL, szModulePath, sizeof(szModulePath) / sizeof(TCHAR));
	wsprintf(szKey, TEXT("CLSID\\%s\\LocalServer32"), g_szClsid);
	if (!CreateRegistryKey(HKEY_CLASSES_ROOT, szKey, NULL, szModulePath))
		return FALSE;

	wsprintf(szKey, TEXT("CLSID\\%s\\ProgID"), g_szClsid);
	if (!CreateRegistryKey(HKEY_CLASSES_ROOT, szKey, NULL, (LPTSTR)g_szProgid))
		return FALSE;

	wsprintf(szKey, TEXT("%s"), g_szProgid);
	if (!CreateRegistryKey(HKEY_CLASSES_ROOT, szKey, NULL, TEXT("COM Server Sample")))
		return FALSE;
  
	wsprintf(szKey, TEXT("%s\\CLSID"), g_szProgid);
	if (!CreateRegistryKey(HKEY_CLASSES_ROOT, szKey, NULL, (LPTSTR)g_szClsid))
		return FALSE;

	return TRUE;
}

BOOL UnregisterServer()
{
	TCHAR szKey[256];

	wsprintf(szKey, TEXT("CLSID\\%s"), g_szClsid);
	SHDeleteKey(HKEY_CLASSES_ROOT, szKey);
	
	wsprintf(szKey, TEXT("%s"), g_szProgid);
	SHDeleteKey(HKEY_CLASSES_ROOT, szKey);

	return TRUE;
}

BOOL CreateRegistryKey(HKEY hKeyRoot, LPTSTR lpszKey, LPTSTR lpszValue, LPTSTR lpszData)
{
	HKEY  hKey;
	LONG  lResult;
	DWORD dwSize;

	lResult = RegCreateKeyEx(hKeyRoot, lpszKey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL);
	if (lResult != ERROR_SUCCESS)
		return FALSE;

	if (lpszData != NULL)
		dwSize = (lstrlen(lpszData) + 1) * sizeof(TCHAR);
	else
		dwSize = 0;

	RegSetValueEx(hKey, lpszValue, 0, REG_SZ, (LPBYTE)lpszData, dwSize);
	RegCloseKey(hKey);
	
	return TRUE;
}

次に、ソースファイル(object.cpp)を示します。

#include <windows.h>
#include "sample.h"

CMyServer::CMyServer()
{
	m_cRef = 1;
}

CMyServer::~CMyServer()
{
}

STDMETHODIMP CMyServer::QueryInterface(REFIID riid, void **ppvObject)
{
	*ppvObject = NULL;

	if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IWindowControl))
		*ppvObject = static_cast<IWindowControl *>(this);
	else
		return E_NOINTERFACE;

	AddRef();
	
	return S_OK;
}

STDMETHODIMP_(ULONG) CMyServer::AddRef()
{
	return InterlockedIncrement(&m_cRef);
}

STDMETHODIMP_(ULONG) CMyServer::Release()
{
	if (InterlockedDecrement(&m_cRef) == 0) {
		delete this;
		return 0;
	}

	return m_cRef;
}

STDMETHODIMP CMyServer::Show(BOOL bShow)
{
	ShowWindow(g_hwnd, bShow ? SW_SHOW : SW_HIDE);
	
	return S_OK;
}

STDMETHODIMP CMyServer::GetTitle(LPWSTR *lplp)
{
	int nLen;

	nLen = GetWindowTextLengthW(g_hwnd) + 1;

	*lplp = (LPWSTR)CoTaskMemAlloc(nLen * sizeof(WCHAR));

	GetWindowText(g_hwnd, *lplp, nLen);

	return S_OK;
}

作成したサーバーがCoCreateInstanceやCoGetClassObjectで起動されるようになるためには、 レジストリにサーバーの情報を登録しなければなりません。 これは次のようにして行います。

C:\\sample.exe -RegServer

上記のように、exeファイルの後にスペースを空けて-RegServerという文字列を指定します。 失敗する場合は管理者として実行しているかを確認してください。 登録を解除する場合は次のようになります。

C:\\sample.exe -UnregServer

-UnregServerを指定することによって、登録を解除することができます。

今回のサーバーのオブジェクトはカスタムインターフェースを実装しているため、 サーバーだけでなくプロキシ/スタブDLLの情報もレジストリに登録する必要があります。 これには、今回示したIDLファイルを基に前節で示した作業を行います。

クライアントの作成例

今回作成したサーバーを操作する例を示します。

#include <windows.h>
#include "sample_h.h"

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	HRESULT        hr;
	IWindowControl *pWindowControl;
	TCHAR          szBuf[256];
	LPWSTR         lpszTitle;
	
	CoInitialize(NULL);

	hr = CoCreateInstance(CLSID_MyServer, NULL, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&pWindowControl));
	if (FAILED(hr)) {
		wsprintf(szBuf, TEXT("IWindowControlの取得に失敗しました。%08x"), hr);
		MessageBox(NULL, szBuf, NULL, MB_ICONWARNING);
		CoUninitialize();
		return 0;
	}

	pWindowControl->Show(TRUE);

	pWindowControl->GetTitle(&lpszTitle);
	MessageBox(NULL, lpszTitle, TEXT("OK"), MB_OK);
	CoTaskMemFree(lpszTitle);
	
	pWindowControl->Release();
	CoUninitialize();
	
	return 0;
}

サーバーはアウトプロセスサーバーとして実装されていたため、CoCreateInstanceにはCLSCTX_LOCAL_SERVERを指定することになります。 CoCreateInstanceが成功した場合はサーバーが起動されることになりますが、 サーバーのウインドウが実際に表示されることになるのは、IWindowControl::Showを呼び出してからです。 IWindowControl::GetTitleを呼び出せば、ウインドウのタイトルを取得することができます。

IWindowControl::Showでウインドウを表示するようにしても、それはフォアグラウンドに表示されないはずです。 これを行うには、ShowでSetForegroundWindowを呼び出すようになっていればよいのですが、 クライアントの意思によってフォアグラウンドに設定するのもよいといえます。 この場合は、サーバーのオブジェクトがIForegroundTransferを実装し、 AllowForegroundTransferメソッドでSetForegroundWindowを呼び出すようにします。 そうするとクライアントは、IForegroundTransfer::AllowForegroundTransferかCoAllowSetForegroundWindowで、 サーバーのウインドウをフォアグラウンドに設定できます。



戻る