はじめに
C#などで利用するためのWindows用DllをMinGW-w64+C++で作成するときの自分用のメモです。
MinGW-w64の環境構築は以下のサイトを参考にしました。ダウンロードしたのは記事作成時点での最新版であるx86_64-13.2.0-release-win32-seh-ucrt-rt_v11-rev1.7z
です。
コーディング環境はWindows10 x64+Visual Studio Code+C/C++ Extension Packです。
全体像
フローについて
ファイルの種類について
分類 | Linux 拡張子 |
例 | Windows 拡張子 |
例 |
---|---|---|---|---|
ソースファイル |
.c .cpp .h
|
getcolor.cpp |
.c .cpp .h
|
getcolor.cpp |
オブジェクトファイル | .o |
getcolor.o |
.o |
getcolor.o |
アーカイブファイル (archive file) |
.a |
libuser32.a |
.lib |
user32.lib |
共有オブジェクト (shared object) |
.so |
libuser32.so |
.dll |
user32.dll |
-
アーカイブファイルは複数のオブジェクトファイルを1つにまとめたファイル。静的ライブラリ(Static library)。
静的ライブラリ.aとオブジェクトファイル.oの違いがわからない
LIBファイルとは?.libファイルの基本概念と活用方法を解説 | THE SIMPLE -
共有オブジェクトは実行可能ファイルの形式。動的ライブラリ(Shared library)。
.soファイルとは - 意味をわかりやすく - IT用語辞典 e-Words
ソースコードの記述
例としてマウスカーソル位置の色を0xFFRRGGBB形式で返すコードを作成します。
/*
getcolor.cpp
g++ -shared -o getcolor.dll getcolor.cpp -luser32 -lgdi32
*/
#include <windows.h>
// マウスカーソル位置の色を返す(0xFFRRGGBB)
extern "C" __declspec(dllexport) DWORD __stdcall GetCursorPointColor()
{
// マウスカーソル位置
POINT point;
GetCursorPos(&point);
// デバイス コンテキストを取得
HDC hdc = GetDC(NULL);
// マウスカーソル位置の色を取得 0x00BBGGRR
COLORREF color = GetPixel(hdc, point.x, point.y);
// デバイス コンテキストを解放
ReleaseDC(NULL, hdc);
// RGBを抜き取り
BYTE red = (BYTE)(color & 0xFF);
BYTE green = (BYTE)((color >> 8) & 0xFF);
BYTE blue = (BYTE)((color >> 16) & 0xFF);
// 戻り値作成
DWORD ret = (0xFF << 24) | (red << 16) | (green << 8) | blue;
return ret;
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
return TRUE;
}
windows.h
windows.h is a Windows-specific header file for the C and C++ programming languages which contains declarations for all of the functions in the Windows API, all the common macros used by Windows programmers, and all the data types used by the various functions and subsystems.
つまり、Windows用のプログラムを作るときに使用する全てのマクロ、関数、データ型の宣言を含んでいるので、includeが必須ということです。
__declspec(dllexport)
Dll内の関数を外部から使用できるようにするために関数を公開します。名前装飾された関数名で公開します。
__declspec(dllexport) を使った DLL からのエクスポート | Microsoft Learn
__declspec(dllexport)
キーワードを使用すると、データ、関数、クラス、クラスのメンバー関数を DLL からエクスポートできます。
dllexport, dllimport | Microsoft Learn
dllexport
of a function exposes the function with its decorated name, sometimes known as "name mangling".
extern "C"
関数の名前をCの形式で名前装飾(Name mangling
、名前マングリング)します。C++の場合、特に指定がないとC++形式で名前装飾しますが、標準的な仕様がなく、どのような名前になるかは実装依存になるため呼び出すのが困難になります。
名前装飾について
...サブルーチン(関数)名などに対する内部名を、その表層的な名前のみならず、関数であればその引数の型や返戻値の型などといった意味的な情報を含めて修飾した(manglingした)名前とするものである。
装飾名 | Microsoft Learn / Decorated names | Microsoft Learn
プログラム内の関数、データ、オブジェクトは、内部ではそれぞれの装飾名で表されます。
こういった名前の装飾は、"名前修飾" とも呼ばれ、リンカーが実行可能ファイルをリンクするとき、正しい関数やオブジェクトを見つけるために役立ちます。
... This name decoration, also known as name mangling, ...
名前装飾により、内部ではそれぞれ異なった装飾名になるため、関数のオーバーロードといったことができるようになります。例えば以下はコンパイル可能ですが、
int AddInt(int x, int y)
{
return x + y;
}
int AddInt(int x)
{
return x + 2;
}
以下はコンパイルNGとなります。
extern "C" int AddInt(int x, int y)
{
return x + y;
}
extern "C" int AddInt(int x)
{
return x + 2;
}
C++形式の名前の装飾
標準的な修飾規則がなく、実装依存。
C形式の名前の装飾
宣言で使用される呼び出し規則によって決まります。
extern
extern (C++) | Microsoft Learn
extern キーワードは、グローバル変数、関数、テンプレート宣言に適用できます。これは、シンボルが外部リンケージを持っていることを指定します。
よく使用するのはこちらの方法ですが、"C"
をつけることで
extern "C"
は、関数が他の場所で定義され、C 言語呼び出し規則を使用することを指定します。extern "C"
修飾子は、ブロック内の複数の関数宣言にも適用できます。
C言語呼び出し規則を使用することを指定できます。
C 言語の実行形式で使う C++ 関数のエクスポート | Microsoft Learn
__stdcall
呼び出し規則の1つです。
__stdcall
呼び出し規則は、Win32 API 関数の呼び出しに使用されます。
<戻り値の型> `__stdcall` <関数名> ...
例えばC#からDllを呼び出す場合、
[DllImport("getcolor.dll")]
public static extern uint GetCursorPointColor();
のようにDllImportAttribute クラスを使用しますが、エントリ ポイントの呼び出し規約を示すCallingConvention フィールドの既定はWinapiになります。CallingConvention 列挙型によるとWinapi
の場合 既定のプラットフォーム呼び出し規則を使用することになり、Windowsではstdcall
、Linuxではcdecl
になります。呼ばれる側のDllと呼ぶ側のWindowsアプリケーションとで呼び出し規則を合わせるために__stdcall
を使用します。
DllMain
DLLへのエントリポイントです。
- DllMain エントリ ポイント (Process.h) - Win32 apps | Microsoft Learn
- Dynamic-Link ライブラリ Entry-Point関数 - Win32 apps | Microsoft Learn
DLLの作成
コンパイル
コンパイルしてソースファイルからオブジェクトファイルを作成します。
g++ -c getcolor.cpp
gcc
とg++
については以下を参照。ヘルプを見る場合はgcc -v --help
。
- Using the GNU Compiler Collection (GCC): Invoking GCC
- Using the GNU Compiler Collection (GCC): Invoking G++
リンク
オブジェクトファイルをリンクしてDllを作成します。今回はライブラリとして
-
User32.lib/User32.dll
(libuser32.a/libuser32.so
)
GetCursorPos、GetDC、ReleaseDCを使用するために必要。 -
Gdi32.lib/Gdi32.dll
(libgdi32.a/libgdi32.so
)
GetPixelを使用するために必要。
を使用しているので、これらのライブラリもリンクします。
g++ -shared -o getcolor.dll getcolor.o -luser32 -lgdi32
オプション | 内容 |
---|---|
-shared |
共有オブジェクト(WindowsでいうDLL)を作成します。 |
-o |
出力ファイル名を指定します。 |
-l library |
ライブラリをリンクします。例えば-luser32 であれば静的ライブラリ libuser32.a (アーカイブファイル)動的ライブラリ libuser32.so (共有オブジェクト)を探してリンクします。両方見つかったときは、動的ライブラリを優先してリンクします。 |
-
ライブラリのリンクについては以下を参照。特に16.6と16.2あたりが分かりやすかったです。
Chapter 16. Using Libraries with GCC Red Hat Enterprise Linux 7 | Red Hat Customer Portal -
dlltool
を使うのか-shared
オプションを使うのかについては以下を参考にしました。
dll - Looking for Information on dlltool --add-indirect (-a) - Stack Overflow
なお、コンパイルとリンクをまとめて実施することもできます。
g++ -shared -o getcolor.dll getcolor.cpp -luser32 -lgdi32
補足
C#コード
上記のgetcolor.dll
を使用したコード例です。フォーム起動時に背景をマウスカーソル位置の色にしています。
/*
getcolor.cs
C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc /target:winexe getcolor.cs
*/
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace GetColor
{
internal static class Program
{
[STAThread]
static void Main()
{
Application.Run(new MainForm());
}
}
public class MainForm : Form
{
internal static class NativeMethods
{
[DllImport("getcolor.dll")]
public static extern uint GetCursorPointColor();
}
public MainForm()
{
//フォーム起動時に背景をマウスカーソル位置の色にする
Load += (sender, e) =>
{
BackColor = Color.FromArgb((int)NativeMethods.GetCursorPointColor());
};
}
}
}
Visual Studio
Visual StudioのCL.exe
でコンパイルする場合は、「x64 Native Tools Command Prompt for VS 2022」を起動した後、作業フォルダへ行き、
cl /LD getcolor.cpp User32.lib Gdi32.lib
/MD、-MT、-LD (ランタイム ライブラリの使用) | Microsoft Learn
DLL のエクスポート テーブルを表示するには、
DUMPBIN /EXPORTS getcolor.dll
DLL からのエクスポート | Microsoft Learn
コードの共通化
MinGWとVisual Studioとでコードを共通化する場合は、__MINGW32__
または__MINGW64__
がdefineされているかで切り分けをします。
- Qt (c++)でWindowsとLinuxを認識するマクロ - スタック・オーバーフロー
- UNIX向けソフトウェアをMinGWに移植する際のテクニック - なんとな~くしあわせ?の日記
- Pre-defined Compiler Macros / Wiki / Compilers
/*
MinGWかCLかによって出力文字列を変更
gcc sample.c -o sample
cl sample.c
*/
#include <stdio.h>
int main(void)
{
#ifdef __MINGW64__
printf("MinGW");
#else
printf("Not MinGW");
#endif
return 0;
}
グローバル変数の共有
グローバル変数をプロセス間で共有する場合は、
Visual Studio
- DLLで共有したデータの寿命 - ぷるぷるの雑記
- data_seg pragma | Microsoft Learn
- comment pragma | Microsoft Learn
- /SECTION (セクション属性の指定) | Microsoft Learn
#pragma data_seg("Shared")
int counter = 0;
#pragma data_seg()
#pragma comment(linker, "/Section:Shared,rws")
MinGW
- Microsoft Windows Variable Attributes (Using the GNU Compiler Collection (GCC))
- Common Variable Attributes (Using the GNU Compiler Collection (GCC))
int counter __attribute__((section("shared"), shared)) = 0;
GNU Binutils
- GNU Binutils - Wikipedia
- The GNU Binary Utilities: はじめに
- GNU Binary Utilities
- システム・ダンプ・ツールの使用 - IBM Documentation
objdump
オブジェクトファイルの情報を表示
objdump -x getcolor.o
nm
オブジェクトファイルのシンボル情報を表示
nm getcolor.o
windres
リソースの作成
アイコンファイルapp.ico
とexeファイル用のmain.c
と
#include <stdio.h>
int main(void)
{
printf("Hello World");
return 0;
}
リソーススクリプトファイルapp.rc
100 ICON "app.ico"
を用意して、コンパイル、リソースファイル作成、リンクをすることでexeファイルのアイコンを設定します。
g++ -c main.c
windres -i app.rc -o icon.o
g++ -o main.exe main.o icon.o