meideru blog

家電メーカーで働いているmeideruのブログです。主に技術系・ガジェット系の話を書いています。

【Windows API】リソースをバイナリで出力する方法

 

Windows向けにプログラムを書いているときに、とあるやりたいことに出くわしました。

内容は以下のようなことです。

やりたいこと

実行ファイルAを実行すると、デスクトップに実行ファイルBを作成する。

ただし、作成する実行ファイルBは、元の実行ファイルAがリソースとして保持していなければならない。

@イメージ図
実行ファイルのリソースから取り出す。

結論から言うと、この問題は解決できました!

なかなかピンポイントかつマニアックな内容ですが、この記事で解説しようと思います(^o^)

目次

方針

まず、実行ファイルBを実行ファイルAにリソースとして保持させます。

実行ファイルAは内部リソースの実行ファイルBを、バイナリ形式で読み取り、新しくファイルを作り、その中にfwriteで書き込みます。

これで、解決できました(・o・)

解説

具体的に解説していこうと思います。

実行ファイルBを作成

まず、サンプルとして実行ファイルBを作成します。

ファイルの内容は以下の通りです。

// 【実行ファイルB】
// メッセージボックスで
//「実行ファイルBです!」と表示する

#define _CRT_SECURE_NO_WARNINGS

#include <Windows.h>

// WIndowsプログラムのエントリーポイント
int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst,
	LPSTR lpsCmdLine, int nCmdShow)
{
	// メッセージボックス
	MessageBox(
		NULL,
		L"実行ファイルBです!",
		L"実行ファイルB",
		MB_ICONINFORMATION | MB_OK);

	return 0;
}

実行ファイルBは、クリックすると「実行ファイルBです!」というメッセージボックスが現れるという単純なプログラムです。

実行ファイルAを作成

次に、本題の実行ファイルAの作成に取り掛かります。

まずは、リソースに実行ファイルBを持たせましょう。リソースファイルとリソースヘッダに以下の記述を追加します。(リソースファイルとリソースヘッダの書き方がわからない方は調べてみてください!)

 

リソースファイルに以下の記述を追加します。

IDR_EXE1                EXE                     "exeB.exe"

リソースの名前(ID)はIDR_EXE1、リソースの種類はEXEにします。最後に、実行ファイルBの名前はexeB.exeなので、”exeB.exe”と記述します。

 

リソースヘッダに以下の記述を追加します。

#define IDR_EXE1                        101

IDR_EXE1の値は101と定義します。

 

最後に、main関数の記述を書きます!

// 【実行ファイルA】
// デスクトップに実行ファイルBを作成する

#define _CRT_SECURE_NO_WARNINGS

#include <Windows.h>
#include <stdio.h>
#include "resource.h"

// WIndowsプログラムのエントリーポイント
int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst,
	LPSTR lpsCmdLine, int nCmdShow)
{
	// ---------- リソースに関して ---------- //

	// リソースのハンドルを取得(リソースの場所を特定)
	const HRSRC hResInfo = FindResource(
		hCurInst,					// モジュール(リソース)のハンドル
		MAKEINTRESOURCE(IDR_EXE1),	// リソースの名前 
		L"EXE");					// リソースの種類

	// リソースを読みだす(リソースに関連付けられたデータのハンドルを取得)
	const HGLOBAL hResData = LoadResource(
		hCurInst,	// モジュール(リソース)のハンドル
		hResInfo);	// リソースのハンドル

	// リソースデータへのポインタを取得
	const LPVOID pvResData = LockResource(hResData);


	// ------- ファイルの作成に関して -------- //

	// ファイルポインタ
	FILE *fp;

	// ファイルオープン(書き込みモードでオープン)
	fp = fopen(
		"C:\\Users\\meideru\\Desktop\\exeB.exe",	// exeB.exeを作成するパス
		"wb");										// 書き込みモード
	if (fp == NULL)		// ファイルオープンできなかった
	{
		return 0;
	}

	// リソースの容量を取得
	DWORD ResSize = SizeofResource(
		hCurInst,	// モジュール(リソース)のハンドル
		hResInfo);	// リソースのハンドル

	// ファイルの書き込み
	fwrite(
		pvResData,	// 書き込むファイルのポインタ
		ResSize,	// 書き込むファイルのサイズ
		1,			// 書き込むファイルの個数
		fp);		// 書き込み先のファイルポインタ

	// ファイルクローズ
	fclose(fp);

	return 0;
}

 

上のソースを用いて少し、具体的に説明します。

 

初めに、リソースヘッダをインクルードしてください。忘れると実行できません^^;

#include "resource.h"

 

リソースハンドルを取得します。これは、リソースの場所を特定しているのに等しいです。

// リソースのハンドルを取得(リソースの場所を特定)
const HRSRC hResInfo = FindResource(
	hCurInst,			// モジュール(リソース)のハンドル
	MAKEINTRESOURCE(IDR_EXE1),	// リソースの名前 
	L"EXE");			// リソースの種類

モジュール(リソース)のハンドルは、winmain関数の引数と同じものでよいです。

それと、本来ならば、MAKEINTRESOURCE(IDR_EXE1)の代わりにL”IDR_EXE1″としても良いはずなのですが、これをやると何故か上手く動作しませんでした。デバッグして確認したところ、リソースのハンドルの場所がNULLで返ってきてしまいました。原因は不明です^^;

もし知っている方がいれば、是非ともコメントで教えてください^^;

 

リソースを読み出します。

// リソースを読みだす(リソースに関連付けられたデータのハンドルを取得)
const HGLOBAL hResData = LoadResource(
	hCurInst,	// モジュール(リソース)のハンドル
	hResInfo);	// リソースのハンドル

リソースに関連付けられたデータのハンドルを取得しています。

 

リソースデータのポインタを取得します。

// リソースデータへのポインタを取得
const LPVOID pvResData = LockResource(hResData);

これで、pvResDataにリソースデータのポインタ変数が格納されます。

 

以上で完成です!

ビルドして実行ファイルA実行すると、無事、デスクトップに実行ファイルBが作成されるはずです。

 

最後に・・・

ちょっと話は変わります。勘の良い方はあることに、気づかれたかと思います。それは、「メモリの開放」についてです。

LoadResourceでリソースを読みだし、LockResourceでリソースデータへのポインタを取得した時点で、メモリを動的に確保していることになりますよね。

動的にメモリを確保したときは、プログラマがメモリを開放しなければいけません。なのに、上のプログラムだと開放していません。

結論から言うと、win32apiでは開放しなくて良いそうです。自動でやってくれるとMSDNに書いてありました。

以下はコピペです。

Win32 アプリケーションでは、LoadResource 関数でロードしたリソースを解放する必要がありません。LoadResource 関数で取得したリソースは、それをロードしたモジュールがアンロードされると自動的に解放されます。

出典:https://msdn.microsoft.com/ja-jp/library/cc364598.aspx

 

ノシ

 - 技術系