1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Posted at

カスタムアクションとして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++クラス(あるいは構造体)である。

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();
};
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を利用すればよい。

// カスタムアクション関数
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;
}
1
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?