EternalWindows
汎用データ転送 / データの変更通知

アプリケーションの通信をDDEから汎用データ転送に変更したいような場合、 当然ながら汎用データ転送はある1つの条件を満たしておく必要があります。 それは、DDEでできることが、汎用データ転送でできなくなってはならないという事です。 たとえば、DDEではサーバー内のデータの変更をWM_DDE_DATAで受信できますが、 こうした通知メカニズムも汎用データ転送はサポートしておかなければなりません。 今回は、このようなサーバーから通知を受け取る方法を見ていくことにします。

COMで何らかのイベントを受け取りたい場合は、イベントシンクと呼ばれるオブジェクトを作成することが一般的です。 具体的には、IConnectionPointContainerでサーバーの接続ポイントを探し、それとイベントシンクを接続することで、 サーバーから通知を受け取るようにするのです。 ただし、汎用データ転送はこのようなメカニズムが考えられる前に設計されていたため、 IDataObject自体に接続のためのメソッドが含まれています。 そして、このメソッドにアドバイズシンクを呼ばれるオブジェクトを指定することで、 データの変更を非同期に受信できます。 データオブジェクトとアドバイズシンクを結ぶ接続はアドバイズ接続と呼ばれ、 IDataObject::DAdviseで確立することができます。

HRESULT IDataObject::DAdvise(
  FORMATETC *pformatetc,
  DWORD advf,
  IAdviseSink *pAdvSink,
  DWORD *pdwConnection
);

pformatetcは、通知を受け取りたいフォーマットを指定します。 advfは、どのように通知を受け取るかを示す定数を指定します。 pAdvSinkは、IAdviseSinkを実装したオブジェクトのアドレスを指定します。 通知が送られる際には、IAdviseSink::OnDataChangeが呼ばれることになります。 pdwConnectionは、接続を識別する値を受け取る変数のアドレスを指定します。

IDataObject::DAdviseに指定可能な定数を次に示します。 これらは、組み合わせて指定することもできます。

通知の種類 説明
0 OnDataChangeが呼ばれた場合に、データが格納されるようにする。
ADVF_NODATA OnDataChangeが呼ばれた場合に、データが格納されないようにする。 このとき、STGMEDIUM構造体のtymedにはTYMED_NULLが格納される。 データが格納されていないからといって、OnDataChangeでIDataObject::GetDataを呼び出してもRPC_E_CANTCALLOUT_INASYNCCALLが返ることになる。 理由は、COMが非同期実行の際にプロセスをまたがるメソッドの呼び出しを許可していないからである。 データを取得したい場合はウインドウへ独自のメッセージをポストし、 それを取得した際にIDataObject::GetDataを呼び出すのが一般的である。
ADVF_PRIMEFIRST 0を指定する場合と基本的に同一だが、DAdviseが成功した時点でOnDataChangeが呼ばれる。
ADVF_ONLYONCE データが変更された場合に、一度だけOnDataChangeを呼び出す。 OnDataChangeが呼ばれると、アドバイズ接続は自動で解除される。

IAdviseSinkにはOnDataChange以外にもメソッドがありますが、 IDataObject::DAdviseだけしか呼び出していない場合は、 OnDataChangeしか呼ばれないことに注意してください。

IDataObject::DAdviseで確立したアドバイズ接続は、通知が不要になった際にIDataObject::DUnadviseで解除することができます。

HRESULT IDataObject::DUnadvise(
  DWORD dwConnection
);

dwConnectionは、接続を識別する値を指定します。

今回のプログラムは、起動中のExcelのデータが変更された場合に、その変更通知を受け取ります。 GetObjectFromROTという関数に指定されているファイルを、事前にExcelで開いておいてください。

#include <windows.h>

class CAdviseSink : public IAdviseSink
{
public:
	STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject);
	STDMETHODIMP_(ULONG) AddRef();
	STDMETHODIMP_(ULONG) Release();
	
	STDMETHODIMP_(void) OnDataChange(FORMATETC *pFormatetc, STGMEDIUM *pStgmed);
	STDMETHODIMP_(void) OnViewChange(DWORD dwAspect, LONG lindex);
	STDMETHODIMP_(void) OnRename(IMoniker *pmk);
	STDMETHODIMP_(void) OnSave();
	STDMETHODIMP_(void) OnClose();
	
	CAdviseSink();
	~CAdviseSink();

private:
	LONG m_cRef;
};

BOOL GetObjectFromROT(LPOLESTR lpszName, IUnknown **ppUnknown);
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 IAdviseSink *pAdviseSink = NULL;
	static IDataObject *pDataObject = NULL;
	static DWORD       dwConnection = 0;

	switch (uMsg) {

	case WM_CREATE: {
		FORMATETC formatetc;
		IUnknown  *pUnknown;

		OleInitialize(NULL);
		
		if (!GetObjectFromROT(L"C:\\sample.xlsx", &pUnknown))
			return -1;
		
		pUnknown->QueryInterface(IID_PPV_ARGS(&pDataObject));
		pUnknown->Release();

		pAdviseSink = new CAdviseSink;

		formatetc.cfFormat = (CLIPFORMAT)RegisterClipboardFormat(TEXT("Csv"));
		formatetc.ptd      = NULL;
		formatetc.dwAspect = DVASPECT_CONTENT;
		formatetc.lindex   = -1;
		formatetc.tymed    = TYMED_HGLOBAL;

		pDataObject->DAdvise(&formatetc, 0, pAdviseSink, &dwConnection);

		return 0;
	}

	case WM_DESTROY:
		if (dwConnection != NULL)
			pDataObject->DUnadvise(dwConnection);
		if (pDataObject != NULL)
			pDataObject->Release();
		if (pAdviseSink != NULL)
			pAdviseSink->Release();
		OleUninitialize();
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

BOOL GetObjectFromROT(LPOLESTR lpszName, IUnknown **ppUnknown)
{
	HRESULT             hr = E_FAIL;
	IMoniker            *pMoniker;
	IEnumMoniker        *pEnumMoniker;
	IBindCtx            *pBindCtx;
	IRunningObjectTable *pRunningObjectTable;
	LPOLESTR            lpszDisplayName;

	*ppUnknown = NULL;
	
	GetRunningObjectTable(0, &pRunningObjectTable);
	pRunningObjectTable->EnumRunning(&pEnumMoniker);
	
	CreateBindCtx(NULL, &pBindCtx);

	while(pEnumMoniker->Next(1, &pMoniker, NULL) == S_OK) {
		pMoniker->GetDisplayName(pBindCtx, NULL, &lpszDisplayName);
		if (lstrcmpW(lpszName, lpszDisplayName) == 0) {
			hr = pRunningObjectTable->GetObject(pMoniker, ppUnknown);
			pMoniker->Release();
			break;
		}
		pMoniker->Release();
	}
	
	pBindCtx->Release();
	pEnumMoniker->Release();
	pRunningObjectTable->Release();

	return hr == S_OK;
}


// CAdviseSink


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

CAdviseSink::~CAdviseSink()
{
}

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

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

	AddRef();
	
	return S_OK;
}

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

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

	return m_cRef;
}

STDMETHODIMP_(void) CAdviseSink::OnDataChange(FORMATETC *pFormatetc, STGMEDIUM *pStgmed)
{
	char *p;
	
	p = (char *)GlobalLock(pStgmed->hGlobal);
	MessageBoxA(NULL, (char *)p, "OnDataChange", MB_OK);
	GlobalUnlock(pStgmed->hGlobal);
}

STDMETHODIMP_(void) CAdviseSink::OnViewChange(DWORD dwAspect, LONG lindex)
{
}

STDMETHODIMP_(void) CAdviseSink::OnRename(IMoniker *pmk)
{
}

STDMETHODIMP_(void) CAdviseSink::OnSave()
{
}

STDMETHODIMP_(void) CAdviseSink::OnClose()
{
}

Excelは起動時に、ファイルの名前を基にしたモニカをROTに格納します。 これは、ROTから取得したモニカの名前が、アプリケーションの指定したExcelファイルと一致すれば、 そのファイルを開いているExcelが起動されていることを意味します。 よって、これを確認するためにGetObjectFromROTという自作関数を用意しています。 この関数は、第1引数のファイル名と一致したモニカを見つけた場合に、そのモニカから関連するオブジェクトを取得し、 このオブジェクトを第2引数に返します。 このオブジェクトはIDataObjectを実装しているため、QueryInterfaceは成功するはずです。

pUnknown->QueryInterface(IID_PPV_ARGS(&pDataObject));
pUnknown->Release();

pAdviseSink = new CAdviseSink;

formatetc.cfFormat = (CLIPFORMAT)RegisterClipboardFormat(TEXT("Csv"));
formatetc.ptd      = NULL;
formatetc.dwAspect = DVASPECT_CONTENT;
formatetc.lindex   = -1;
formatetc.tymed    = TYMED_HGLOBAL;

pDataObject->DAdvise(&formatetc, 0, pAdviseSink, &dwConnection);

IDataObjectを取得したら、IDataObject::DAdviseを呼び出すための準備に入ります。 まず、IAdviseSinkを実装するCAdviseSink型のオブジェクトを作成します。 このオブジェクトがなければ、データの変更通知を受け取ることができません。 続いて、通知を受け取りたいフォーマットを初期化することになります。 今回はCsv形式のデータをグローバルメモリで受け取りたいということで、上記のようにしています。 一通りの準備が終了すれば、IDataObject::DAdviseを呼び出すことになります。 第2引数に0を指定していることから、通知が送られる際には変更後のデータも送られるようになります。

IDataObject::DAdviseに指定したフォーマットのデータが変更された場合は、 IAdviseSink::OnDataChangeが呼ばれることになります。 今回はここで、変更後のデータを表示しています。

STDMETHODIMP_(void) CAdviseSink::OnDataChange(FORMATETC *pFormatetc, STGMEDIUM *pStgmed)
{
	char *p;
	
	p = (char *)GlobalLock(pStgmed->hGlobal);
	MessageBoxA(NULL, (char *)p, "OnDataChange", MB_OK);
	GlobalUnlock(pStgmed->hGlobal);
}

データはグローバルメモリで受け取るように指定していたため、 pStgmed->hGlobalから変更後のデータにアクセスできるようになります。


戻る