EternalWindows
COMサーバー / 接続ポイントサンプル

今回作成するカスタムインターフェースは、ファイルのコピーを行うCopyFileというメソッドが用意されています。 この関数は内部でWindows APIのCopyFileExを呼び出しますが、 もしクライアントがイベントの通知を望むようになっているならば、 このコピーの経過をクライアントへ通知します。 まず、IDLファイルから確認します。

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

[
	object,
	dual,
	uuid(A09AFC29-31EA-462a-9DF0-40A7D2D16E76)
]
interface IFileControl : IDispatch
{
	typedef [v1_enum] enum tagFILEMODE {
		FM_READ = 0,
		FM_WRITE = 1
	} FILEMODE;

	[id(1)] HRESULT CreateFile([in] BSTR bstrFileName, [in] FILEMODE filemode); 
	[id(2)] HRESULT ReadFile([in] DWORD dwLength, [out, retval] BSTR *lp);
	[id(3)] HRESULT WriteFile([in] BSTR bstrData, [in] DWORD dwLength);
	[id(4)] HRESULT CloseFile();
	[id(5), propput] HRESULT FilePos([in] DWORD dwPos);
	[id(6)] HRESULT CopyFile([in] BSTR bstrExist, [in] BSTR bstrNew);
}

[
	uuid(CFE6456E-8930-416d-B169-09B06AC9ECE2),
	version(1.0),
	helpstring("sample TypeLib")
]
library MyServerLib
{
	importlib("stdole2.tlb");

	[
		uuid(0CB3C76F-FDDB-455c-AE14-D2179FFCA729)
	]
	dispinterface IFileControlEvents
	{
	properties:
	methods:
		[id(1)] void CopyEvent([in] LARGE_INTEGER li);
	}

	[
		uuid(79BDE8FF-CEE2-4c6d-A7B2-BED85A67A708)
	]
	coclass MyServer
	{
		[default] interface IFileControl;
		[default, source] dispinterface IFileControlEvents;
	}
}

cpp_quote("#define DISPID_COPYEVENT 1")

前節との変更点はIFileControlにCopyFileというメソッドが追加されている点と、 IFileControlEventsというディスパッチインターフェースを定義している点です。 dispinterfaceで定義されたインターフェースは、pFileControlEvents->CopyEvent()のような使い方をするためのものではなく、 単に一連のプロパティやメソッドを識別する指標に過ぎません。 より簡単に言えば、IDispatch::InvokeのDISPIDが1であるならば、コピー系のイベントの通知であるということを示しているだけです。 dispinterfaceのヘッダにはobjectを記述しないようにし、ボディにはpropertiesとmethodsを記述します。 propertiesとmethodsは必ずこの順番になっていなければならず、プロパティが存在しない場合でも名前は記述するようにします。 dispinterfaceの記述をlibrary内で行えばDIID_IFileControlEventsというIIDが定義され、 これはクライアントがオブジェクトに 接続する際に必要になります。 cpp_quoteによって、DISPID_COPYEVENTを定義している点も重要です。 これにより、ヘッダーファイルにDISPID_COPYEVENTが定義され、クライアントも参照できるようになります。 次にDLLのdefファイルを示します。

LIBRARY	"mydll"

EXPORTS
	DllCanUnloadNow PRIVATE
	DllGetClassObject PRIVATE
	DllRegisterServer PRIVATE
	DllUnregisterServer PRIVATE

次に、ヘッダーファイルを示します。

#include "sample_h.h"

class CConnectionPoint;

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

	STDMETHODIMP GetTypeInfoCount(UINT *pctinfo);
	STDMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo);
	STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId);
	STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr);

	STDMETHODIMP CreateFile(BSTR bstrFileName, FILEMODE filemode);
	STDMETHODIMP ReadFile(DWORD dwLength, BSTR *lp);
	STDMETHODIMP WriteFile(BSTR bstrData, DWORD dwLength);
	STDMETHODIMP CloseFile();
	STDMETHODIMP put_FilePos(DWORD dwPos);
	STDMETHODIMP CopyFile(BSTR bstrExist, BSTR bstrNew);

	STDMETHODIMP EnumConnectionPoints(IEnumConnectionPoints **ppEnum);
	STDMETHODIMP FindConnectionPoint(REFIID riid, IConnectionPoint **ppCP);

	static DWORD CALLBACK CopyProgressRoutine(
		LARGE_INTEGER TotalFileSize, LARGE_INTEGER TotalBytesTransferred,
		LARGE_INTEGER StreamSize, LARGE_INTEGER StreamBytesTransferred, DWORD dwStreamNumber,
		DWORD dwCallbackReason, HANDLE hSourceFile, HANDLE hDestinationFile, LPVOID lpData
	);
	
	CMyServer();
	~CMyServer();
	
private:
	LONG             m_cRef;
	HANDLE           m_hFile;
	ITypeInfo        *m_pTypeInfo;
	CConnectionPoint *m_pConnectionPoint;
};

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

	STDMETHODIMP GetConnectionInterface(IID *pIID);
	STDMETHODIMP GetConnectionPointContainer(IConnectionPointContainer **ppCPC);
	STDMETHODIMP Advise(IUnknown *pUnkSink, DWORD *pdwCookie);
	STDMETHODIMP Unadvise(DWORD dwCookie);
	STDMETHODIMP EnumConnections(IEnumConnections **ppEnum);

	void NotifyEvent(LARGE_INTEGER li);

private:
	LONG      m_cRef;
	IDispatch *m_pEventSink;
};

class CMyServerFactory : 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 void LockModule(BOOL bLock);
extern BOOL CreateRegistryKey(HKEY hKeyRoot, LPTSTR lpszKey, LPTSTR lpszValue, LPTSTR lpszData);
extern void GetGuidString(REFGUID rguid, LPTSTR lpszGuid);

次に、ソースファイルを示します。

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

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

#define EVENT_COOKIE 100

const TCHAR g_szProgid[] = TEXT("Sample.MyServer2");

LONG      g_lLocks = 0;
HINSTANCE g_hinstDll = NULL;
TCHAR     g_szClsid[256];
TCHAR     g_szLibid[256];


// CMyServer


CMyServer::CMyServer()
{
	ITypeLib *pTypeLib;
	HRESULT  hr;

	m_cRef = 1;
	m_pTypeInfo = NULL;
	m_pConnectionPoint = NULL;

	LockModule(TRUE);

	hr = LoadRegTypeLib(LIBID_MyServerLib, 1, 0, 0, &pTypeLib);
	if (SUCCEEDED(hr)) {
		pTypeLib->GetTypeInfoOfGuid(IID_IFileControl, &m_pTypeInfo);
		pTypeLib->Release();
	}
}

CMyServer::~CMyServer()
{
	if (m_pTypeInfo != NULL)
		m_pTypeInfo->Release();

	LockModule(FALSE);
}

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

	if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IDispatch) || IsEqualIID(riid, IID_IFileControl))
		*ppvObject = static_cast<IFileControl *>(this);
	else if (IsEqualIID(riid, IID_IConnectionPointContainer))
		*ppvObject = static_cast<IConnectionPointContainer *>(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::GetTypeInfoCount(UINT *pctinfo)
{
	*pctinfo = m_pTypeInfo != NULL ? 1 : 0;
	
	return S_OK;
}

STDMETHODIMP CMyServer::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo)
{
	if (m_pTypeInfo == NULL)
		return E_NOTIMPL;

	m_pTypeInfo->AddRef();
	*ppTInfo = m_pTypeInfo;

	return S_OK;
}

STDMETHODIMP CMyServer::GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId)
{
	if (m_pTypeInfo == NULL)
		return E_NOTIMPL;

	return m_pTypeInfo->GetIDsOfNames(rgszNames, cNames, rgDispId);
}

STDMETHODIMP CMyServer::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
{
	void *p = static_cast<IFileControl *>(this);
	
	if (m_pTypeInfo == NULL)
		return E_NOTIMPL;

	return m_pTypeInfo->Invoke(p, dispIdMember, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr);
}

STDMETHODIMP CMyServer::CreateFile(BSTR bstrFileName, FILEMODE filemode)
{
	DWORD dwDesiredAccess;
	DWORD dwCreationDisposition;

	if (filemode == FM_READ) {
		dwDesiredAccess = GENERIC_READ;
		dwCreationDisposition = OPEN_EXISTING;
	}
	else if (filemode == FM_WRITE) {
		dwDesiredAccess = GENERIC_WRITE;
		dwCreationDisposition = CREATE_ALWAYS;
	}
	else
		return E_FAIL;

	m_hFile = ::CreateFileW(bstrFileName, dwDesiredAccess, 0, NULL, dwCreationDisposition, FILE_ATTRIBUTE_NORMAL, NULL);

	return HRESULT_FROM_WIN32(GetLastError());
}

STDMETHODIMP CMyServer::ReadFile(DWORD dwLength, BSTR *lp)
{
	DWORD  dwReadByte;
	LPSTR  lpszA;
	LPWSTR lpszW;

	lpszA = (LPSTR)CoTaskMemAlloc(dwLength + 1);
	::ReadFile(m_hFile, lpszA, dwLength, &dwReadByte, NULL);
	lpszA[dwReadByte] = '\0';

	dwReadByte++;
	lpszW = (LPWSTR)CoTaskMemAlloc(dwReadByte * sizeof(WCHAR));

	MultiByteToWideChar(CP_ACP, 0, lpszA, -1, lpszW, dwReadByte);
	*lp = SysAllocString(lpszW);

	CoTaskMemFree(lpszA);
	CoTaskMemFree(lpszW);

	return S_OK;
}

STDMETHODIMP CMyServer::WriteFile(BSTR bstrData, DWORD dwLength)
{
	DWORD dw, dwWriteByte;
	LPSTR lpszA;

	dw = SysStringLen(bstrData) + 1;
	lpszA = (LPSTR)CoTaskMemAlloc(dw);

	WideCharToMultiByte(CP_ACP, 0, bstrData, -1, lpszA, dw, NULL, NULL);
	::WriteFile(m_hFile, lpszA, dwLength, &dwWriteByte, NULL);
	
	CoTaskMemFree(lpszA);

	return HRESULT_FROM_WIN32(GetLastError());
}

STDMETHODIMP CMyServer::CloseFile()
{
	CloseHandle(m_hFile);

	return HRESULT_FROM_WIN32(GetLastError());
}

STDMETHODIMP CMyServer::put_FilePos(DWORD dwPos)
{
	SetFilePointer(m_hFile, dwPos, NULL, FILE_CURRENT);

	return HRESULT_FROM_WIN32(GetLastError());
}

STDMETHODIMP CMyServer::CopyFile(BSTR bstrExist, BSTR bstrNew)
{
	BOOL bCancel = FALSE;

	CopyFileExW(bstrExist, bstrNew, CopyProgressRoutine, (LPVOID)m_pConnectionPoint, &bCancel, 0);

	return HRESULT_FROM_WIN32(GetLastError());
}

STDMETHODIMP CMyServer::EnumConnectionPoints(IEnumConnectionPoints **ppEnum)
{
	return E_NOTIMPL;
}

STDMETHODIMP CMyServer::FindConnectionPoint(REFIID riid, IConnectionPoint **ppCP)
{
	if (!IsEqualIID(riid, DIID_IFileControlEvents))
		return E_FAIL;
	
	m_pConnectionPoint = new CConnectionPoint;

	*ppCP = m_pConnectionPoint;

	return S_OK;
}

DWORD CALLBACK CMyServer::CopyProgressRoutine(
	LARGE_INTEGER TotalFileSize, LARGE_INTEGER TotalBytesTransferred,
	LARGE_INTEGER StreamSize, LARGE_INTEGER StreamBytesTransferred, DWORD dwStreamNumber,
	DWORD dwCallbackReason, HANDLE hSourceFile, HANDLE hDestinationFile, LPVOID lpData)
{
	CConnectionPoint *pConnectionPoint = (CConnectionPoint *)lpData;

	if (pConnectionPoint != NULL)
		pConnectionPoint->NotifyEvent(TotalBytesTransferred);

	return PROGRESS_CONTINUE;
}


// CConnectionPoint


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

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

	AddRef();
	
	return S_OK;
}

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

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

	return m_cRef;
}

STDMETHODIMP CConnectionPoint::GetConnectionInterface(IID *pIID)
{
	return E_NOTIMPL;
}

STDMETHODIMP CConnectionPoint::GetConnectionPointContainer(IConnectionPointContainer **ppCPC)
{
	return E_NOTIMPL;
}

STDMETHODIMP CConnectionPoint::Advise(IUnknown *pUnkSink, DWORD *pdwCookie)
{
	pUnkSink->QueryInterface(IID_PPV_ARGS(&m_pEventSink));
	if (m_pEventSink == NULL)
		return E_FAIL;

	*pdwCookie = EVENT_COOKIE;

	return S_OK;
}

STDMETHODIMP CConnectionPoint::Unadvise(DWORD dwCookie)
{
	if (dwCookie != EVENT_COOKIE)
		return E_FAIL;

	m_pEventSink->Release();
	m_pEventSink = NULL;

	return S_OK;
}

STDMETHODIMP CConnectionPoint::EnumConnections(IEnumConnections **ppEnum)
{
	return E_NOTIMPL;
}

void CConnectionPoint::NotifyEvent(LARGE_INTEGER li)
{
	VARIANT    var, varResult;
	DISPPARAMS dispParams;
	EXCEPINFO  excepInfo;
	UINT       uArgErr;

	if (m_pEventSink == NULL)
		return;

	var.vt = VT_I8;
	var.llVal = li.QuadPart;
	dispParams.cArgs = 1;
	dispParams.rgvarg = &var;

	m_pEventSink->Invoke(DISPID_COPYEVENT, IID_NULL, 0, DISPATCH_METHOD, &dispParams, &varResult, &excepInfo, &uArgErr);
}


// CMyServerFactory


STDMETHODIMP CMyServerFactory::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) CMyServerFactory::AddRef()
{
	LockModule(TRUE);

	return 2;
}

STDMETHODIMP_(ULONG) CMyServerFactory::Release()
{
	LockModule(FALSE);

	return 1;
}

STDMETHODIMP CMyServerFactory::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 CMyServerFactory::LockServer(BOOL fLock)
{
	LockModule(fLock);

	return S_OK;
}


// DLL Export


STDAPI DllCanUnloadNow(void)
{
	return g_lLocks == 0 ? S_OK : S_FALSE;
}

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppv)
{
	static CMyServerFactory serverFactory;
	HRESULT hr;

	*ppv = NULL;
	
	if (IsEqualCLSID(rclsid, CLSID_MyServer))
		hr = serverFactory.QueryInterface(riid, ppv);
	else
		hr = CLASS_E_CLASSNOTAVAILABLE;

	return hr;
}

STDAPI DllRegisterServer(void)
{
	TCHAR    szModulePath[256], szKey[256];
	WCHAR    szTypeLibPath[256];
	HRESULT  hr;
	ITypeLib *pTypeLib;

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

	GetModuleFileName(g_hinstDll, szModulePath, sizeof(szModulePath) / sizeof(TCHAR));
	wsprintf(szKey, TEXT("CLSID\\%s\\InprocServer32"), g_szClsid);
	if (!CreateRegistryKey(HKEY_CLASSES_ROOT, szKey, NULL, szModulePath))
		return E_FAIL;
	
	wsprintf(szKey, TEXT("CLSID\\%s\\InprocServer32"), g_szClsid);
	if (!CreateRegistryKey(HKEY_CLASSES_ROOT, szKey, TEXT("ThreadingModel"), TEXT("Apartment")))
		return E_FAIL;
	
	wsprintf(szKey, TEXT("CLSID\\%s\\ProgID"), g_szClsid);
	if (!CreateRegistryKey(HKEY_CLASSES_ROOT, szKey, NULL, (LPTSTR)g_szProgid))
		return E_FAIL;

	wsprintf(szKey, TEXT("%s"), g_szProgid);
	if (!CreateRegistryKey(HKEY_CLASSES_ROOT, szKey, NULL, TEXT("COM Server Sample2")))
		return E_FAIL;
  
	wsprintf(szKey, TEXT("%s\\CLSID"), g_szProgid);
	if (!CreateRegistryKey(HKEY_CLASSES_ROOT, szKey, NULL, (LPTSTR)g_szClsid))
		return E_FAIL;
	
	GetModuleFileNameW(g_hinstDll, szTypeLibPath, sizeof(szTypeLibPath) / sizeof(TCHAR));
	hr = LoadTypeLib(szTypeLibPath, &pTypeLib);
	if (SUCCEEDED(hr)) {
		hr = RegisterTypeLib(pTypeLib, szTypeLibPath, NULL);
		if (SUCCEEDED(hr)) {
			wsprintf(szKey, TEXT("CLSID\\%s\\TypeLib"), g_szClsid);
			if (!CreateRegistryKey(HKEY_CLASSES_ROOT, szKey, NULL, g_szLibid)) {
				pTypeLib->Release();
				return E_FAIL;
			}
		}	
		pTypeLib->Release();
	}

	return S_OK;
}

STDAPI DllUnregisterServer(void)
{
	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);
	
	UnRegisterTypeLib(LIBID_MyServerLib, 1, 0, LOCALE_NEUTRAL, SYS_WIN32);

	return S_OK;
}

BOOL WINAPI DllMain(HINSTANCE hinstDll, DWORD dwReason, LPVOID lpReserved)
{
	switch (dwReason) {

	case DLL_PROCESS_ATTACH:
		g_hinstDll = hinstDll;
		DisableThreadLibraryCalls(hinstDll);

		GetGuidString(CLSID_MyServer, g_szClsid);
		GetGuidString(LIBID_MyServerLib, g_szLibid);
		return TRUE;
	}

	return TRUE;
}


// Function


void LockModule(BOOL bLock)
{
	if (bLock)
		InterlockedIncrement(&g_lLocks);
	else
		InterlockedDecrement(&g_lLocks);
}

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;
}

void GetGuidString(REFGUID rguid, LPTSTR lpszGuid)
{
	LPWSTR lpsz;

	StringFromCLSID(rguid, &lpsz);
#ifdef UNICODE
	lstrcpyW(lpszGuid, lpsz);
#else
	WideCharToMultiByte(CP_ACP, 0, lpsz, -1, lpszGuid, 256, NULL, NULL);
#endif
	CoTaskMemFree(lpsz);
}

COMでは、オブジェクトからクライアントへイベントを通知するための標準として、 接続ポイントと呼ばれる仕組みを用意しています。 この仕組みでは、クライアントが通知を望むイベントのIIDをオブジェクトに渡すことができ、 オブジェクトはこのイベントをサポートする別のオブジェクト(接続ポイント)をクライアントに返すことができます。 この動作を満たすためには、オブジェクトがIConnectionPointContainerを継承し、 FindConnectionPointを適切に実装しなければなりません。

STDMETHODIMP CMyServer::FindConnectionPoint(REFIID riid, IConnectionPoint **ppCP)
{
	if (!IsEqualIID(riid, DIID_IFileControlEvents))
		return E_FAIL;
	
	m_pConnectionPoint = new CConnectionPoint;

	*ppCP = m_pConnectionPoint;

	return S_OK;
}

接続ポイントを必要とするクライアントは、オブジェクトからIConnectionPointContainerを照会し、 FindConnectionPointに通知を望むイベントのIIDを指定します。 今回のサーバーは、DIID_IFileControlEventsに関連するイベントを通知できますから、 このときには接続ポイントの役割を果たすCConnectionPointを返すようにしています。 このオブジェクトは、IConnectionPointを継承しています。

接続ポイントを取得したクライアントは、イベントの通知を望むためにIConnectionPoint::Adviseを呼び出します。

STDMETHODIMP CConnectionPoint::Advise(IUnknown *pUnkSink, DWORD *pdwCookie)
{
	pUnkSink->QueryInterface(IID_PPV_ARGS(&m_pEventSink));
	if (m_pEventSink == NULL)
		return E_FAIL;

	*pdwCookie = EVENT_COOKIE;

	return S_OK;
}

第1引数のオブジェクトは一般にイベントシンクと呼ばれ、 いわばイベントを受け取るためにクライアントが用意したオブジェクトです。 イベントの通知はIDispatchを通じて行うため、QueryInterfaceでそれを実装しているかを確認し、 さらにメンバとして保存するようにします。 pdwCookieは、イベントシンクと接続ポイントとの間で確立された接続を識別する値を指定します。 これは、任意の値で構いません。

クライアントは、イベントの通知が不要になった場合にIConnectionPoint::Unadviseを呼び出します。

STDMETHODIMP CConnectionPoint::Unadvise(DWORD dwCookie)
{
	if (dwCookie != EVENT_COOKIE)
		return E_FAIL;

	m_pEventSink->Release();
	m_pEventSink = NULL;

	return S_OK;
}

第1引数は、切断したくなった接続を示す値を指定します。 この値がAdviseで返した値と一致する場合は、接続のために取得したイベントシンクを開放します。

今回のサーバーでどのような際にイベントが通知されるかというと、ファイルがコピーされようとしているときです。 ファイルをコピーする際には、CopyFileが呼ばれます。

STDMETHODIMP CMyServer::CopyFile(BSTR bstrExist, BSTR bstrNew)
{
	BOOL bCancel = FALSE;

	CopyFileExW(bstrExist, bstrNew, CopyProgressRoutine, (LPVOID)m_pConnectionPoint, &bCancel, 0);

	return HRESULT_FROM_WIN32(GetLastError());
}

このメソッドでは、Windows APIのCopyFileExを呼び出してコピーを実行しようとしています。 第3引数はコールバック関数のアドレスであり、この関数にはコピーの途中経過が送られるようになっています。 コールバック関数の実装は次のようになっています。

DWORD CALLBACK CMyServer::CopyProgressRoutine(
	LARGE_INTEGER TotalFileSize, LARGE_INTEGER TotalBytesTransferred,
	LARGE_INTEGER StreamSize, LARGE_INTEGER StreamBytesTransferred, DWORD dwStreamNumber,
	DWORD dwCallbackReason, HANDLE hSourceFile, HANDLE hDestinationFile, LPVOID lpData)
{
	CConnectionPoint *pConnectionPoint = (CConnectionPoint *)lpData;

	if (pConnectionPoint != NULL)
		pConnectionPoint->NotifyEvent(TotalBytesTransferred);

	return PROGRESS_CONTINUE;
}

最終引数のlpDataには、CopyFileExの第4引数が渡されます。 これはCConnectionPoint型の変数を指定していたため、CConnectionPointでキャストすることができます。 NotifyEventは、イベントの通知をクライアントへ行うためのメソッドです。 第1引数は現在まででコピーした総バイト数になります。

void CConnectionPoint::NotifyEvent(LARGE_INTEGER li)
{
	VARIANT    var, varResult;
	DISPPARAMS dispParams;
	EXCEPINFO  excepInfo;
	UINT       uArgErr;

	if (m_pEventSink == NULL)
		return;

	var.vt = VT_I8;
	var.llVal = li.QuadPart;
	dispParams.cArgs = 1;
	dispParams.rgvarg = &var;

	m_pEventSink->Invoke(DISPID_COPYEVENT, IID_NULL, 0, DISPATCH_METHOD, &dispParams, &varResult, &excepInfo, &uArgErr);
}

m_pEventSinkがNULLであるということは、クライアントがAdviseを呼び出していないということですから、 イベントの通知を行う必要はありません。 IDispatch::Invokeの第1引数はイベントを識別するDISPIDを指定し、 第4引数にはDISPATCH_METHODを指定します。 第5引数は通知するデータであり、DISPPARAMS.cArgsに1を指定したらDISPPARAMS.rgvargに1個のVARIANT構造体を指定します。 渡したいデータはこれまでコピーした総サイズであり、 これはLONNLONG型で表すことができるため、vtにVT_I8を指定します。 そして、llValにLONNLONG型の値を指定します。

クライアントの例

今回作成したサーバーを使用するクライアントの例を示します。

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

class CEventSink : public IDispatch
{
public:
	STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject);
	STDMETHODIMP_(ULONG) AddRef();
	STDMETHODIMP_(ULONG) Release();
	
	STDMETHODIMP GetTypeInfoCount(UINT *pctinfo);
	STDMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo);
	STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId);
	STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr);

	CEventSink();

private:
	LONG m_cRef;
};

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	HRESULT                   hr;
	IFileControl              *pFileControl;
	TCHAR                     szBuf[256];
	BSTR                      bstrExist, bstrNew;
	DWORD                     dwCookie;
	IConnectionPointContainer *pConnectionPointContainer;
	IConnectionPoint          *pConnectionPoint;
	CEventSink                *pEventSink;

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

	hr = pFileControl->QueryInterface(IID_PPV_ARGS(&pConnectionPointContainer));
	if (FAILED(hr)) {
		pFileControl->Release();
		CoUninitialize();
		return 0;
	}
	pConnectionPointContainer->FindConnectionPoint(DIID_IFileControlEvents, &pConnectionPoint);
	pConnectionPointContainer->Release();

	pEventSink = new CEventSink();
	pConnectionPoint->Advise(pEventSink, &dwCookie);

	bstrExist = SysAllocString(L"C:\\exist.txt");
	bstrNew = SysAllocString(L"C:\\new.txt");
	pFileControl->CopyFile(bstrExist, bstrNew);
	
	MessageBox(NULL, TEXT("終了します。"), TEXT("OK"), MB_OK);

	SysFreeString(bstrNew);
	SysFreeString(bstrExist);
	pConnectionPoint->Unadvise(dwCookie);
	pConnectionPoint->Release();
	pEventSink->Release();
	pFileControl->Release();
	CoUninitialize();
	
	return 0;
}


// CEventSink


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

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

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

	AddRef();
	
	return S_OK;
}

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

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

	return m_cRef;
}

STDMETHODIMP CEventSink::GetTypeInfoCount(UINT *pctinfo)
{
	*pctinfo = 0;
	
	return S_OK;
}

STDMETHODIMP CEventSink::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo)
{
	return E_NOTIMPL;
}

STDMETHODIMP CEventSink::GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId)
{
	return E_NOTIMPL;
}

STDMETHODIMP CEventSink::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
{
	TCHAR szBuf[256];

	if (dispIdMember == DISPID_COPYEVENT) {
		wsprintf(szBuf, TEXT("TotalBytesTransferred : %d"), pDispParams->rgvarg[0].llVal);
		MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);
	}

	return S_OK;
}

このクライアントは、IFileControl::CopyFileを呼び出してファイルのコピーを行うのが目的ですが、 その前にイベントを受信するための処理を行っています。 まず、オブジェクトからIConnectionPointContainerを取得し、 FindConnectionPointを呼び出してイベントを通知できるIConnectionPointを取得します。 次に、IConnectionPoint::Adviseを呼び出してイベントを通知するよう指示しますが、 このためにはイベントシンク(イベントを受け取るオブジェクト)を用意しておかなければなりません。 イベントはIDispatchによって通知されるため、CEventSinkは必ずIDispatchを実装しておきます。 準備が整ったらCopyFileを呼び出しますが、このメソッドが制御を返す前にはCEventSink::Invokeが何回か呼ばれるはずです。



戻る