【WiX Toolset】カスタムアクション内で他のDLLを呼び出す

カスタムアクションとしてC/C++で書かれたDLL内の関数を呼び出すには、下記のように```Binary```タグでDLLをMSIファイル内に格納し、```CustomAction```タグで呼び出す関数を指定すればよい。

```xml
<!-- BinaryタグでDLLをMSI内に格納 -->
<Binary
    Id="CustomActions.dll" 
    SourceFile="[CustomActions.dll のファイルパス]"
    />

<!-- CustomActionタグでDLL内の関数を指定 -->
<CustomAction
    Id="CustomActionFunc"
    BinaryKey="CustomActions.dll"
    DllEntry="CustomActionFunc"
    Execute="immediate"
    Return="check"
    />
```

もしカスタムアクションとして記述された関数の動作が他のDLLに依存する場合、カスタムアクションの実行時に依存先DLLもユーザーのマシン上に存在する必要がある。他の製品でも使用されているDLLであれば、運良くユーザーのマシン上にインストールされていることもあるかもしれないが、カスタムアクションを定義したDLLと一緒に依存先DLLも配布するのが確実だろう。

### 依存先DLLのMSIファイル内への格納

依存先DLLをカスタムアクションを定義したDLLと一緒に配布するには、依存先DLLもMSIファイル内に格納する必要がある。依存先DLLもインストール対象ファイルの一部としてコンポーネントに含めてしまえば、MSIファイル内にDLLを格納しユーザーのマシン上にコピーすることが可能だが、それではDLL内の関数を呼び出せるタイミングが、インストール対象ファイルのコピーより後に限られてしまう。任意のタイミングでDLL内の関数を呼び出したければ、依存先DLLも下記のように```Binary```タグによってMSIファイルに埋め込みばよい。
```xml
<Binary
    Id="CommonHelper.dll" 
    SourceFile="[CommonHelper.dll のファイルパス]"
    />
```

### 依存先DLLの展開・ロード

```Binary```タグによりMSIファイル内に格納されたファイルは、カスタムアクションのDLLであれば、Windowsインストーラによって自動的に展開されるが、そうでないDLLは自前でMSIファイル内の```Binary```テーブルより読み出し、ファイルシステム上に展開する必要がある。下記のコードは、```Binary```タグによりMSIファイル内に組み込まれた```CommonHelper.dll```をロードし、DLL内に定義された関数を呼び出すためのC++クラス(あるいは構造体)である。

```cpp:CommonHelperLoader.h
struct CommonHelperLoader
{
    // デストラクタ
	~CommonHelperLoader();

    // DLLをロードし、関数を呼び出せる状態にする。
    //   hInstall ... [in] 現在のインストーラのハンドル
	HRESULT Load(MSIHANDLE hInstall);
    // DLLで定義された関数 HelperFunc を呼び出す。
	HRESULT CallHelperFunc();

private:

    // 一時ファイルとして保存されたDLLのファイルパス
	std::wstring tempDllFilePath{};
	// DLLのモジュールハンドル
	HMODULE hDLL{ nullptr };

    // MSIからDLLのデータを取得する。
    //   hInstall ... [in] 現在のインストーラのハンドル
    //   data     ... [out] DLLデータ
    //   dataSize ... [out] DLLデータのサイズ
	HRESULT GetDLLData(MSIHANDLE hInstall, std::unique_ptr<char[]>& data, DWORD& dataSize);
    // DLLデータを書き込むための一時ファイルを作成する
	HRESULT CreateTempDLLFile();
	// DLLデータを一時ファイルに書き込む。
    //   data     ... [in] DLLデータ
    //   dataSize ... [in] DLLデータのサイズ
	HRESULT WriteDLLData(char *data, DWORD dataSize);
	// 一時ファイルよりDLLをロードする。
	HRESULT LoadDLL();
};
```

```cpp:CommonHelperLoader.cpp
#include "CommonHelperLoader.h"

typedef BOOL(WINAPI* PHELPER_FUNC)();

// デストラクタ
CommonHelperLoader::~CommonHelperLoader()
{
	if (this->hDLL)
	{
	    // DLLを開放
		FreeLibrary(this->hDLL);
	}
	if (!this->tempDllFilePath.empty())
	{
	    // 一時ファイルを削除
		DeleteFile(this->tempDllFilePath.c_str());
	}
}

//
// Public Functions
//

// DLLをロードし、関数を呼び出せる状態にする。
HRESULT CommonHelperLoader::Load(MSIHANDLE hInstall)
{
    // プライベート関数を順に呼び出し。
    
	std::unique_ptr<char[]> dllData;
	DWORD dllSize{ 0 };
	HRESULT hResult;

	hResult = GetDLLData(hInstall, dllData, dllSize);
	RETURN_IF_FAILED(hResult);

	hResult = GetTempDLLFile();
	RETURN_IF_FAILED(hResult);

	hResult = WriteDLLData(dllData.get(), dllSize);
	RETURN_IF_FAILED(hResult);

	hResult = LoadDLL();
	RETURN_IF_FAILED(hResult);

	return S_OK;
}

// DLLで定義された関数 HelperFunc を呼び出す。
HRESULT CommonHelperLoader::CallHelperFunc()
{
    // 関数のアドレスを取得
	auto pProc = (PHELPER_FUNC)GetProcAddress(this->hDLL, "HelperFunc");
	if (pProc == nullptr)
	{
		DWORD err = GetLastError();
		return HRESULT_FROM_WIN32(err);
	}

    // 関数を呼び出し
	if (!pProc())
	{
        return E_FAIL;
	}

	return S_OK;
}

//
// Private Functions
//

// MSIからDLLのデータを取得する。
HRESULT CommonHelperLoader::GetDLLData(MSIHANDLE hInstall, std::unique_ptr<char[]>& data, DWORD& dataSize)
{
	UINT ret;

	MSIHANDLE hDatabase{ 0 };
	MSIHANDLE hView{ 0 };
	MSIHANDLE hRecord{ 0 };

    //各HANDLEのクリーンアップ。(関数のスコープを抜けた時に実行)
	auto cleanUp = wil::scope_exit(
		[&hDatabase, &hView, &hRecord] {
			if (hRecord)
			{
				MsiCloseHandle(hRecord);
			}
			if (hView)
			{
				MsiCloseHandle(hView);
			}
			if (hDatabase)
			{
			    MsiCloseHandle(hDatabase);
			}
		}
	);

    // 現在のMSIデータベースへのハンドルを取得
	hDatabase = MsiGetActiveDatabase(hInstall);
	if (!hDatabase)
	{
		return E_FAIL;
	}

    // Binary テーブルから CommonHelper.dll のデータを取得クエリを作成。
	ret = MsiDatabaseOpenView(hDatabase,
		L"select `Data` from `Binary` where `Name` = 'CommonHelper.dll'", &hView);
	if (ret != ERROR_SUCCESS)
	{
		return HRESULT_FROM_WIN32(ret);
	}

    // クエリを実行
	ret = MsiViewExecute(hView, 0);
	if (ret != ERROR_SUCCESS)
	{
		return HRESULT_FROM_WIN32(ret);
	}

    // クエリより1行フェッチ
	ret = MsiViewFetch(hView, &hRecord);
	if (ret != ERROR_SUCCESS)
	{
		return HRESULT_FROM_WIN32(ret);
	}

    // 1列目のデータサイズを取得
	ret = MsiRecordDataSize(hRecord, 1);
	if (!ret)
	{
		return E_FAIL;
	}

    // 取得したデータサイズ分のバッファを確保。バッファはOUTPUTの引数にセットして呼び出し元に返す。
    // データサイズもOUTPUTの引数にセットし、呼び出し元に返す。
	data = std::unique_ptr<char[]>(new char[ret]);
	dataSize = ret;

    // 1列目のデータをバッファに読み込み。
	DWORD bufSize = ret;
	ret = MsiRecordReadStream(hRecord, 1, data.get(), &bufSize);
	if (ret != ERROR_SUCCESS)
	{
		return HRESULT_FROM_WIN32(ret);
	}

	return S_OK;
}

// DLLデータを書き込むための一時ファイルを作成する
HRESULT CommonHelperLoader::GetTempDLLFile()
{
	WCHAR tempPath[MAX_PATH + 1];
	UINT ret;

    // TEMPフォルダのパスを取得
	if (!GetTempPath(MAX_PATH + 1, tempPath))
	{
		DWORD err = GetLastError();
		return HRESULT_FROM_WIN32(err);
	}

    // 一時ファイルのパスを取得。この時点でファイルが作成される。
	ret = GetTempFileName(tempPath, L"CMH", 0, tempPath);
	if (!ret)
	{
		DWORD err = GetLastError();
		return HRESULT_FROM_WIN32(err);
	}

	this->tempDllFilePath = tempPath;

	return S_OK;
}

// DLLデータを一時ファイルに書き込む。
HRESULT CommonHelperLoader::WriteDLLData(char* data, DWORD dataSize)
{
    // 一時ファイルをオープン
	wil::unique_handle hDllFile{ 
		CreateFile(
			this->tempDllFilePath.c_str(), 
			GENERIC_WRITE, 
			0, 
			nullptr, 
			OPEN_EXISTING, 
			FILE_ATTRIBUTE_NORMAL, 
			nullptr) 
	};
	
	if (hDllFile.get() == INVALID_HANDLE_VALUE)
	{
		DWORD err = GetLastError();
		return HRESULT_FROM_WIN32(err);
	}
	
	// 一時ファイルにデータを書き込み
	if (!WriteFile(hDllFile.get(), data, dataSize, nullptr, nullptr))
	{
		DWORD err = GetLastError();
		return HRESULT_FROM_WIN32(err);
	}

	return S_OK;
}

// 一時ファイルよりDLLをロードする。
HRESULT CommonHelperLoader::LoadDLL()
{
	auto hModule = LoadLibrary(this->tempDllFilePath.c_str());
	if (hModule == nullptr)
	{
		DWORD err = GetLastError();
		return HRESULT_FROM_WIN32(err);
	}

	this->hDLL = hModule;

	return S_OK;
}
```

### カスタムアクションからの依存先DLL利用

カスタムアクションから依存先DLL(```CommonHelper.dll```)の関数を呼び出すには、カスタムアクション関数から前節に掲載したクラス```CommonHelperLoader```を利用すればよい。

```cpp
// カスタムアクション関数
UINT WINAPI CustomActionFunc(MSIHANDLE hInstall)
{
    HRESULT hResult

	CommonHelperLoader loader{};

    // CommonHelper.dll をロード
	hResult = loader.Load(hInstall);
	if (FAILED(hResult))
	{
		return ERROR_INSTALL_FAILURE;
	}

    // CommonHelper.dll の関数 HelperFunc を呼び出す。
	hResult = loader.CallHelperFunc();
	if (FAILED(hResult))
	{
		return ERROR_INSTALL_FAILURE;
	}

	return ERROR_SUCCESS;
}
```