C++
VisualStudio
Win32
dll

Visual Studio 2017 Visual C++ による Win32 DLL の開発 (関数ライブラリ)

はじめに

DLL は Dynamic Link Library の略で、実行時に exe ファイルにリンクされるライブラリファイルです。ここでは、DLL の作成とテストについて説明します。

クラスのDLLについてはこちら
リソースのDLLについてはこちら

プロジェクトの作成

「新しいプロジェクト」ダイアログで 「Windows デスクトップ」の中の「ダイナミックリンクライブラリ (DLL)」を選びます。OK ボタンをクリックすると、ただちにプロジェクトが作成され、ソリューションエクスプローラに表示されます。

DllWin32Project.png

モジュール定義ファイルを使う方法

DLL (DLLSample1.dll)

DLL では外部に公開する関数(エクスポート関数)をモジュール定義ファイルに記述することで指定します。(後述するように別の方法もあります)。

プロジェクトにはデフォルトでモジュール定義ファイルが含まれていないので、ソリューションエクスプローラで追加します。プロジェクトを選んで追加から新しい項目コンテキストメニューを開くと、「コード」の中に「モジュール定義ファイル(.def)」があるので、ライブラリ名でモジュール定義ファイルをプロジェクトに追加します。

DLLModuleDef.png

ライブラリ名と外部公開関数をモジュール定義ファイルに記述します。@1, @2, .. は序数と呼ばれる番号で @ の後に 1 から順に番号を付けます。

下のモジュール定義ファイルの例では、ライブラリ名が DLLSample1、外部公開関数が GetFrameBorderWidth, GetFrameBorderHeight, GetTitleBarHeight としています。

LIBRARY DLLSample1
EXPORTS
    GetFrameBorderWidth @1
    GetFrameBorderHeight    @2
    GetTitleBarHeight   @3

このサンプルでは、ウィンドウの境界線の幅(上下と左右)とタイトルバーの高さを返す関数を公開します。

ヘッダーファイル DLLSample1.h をプロジェクトに追加して、次のように公開関数のプロトタイプを記述します。このプロジェクトは、C++ プロジェクトなので関数名をコーディングどおりで公開するため extern "C" を付けています。

#pragma once
extern "C" int GetFrameBorderWidth();
extern "C" int GetFrameBorderHeight();
extern "C" int GetTitleBarHeight();
CPP ファイルの内容を下に示します。ここでは、ヘッダーで宣言した公開関数の実装を行います。

// DLLSample1.cpp : DLL アプリケーション用にエクスポートされる関数を定義します。
//

#include "stdafx.h"

// ウィンドウの左右境界線の幅を得る。
int GetFrameBorderWidth()
{
    return GetSystemMetrics(SM_CXBORDER);
}

// ウィンドウの上下境界線の高さを得る。
int GetFrameBorderHeight()
{
    return GetSystemMetrics(SM_CYBORDER);
}

// ウィンドウのタイトルバーの高さを得る。
int GetTitleBarHeight()
{
    return GetSystemMetrics(SM_CYCAPTION);
}

テスト

普通にコンソール・アプリケーション・プロジェクトを作成します。

DLL の情報をプロジェクトに追加します。プロジェクトのプロパティでリンカー/入力で「追加の依存ファイル」にタイプライブラリを追加します。これは、ソリューションの Debug フォルダ内にあり、名前はこの場合 DLLSample1.lib です。

ソースは以下のようになります。ヘッダーファイル DLLSample1.h は手動で追加します。

stdafx.h

DLLSample1.h をインクルードします。

// stdafx.h : 標準のシステム インクルード ファイルのインクルード ファイル、または
// 参照回数が多く、かつあまり変更されない、プロジェクト専用のインクルード ファイル
// を記述します。
//
#pragma once

#include "targetver.h"

#include <stdio.h>
#include <tchar.h>

// TODO: プログラムに必要な追加ヘッダーをここで参照してください
#include "DLLSample1.h"

DLLSample1.h

DLL の外部公開関数のプロトタイプを記述します。

#pragma once
extern "C" int GetFrameBorderWidth();
extern "C" int GetFrameBorderHeight();
extern "C" int GetTitleBarHeight();

TestDLLSample1.cpp

DLLSample1.dll の公開関数を呼び出して結果を表示します。

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

int main()
{
    printf_s("DLLSample1 のテスト\n\n");

    int cx = GetFrameBorderWidth();
    printf_s("BorderWidth = %d\n", cx);
    int cy = GetFrameBorderHeight();
    printf_s("BorderHeight = %d\n", cy);
    int tb = GetTitleBarHeight();
    printf_s("TitleBar Height = %d\n", tb);

    getchar();
    return 0;
}

__declspec(dllexport) を使う方法

モジュール定義ファイルで、外部公開関数を指定する方法の他に、関数宣言のところで直接、外部公開関数であることを指定することもできます。これは、関数に __declspec(dllexport) を付けることにより行います。

DLL (DLLSample2.dll)

ソースファイルですが、次のようになります。ヘッダーファイル (stdafx.h) は、特に変更なしです。

この DLL ではウィンドウ各部の色を取得する GetWindowColor 関数を公開しています。

// DllSample2.cpp : DLL アプリケーション用にエクスポートされる関数を定義します。
//
#include "stdafx.h"

extern "C" __declspec(dllexport) DWORD GetWindowColor(int c);

// ウィンドウの各部の色を得る。
extern "C" __declspec(dllexport) DWORD GetWindowColor(int c)
{
    DWORD color = 0xffffffff;

    switch (c)
    {
    case 0:
        // ウィンドウの背景色。
        color = GetSysColor(COLOR_WINDOW);
        break;
    case 1:
        // ウィンドウの枠の色。
        color = GetSysColor(COLOR_WINDOWFRAME);
        break;
    case 2:
        // ウィンドウ内のテキストの色。
        color = GetSysColor(COLOR_WINDOWTEXT);
        break;
    default:
        break;
    }

    return color;
}

テスト

DLLSample2.h

このヘッダーファイルでは公開関数のプロトタイプを宣言しています。このヘッダーファイルは、stdafx.h にインクルードします。

#pragma once
#include <Windows.h>
extern "C" __declspec(dllimport) DWORD GetWindowColor(int c);

TestDLLSample2.cpp

このテストプログラムでは、公開関数を呼び出して結果を表示します。

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

int main()
{
    DWORD color;

    // ウィンドウの背景色
    color = GetWindowColor(0);
    printf_s("%08x\n", color);

    // ウィンドウの枠の色
    color = GetWindowColor(1);
    printf_s("%08x\n", color);

    // ウィンドウ内のテキストの色
    color = GetWindowColor(2);
    printf_s("%08x\n", color);

    getchar();
    return 0;
}

動的に DLL をロードして使用する方法

DLL はアプリケーション開始のときに exe ファイルにリンクされますが、実行開始後に LoadLibrary (あるいは LoadLibraryEx) API 関数を使って動的にロードすることもできます。

LoadLibrary が成功すると、HMODULE 型のハンドルが返されるので、GetProcAddress API 関数を使って、モジュールハンドルと外部公開関数の名前から外部公開関数の関数ポインタを取得します。

その関数ポインタを使って、公開関数を呼び出します。

// TestDLLSample2L.cpp : アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"
#define DLLPATH L"C:\\workspace\\project2017\\Win32Projects\\Debug\\DllSample1.dll"

using func = int(*)();

int main()
{
    func ProcAddr;
    HMODULE hMod = LoadLibrary(DLLPATH);
    if (hMod == NULL)
        return -1;

    ProcAddr = (func)GetProcAddress(hMod, "GetTitleBarHeight");
    if (ProcAddr == NULL)
        return -2;
    int h = (ProcAddr)();

    printf_s("TitleBar Height = %d\n", h);
    FreeLibrary(hMod);
    getchar();
    return 0;
}

-