カスタムアクションとしてC/C++で書かれたDLL内の関数を呼び出すには、下記のようにBinary
タグでDLLをMSIファイル内に格納し、CustomAction
タグで呼び出す関数を指定すればよい。
<!-- 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ファイルに埋め込みばよい。
<Binary
Id="CommonHelper.dll"
SourceFile="[CommonHelper.dll のファイルパス]"
/>
依存先DLLの展開・ロード
Binary
タグによりMSIファイル内に格納されたファイルは、カスタムアクションのDLLであれば、Windowsインストーラによって自動的に展開されるが、そうでないDLLは自前でMSIファイル内のBinary
テーブルより読み出し、ファイルシステム上に展開する必要がある。下記のコードは、Binary
タグによりMSIファイル内に組み込まれたCommonHelper.dll
をロードし、DLL内に定義された関数を呼び出すためのC++クラス(あるいは構造体)である。
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();
};
#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
を利用すればよい。
// カスタムアクション関数
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;
}