EternalWindows
汎用データ転送 / データの設定

オブジェクトからIDataObjectを取得することができれば、 そのオブジェクトからデータを取得するだけでなく、 オブジェクトに対してデータを設定することもできます。 データを設定するにはSetDataを呼び出します。

HRESULT IDataObject::SetData(
  FORMATETC *pformatetc,
  STGMEDIUM *pmedium,
  BOOL fRelease
);

pformatetcは、データのフォーマットを格納したFORMATETC構造体のアドレスを指定します。 pmediumは、設定するデータを格納したSTGMEDIUM構造体のアドレスを指定します。 fReleaseは、データをメソッドに側に開放させるかどうかを指定します。 TRUEの場合はメソッドがデータを開放しますが、FALSEの場合は開放されません。

今回のプログラムの内容を考えていた当初は、Excelに対してCsv形式のデータを設定しようと思っていました。 ただ、実際に試したところこれは上手くいかなかったため、様々なオブジェクトを試してみた結果、 Wordのオブジェクトを使用することにしました。 理由は次の通りです。

オブジェクトのProgID 説明
Word.Document Wordのオブジェクトは、CF_TEXT、CF_RTF、CF_HTMLのフォーマットをサポートする。 SetDataの呼び出しは確かに成功し、作成されたファイルにも問題はない。
Excel.Sheet Excelのオブジェクトは様々なフォーマットをサポートし、それらをSetDataに指定するとメソッドは確かに成功する。 しかし、作成したファイルの中身を確認すると、Excelのシートが作成されていない。
Word.RTF.8 ワードパッドのオブジェクトは、CF_TEXT、CF_RTF、CF_HTMLのフォーマットをサポートする。 しかし、実際にSetDataを呼び出してみるとアクセス違反を示すエラーが返る。
Paint.Picture ペイントのオブジェクトは、SetDataで設定できるフォーマットが存在しない。

オブジェクトがどのようなフォーマットをサポートしているか確認するには、 IDataObject::EnumFormatEtcでIEnumFORMATETCを取得します。 このとき、DATADIR_SETを指定すればSetDataで設定可能なフォーマットを列挙できることになり、 DATADIR_GETを指定すればGetDataで取得可能なフォーマットを列挙できることになります。 IEnumFORMATETC::Nextを呼び出すことで返されたFORMATETC構造体が、 オブジェクトがサポートしているフォーマットということになります。

今回のプログラムは、Word形式のファイルを作成します。

#include <windows.h>

BOOL LoadFile(LPOLESTR lpszHideFilePath, IUnknown *pUnknown);
BOOL SaveFile(LPOLESTR lpszOutFilePath, IUnknown *pUnknown);
BOOL ObjectClose(IUnknown *pUnknown);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	char        szText[] = "sample";
	char        *p;
	WCHAR       szOutFilePath[] = L"C:\\sample.docx";
	WCHAR       szHideFilePath[MAX_PATH];
	HRESULT     hr;
	CLSID       clsid;
	HGLOBAL     hglobal;
	FORMATETC   formatetc;
	STGMEDIUM   medium;
	IDataObject *pDataObject;

	OleInitialize(NULL);
	
	hr = CLSIDFromProgID(L"Word.Document", &clsid);
	if (FAILED(hr)) {
		OleUninitialize();
		return 0;
	}

	hr = CoCreateInstance(clsid, NULL, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&pDataObject));
	if (FAILED(hr)) {
		OleUninitialize();
		return 0;
	}

	GetCurrentDirectoryW(MAX_PATH, szHideFilePath);
	lstrcatW(szHideFilePath, L"\\temp.docx");
	if (!LoadFile(szHideFilePath, pDataObject)) {
		MessageBox(NULL, TEXT("ファイルのロードに失敗しました。"), NULL, MB_ICONWARNING);
		pDataObject->Release();
		OleUninitialize();
		return 0;
	}

	hglobal = GlobalAlloc(GPTR, lstrlenA(szText) + 1);
	p = (char *)GlobalLock(hglobal);
	lstrcpyA(p, szText);
	GlobalUnlock(hglobal);

	formatetc.cfFormat = CF_TEXT;
	formatetc.ptd      = NULL;
	formatetc.dwAspect = DVASPECT_CONTENT;
	formatetc.lindex   = -1;
	formatetc.tymed    = TYMED_HGLOBAL;

	medium.tymed = TYMED_HGLOBAL;
	medium.hGlobal = hglobal;
	medium.pUnkForRelease = NULL;

	hr = pDataObject->SetData(&formatetc, &medium, FALSE);
	if (FAILED(hr)) {
		MessageBox(NULL, TEXT("データの設定に失敗しました。"), NULL, MB_ICONWARNING);
		ObjectClose(pDataObject);
		DeleteFile(szHideFilePath);
		pDataObject->Release();
		OleUninitialize();
		return 0;
	}
	
	if (SaveFile(szOutFilePath, pDataObject)) 
		MessageBox(NULL, TEXT("ファイルを作成しました。"), TEXT("OK"), MB_OK);
	else
		MessageBox(NULL, TEXT("ファイルの作成に失敗しました。"), NULL, MB_ICONWARNING);

	ObjectClose(pDataObject);
	DeleteFile(szHideFilePath);
	GlobalFree(hglobal);
	pDataObject->Release();
	OleUninitialize();

	return 0;
}

BOOL LoadFile(LPOLESTR lpszHideFilePath, IUnknown *pUnknown)
{
	HRESULT      hr;
	HANDLE       hFile;
	IPersistFile *pPersistFile;

	hFile = CreateFile(lpszHideFilePath, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_HIDDEN, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
		return FALSE;
	CloseHandle(hFile);

	pUnknown->QueryInterface(IID_PPV_ARGS(&pPersistFile));
	hr = pPersistFile->Load(lpszHideFilePath, STGM_READWRITE);
	if (FAILED(hr)) {
		DeleteFile(lpszHideFilePath);
		pPersistFile->Release();
		return FALSE;
	}
	
	pPersistFile->Release();

	return TRUE;
}

BOOL SaveFile(LPOLESTR lpszOutFilePath, IUnknown *pUnknown)
{
	HRESULT      hr;
	IPersistFile *pPersistFile;

	pUnknown->QueryInterface(IID_PPV_ARGS(&pPersistFile));
	hr = pPersistFile->Save(lpszOutFilePath, FALSE);
	if (FAILED(hr)) {
		pPersistFile->Release();
		return FALSE;
	}

	pPersistFile->SaveCompleted(NULL);
	pPersistFile->Release();

	return TRUE;
}

BOOL ObjectClose(IUnknown *pUnknown)
{
	IOleObject *pOleObject;
	
	pUnknown->QueryInterface(IID_PPV_ARGS(&pOleObject));
	pOleObject->Close(OLECLOSE_NOSAVE);
	pOleObject->Release();
	
	return TRUE;
}

WordのProgIDから関連するCLSIDを取得し、 そのCLSIDを基にWordのオブジェクトを作成する点は問題ないと思われます。 これが成功すれば後はIDataObject::SetDataを呼び出せばよいように思えますが、 その前にオブジェクトを初期化しておく必要があります。 こうした初期化には、IPersistStreamInit::InitNewを使用することが多いと思われますが、 WordはIPersistStreamInitをサポートしていないため、 IPersistFile::Loadで初期化をすることになります。 Loadという自作関数は内部でIPersistFile::Loadを呼び出しますが、 このためにはファイルパスを指定しなければなりません。

GetCurrentDirectoryW(MAX_PATH, szHideFilePath);
lstrcatW(szHideFilePath, L"\\temp.docx");
if (!LoadFile(szHideFilePath, pDataObject)) {
	MessageBox(NULL, TEXT("ファイルのロードに失敗しました。"), NULL, MB_ICONWARNING);
	pDataObject->Release();
	OleUninitialize();
	return 0;
}

IPersistFile::Loadを成功させるためには、空でもよいのでWord形式のファイルを作成する必要があります。 ファイルの作成場所はカレントディレクトリとし、ファイルの拡張子は必ずWord形式にします。 LoadFileの内部は次のようになっています。

BOOL LoadFile(LPOLESTR lpszHideFilePath, IUnknown *pUnknown)
{
	HRESULT      hr;
	HANDLE       hFile;
	IPersistFile *pPersistFile;

	hFile = CreateFile(lpszHideFilePath, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_HIDDEN, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
		return FALSE;
	CloseHandle(hFile);

	pUnknown->QueryInterface(IID_PPV_ARGS(&pPersistFile));
	hr = pPersistFile->Load(lpszHideFilePath, STGM_READWRITE);
	if (FAILED(hr)) {
		DeleteFile(lpszHideFilePath);
		pPersistFile->Release();
		return FALSE;
	}
	
	pPersistFile->Release();

	return TRUE;
}

まず、CreateFileでファイルを作成するようにします。 このファイルはIPersistFile::Loadを成功するために作成したのであって、 プログラムの終了時には削除することになりますし、 実際にファイルシステム上で見える必要もないため、 FILE_ATTRIBUTE_HIDDENを指定します。 これで、隠しファイルとして作成されることになります。 作成したファイルのハンドルは、直ちにCloseHandleで閉じるようにします。 理由は、オープンしたままではIPersistFile::Loadに失敗するからです。

IPersistFile::Loadが成功したら、IDataObject::SetDataを呼び出すことができます。

hglobal = GlobalAlloc(GPTR, lstrlenA(szText) + 1);
p = (char *)GlobalLock(hglobal);
lstrcpyA(p, szText);
GlobalUnlock(hglobal);

formatetc.cfFormat = CF_TEXT;
formatetc.ptd      = NULL;
formatetc.dwAspect = DVASPECT_CONTENT;
formatetc.lindex   = -1;
formatetc.tymed    = TYMED_HGLOBAL;

medium.tymed = TYMED_HGLOBAL;
medium.hGlobal = hglobal;
medium.pUnkForRelease = NULL;

hr = pDataObject->SetData(&formatetc, &medium, FALSE);

FORMATETC構造体のcfFormatメンバにCF_TEXTを指定し、tymedメンバにTYMED_HGLOBALを指定しているため、 グローバルメモリにテキストデータを指定しておく必要があります。 具体的には、GlobalAllocで確保したメモリにlstrcpyAでデータをコピーし、 このメモリをSTGMEDIUM構造体のhGlobalメンバに指定します。 SetDataの第3引数にはFALSEを指定しているため、データはアプリケーションが明示的に開放することになります。

SaveFileという自作関数は、内部でIPersistFile::Saveを呼び出しています。 これによって、Wordのファイルが作成されることになります。 終了処理を行う際にはObjectCloseという自作関数を通じて、IOleObject::Closeを呼び出しています。 ファイルをロードしている場合はこれを行っておかないと、Wordのプロセスが存在したままになりますし、 一時的に作成した隠しファイルをDeleteFileで削除できないことになります。


戻る