Visual Studio 2017 Visual C++ による Win32 DLL の開発 (リソースのみの DLL)

#はじめに
DLL には関数やクラスの他、リソースも含めることができます。そして、リソースだけからなる DLL も作成できます。

例えば、企業の業務用アプリ間でリソース DLL を共有することで、アイコンやビットマップ、メッセージなどを共通化するのに役立ちます。

#プロジェクトの作成
リソースのみの DLL のプロジェクトは、通常の(関数用の) Win32 DLL と同じです。「新しいプロジェクト」ダイアログで Fig.1 のように DLL プロジェクトを選び、名前を適切に変更して OK ボタンをクリックします。

![VCProj_Win32DLL.png](https://qiita-image-store.s3.amazonaws.com/0/62802/bf8c7331-658d-6e8f-7095-a6feec33bfa8.png)
Fig.1 新しいプロジェクトダイアログ

エントリーポイント (main 関数) は不要なので、プロジェクトのプロパティでエントリーポイントが存在しないように設定します。(Fig.2)

![ResouceDLLProp.png](https://qiita-image-store.s3.amazonaws.com/0/62802/e96ba73c-a698-6d9a-0958-a7f1ebd92978.png)
Fig.2 エントリーポイントなしの設定

プロジェクトは元々、関数の DLL 用なので、不要なコードが含まれています。そのコードをコメントアウトします。(ファイルは dllmain.cpp)


###不要なコードのコメントアウト (dllmain.cpp)

```
// dllmain.cpp : DLL アプリケーションのエントリ ポイントを定義します。
#include "stdafx.h"

//BOOL APIENTRY DllMain( HMODULE hModule,
//                       DWORD  ul_reason_for_call,
//                       LPVOID lpReserved
//                     )
//{
//    switch (ul_reason_for_call)
//    {
//    case DLL_PROCESS_ATTACH:
//    case DLL_THREAD_ATTACH:
//    case DLL_THREAD_DETACH:
//    case DLL_PROCESS_DETACH:
//        break;
//    }
//    return TRUE;
//}
```


#リソースの追加
ソリューションエクスプローラでプロジェクトを選び、「追加」コンテキストメニューから「リソース」を実行します。Fig.3 のようなダイアログが開くので、必要なリソースを追加します。新しく作成する場合は「新規作成」、既存のアイコンなどを利用する場合は、「インポート」を実行します。

![ResouceAddDlg.png](https://qiita-image-store.s3.amazonaws.com/0/62802/891ec669-eb81-c6d6-933a-e224bff7dae1.png)
Fig.3 リソースの追加ダイアログ

プログラムはないので、コーディングは不要です。このままビルドを行い DLL ファイルができていることを確認します。

#リソース DLL の使用例
このサンプルは DLL からアイコンをロードして表示する MFC アプリケーションです。このサンプルの要点を以下に示します。

- OnInitDialog ハンドラの中で LoadLibrary API 関数で DLL をロードします。(DLL のロードは必ず動的に行います)
- この後、DLL のモジュールハンドルを使って、DLL のアイコンを LoadImage API 関数 でロードして、ハンドルを取得します。このとき、アイコンの ID は DLL の中のものなので、シンボルでなく具体的な数を指定します。リソースビューでリソースファイル (.rc) を選択してコンテキストメニューで「リソースシンボル」メニューを実行すると Fig.4 のようなダイアログが開くので、シンボルの具体的な番号を知ることができます。
- LoadImage API 関数でアイコンの ID はそのまま使用できないので、MAKEINTRESOURCE マクロを使って渡します。
- アイコンの表示は、OnPaint ハンドラの中で DrawIcon API 関数で行っています。
- アプリが終了するとき、OnClose ハンドラで FreeLibrary API 関数で DLL を解放します。

![ResourceSymbol.png](https://qiita-image-store.s3.amazonaws.com/0/62802/7db1c16b-87ea-a149-fcff-89320a7fd833.png)
Fig.4 リソースシンボル

###ソースファイル

```C++
// TestDllResourceDlg.cpp : 実装ファイル
//

#include "stdafx.h"
#include "TestDllResource.h"
#include "TestDllResourceDlg.h"
#include "afxdialogex.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

#define DLLPATH L"C:\\workspace\\project2017\\Win32Projects\\Debug\\DllResource.dll"

// アプリケーションのバージョン情報に使われる CAboutDlg ダイアログ

class CAboutDlg : public CDialogEx
{
public:
	CAboutDlg();

// ダイアログ データ
#ifdef AFX_DESIGN_TIME
	enum { IDD = IDD_ABOUTBOX };
#endif

	protected:
	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV サポート

// 実装
protected:
	DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX)
{
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()


// CTestDllResourceDlg ダイアログ



CTestDllResourceDlg::CTestDllResourceDlg(CWnd* pParent /*=NULL*/)
	: CDialog(IDD_TESTDLLRESOURCE_DIALOG, pParent)
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CTestDllResourceDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CTestDllResourceDlg, CDialog)
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_WM_CLOSE()
END_MESSAGE_MAP()


// CTestDllResourceDlg メッセージ ハンドラー

BOOL CTestDllResourceDlg::OnInitDialog()
{
	CDialog::OnInitDialog();

	// "バージョン情報..." メニューをシステム メニューに追加します。

	// IDM_ABOUTBOX は、システム コマンドの範囲内になければなりません。
	ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
	ASSERT(IDM_ABOUTBOX < 0xF000);

	CMenu* pSysMenu = GetSystemMenu(FALSE);
	if (pSysMenu != NULL)
	{
		BOOL bNameValid;
		CString strAboutMenu;
		bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
		ASSERT(bNameValid);
		if (!strAboutMenu.IsEmpty())
		{
			pSysMenu->AppendMenu(MF_SEPARATOR);
			pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
		}
	}

	// このダイアログのアイコンを設定します。アプリケーションのメイン ウィンドウがダイアログでない場合、
	//  Framework は、この設定を自動的に行います。
	SetIcon(m_hIcon, TRUE);			// 大きいアイコンの設定
	SetIcon(m_hIcon, FALSE);		// 小さいアイコンの設定

	// TODO: 初期化をここに追加します。
	m_hModule = LoadLibrary(DLLPATH);
	m_hIcon1 = (HICON)LoadImage(m_hModule, MAKEINTRESOURCE(101), IMAGE_ICON, 0, 0, LR_DEFAULTCOLOR| LR_DEFAULTSIZE);

	return TRUE;  // フォーカスをコントロールに設定した場合を除き、TRUE を返します。
}

void CTestDllResourceDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
	if ((nID & 0xFFF0) == IDM_ABOUTBOX)
	{
		CAboutDlg dlgAbout;
		dlgAbout.DoModal();
	}
	else
	{
		CDialog::OnSysCommand(nID, lParam);
	}
}

// ダイアログに最小化ボタンを追加する場合、アイコンを描画するための
//  下のコードが必要です。ドキュメント/ビュー モデルを使う MFC アプリケーションの場合、
//  これは、Framework によって自動的に設定されます。

void CTestDllResourceDlg::OnPaint()
{
	if (IsIconic())
	{
		CPaintDC dc(this); // 描画のデバイス コンテキスト

		SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

		// クライアントの四角形領域内の中央
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// アイコンの描画
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		POINT pos;
		pos.x = 10;
		pos.y = 10;
		CPaintDC dc(this);
		dc.DrawIcon(pos, m_hIcon1);
		// CDialog::OnPaint();
	}
}

// ユーザーが最小化したウィンドウをドラッグしているときに表示するカーソルを取得するために、
//  システムがこの関数を呼び出します。
HCURSOR CTestDllResourceDlg::OnQueryDragIcon()
{
	return static_cast<HCURSOR>(m_hIcon);
}

void CTestDllResourceDlg::OnClose()
{
	// TODO: ここにメッセージ ハンドラー コードを追加するか、既定の処理を呼び出します。
	FreeLibrary(m_hModule);
	CDialog::OnClose();
}
```

###ヘッダーファイル

```C++
// TestDllResourceDlg.h : ヘッダー ファイル
//

#pragma once


// CTestDllResourceDlg ダイアログ
class CTestDllResourceDlg : public CDialog
{
// コンストラクション
public:
	CTestDllResourceDlg(CWnd* pParent = NULL);	// 標準コンストラクター

// ダイアログ データ
#ifdef AFX_DESIGN_TIME
	enum { IDD = IDD_TESTDLLRESOURCE_DIALOG };
#endif

	protected:
	virtual void DoDataExchange(CDataExchange* pDX);	// DDX/DDV サポート


// 実装
protected:
	HICON m_hIcon;
	HMODULE m_hModule;
	HICON m_hIcon1;

	// 生成された、メッセージ割り当て関数
	virtual BOOL OnInitDialog();
	afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
	afx_msg void OnPaint();
	afx_msg HCURSOR OnQueryDragIcon();
	DECLARE_MESSAGE_MAP()
public:
	afx_msg void OnClose();
};
```

###実行例

![TestResouceDLL.png](https://qiita-image-store.s3.amazonaws.com/0/62802/8443f9bd-7351-79d7-6aea-dcf5dc78d2fe.png)


#ExtractIcon や ExtractIconEx を使ってアイコンを取り出す
DLL からアイコンを取り出す場合は、ExtractIcon や ExtractIconEx API 関数を使う方法もあります。InitDialog ハンドラのところで、LoadImage API 関数をこれらの関数に置き換えます。

次のコードは ExtractIcon を使用する例ですが、直接、DLL のパス名を指定するので、LoadLibrary 関数の呼び出しは不要になります。その代りインスタンスハンドルがパラメータとして必要です。

また、リソース ID は使用せず、アイコンの番号(先頭は 0)でアイコンを特定します。

```
// TODO: 初期化をここに追加します。
HINSTANCE hInst = AfxGetInstanceHandle();
m_hIcon1 = ExtractIcon(hInst, DLLPATH, 0);
```

-