MinGW+C言語でWindows用Dllを作成する

# はじめに

C#などで利用するためのWindows用DllをMinGW-w64+C++で作成するときの自分用のメモです。  

MinGW-w64の環境構築は以下のサイトを参考にしました。ダウンロードしたのは記事作成時点での最新版である`x86_64-13.2.0-release-win32-seh-ucrt-rt_v11-rev1.7z`です。

* [C言語 | MinGW-w64のダウンロードとインストール](https://www.javadrive.jp/cstart/install/index6.html)  
* [GDExtensionシンプルに環境構築するよ Pt.2｜NwSnKn](https://note.com/nwsnkn/n/n561042fce5e6)  

コーディング環境はWindows10 x64+[Visual Studio Code](https://azure.microsoft.com/ja-jp/products/visual-studio-code)＋[C/C++ Extension Pack](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools-extension-pack)です。  

# 全体像

## フローについて

* [分割コンパイルとリンケージ](https://www.cc.kyoto-su.ac.jp/~hxm/cstext/prog06.html)  
* [翻訳単位 - C 入門](http://kaitei.net/c/translation-units/#%E5%88%86%E5%89%B2%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%AB)  

![flow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/bf148b37-19a0-6d81-ab22-d590bcce1025.png)  

## ファイルの種類について

|分類|Linux<br>拡張子|例|Windows<br>拡張子|例|
|:----|:----|:----|:----|:----|
|ソースファイル|`.c`<br>`.cpp`<br>`.h`|`getcolor.cpp`|`.c`<br>`.cpp`<br>`.h`|`getcolor.cpp`|
|オブジェクトファイル|`.o`|`getcolor.o`|`.o`|`getcolor.o`|
|アーカイブファイル<br>（archive file）|`.a`|`libuser32.a`|`.lib`|`user32.lib`|
|共有オブジェクト<br>（shared object）|`.so`|`libuser32.so`|`.dll`|`user32.dll`|

* アーカイブファイルは複数のオブジェクトファイルを１つにまとめたファイル。静的ライブラリ（Static library）。  
    [静的ライブラリ.aとオブジェクトファイル.oの違いがわからない](https://teratail.com/questions/224363)  
    [LIBファイルとは？.libファイルの基本概念と活用方法を解説 | THE SIMPLE](https://the-simple.jp/what-is-a-lib-file-explains-the-basic-concepts-of-lib-files-and-how-to-use-them)  

* 共有オブジェクトは実行可能ファイルの形式。動的ライブラリ（Shared library）。  
    [.soファイルとは - 意味をわかりやすく - IT用語辞典 e-Words](https://e-words.jp/w/.so%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB.html)  

# ソースコードの記述

例としてマウスカーソル位置の色を0xFFRRGGBB形式で返すコードを作成します。  

```cpp
/*
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 - Wikipedia](https://en.wikipedia.org/wiki/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](https://learn.microsoft.com/ja-jp/cpp/build/exporting-from-a-dll-using-declspec-dllexport?view=msvc-170)  
> `__declspec(dllexport)` キーワードを使用すると、データ、関数、クラス、クラスのメンバー関数を DLL からエクスポートできます。  

[dllexport, dllimport | Microsoft Learn](https://learn.microsoft.com/en-us/cpp/cpp/dllexport-dllimport?view=msvc-170)  
> `dllexport` of a function exposes the function with its decorated name, sometimes known as "name mangling".

## extern "C"

関数の名前をCの形式で名前装飾（`Name mangling`、名前マングリング）します。C++の場合、特に指定がないとC++形式で名前装飾しますが、標準的な仕様がなく、どのような名前になるかは実装依存になるため呼び出すのが困難になります。  

### 名前装飾について  

[名前修飾 - Wikipedia](https://ja.wikipedia.org/wiki/%E5%90%8D%E5%89%8D%E4%BF%AE%E9%A3%BE)
> ...サブルーチン（関数）名などに対する内部名を、その表層的な名前のみならず、関数であればその引数の型や返戻値の型などといった意味的な情報を含めて修飾した（manglingした）名前とするものである。

[装飾名 | Microsoft Learn](https://learn.microsoft.com/ja-jp/cpp/build/reference/decorated-names?view=msvc-170) / [Decorated names | Microsoft Learn](https://learn.microsoft.com/en-us/cpp/build/reference/decorated-names?view=msvc-170)  
> プログラム内の関数、データ、オブジェクトは、内部ではそれぞれの装飾名で表されます。  
> こういった名前の装飾は、"名前修飾" とも呼ばれ、リンカーが実行可能ファイルをリンクするとき、正しい関数やオブジェクトを見つけるために役立ちます。  

> ... This name decoration, also known as name mangling, ...

名前装飾により、内部ではそれぞれ異なった装飾名になるため、関数のオーバーロードといったことができるようになります。例えば以下はコンパイル可能ですが、  
```cpp
int AddInt(int x, int y)
{
    return x + y;
}

int AddInt(int x)
{
    return x + 2;
}
```
以下はコンパイルNGとなります。  
```cpp
extern "C" int AddInt(int x, int y)
{
    return x + y;
}

extern "C" int AddInt(int x)
{
    return x + 2;
}
```

### C++形式の名前の装飾

標準的な修飾規則がなく、実装依存。  
* [コンパイラによる名前修飾の相違](https://ja.wikipedia.org/wiki/%E5%90%8D%E5%89%8D%E4%BF%AE%E9%A3%BE#%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%A9%E3%81%AB%E3%82%88%E3%82%8B%E5%90%8D%E5%89%8D%E4%BF%AE%E9%A3%BE%E3%81%AE%E7%9B%B8%E9%81%95)  
* [Format of a C++ decorated name](https://learn.microsoft.com/en-us/cpp/build/reference/decorated-names?view=msvc-170#Format)  

### C形式の名前の装飾

宣言で使用される[呼び出し規則](https://learn.microsoft.com/ja-jp/cpp/cpp/calling-conventions?view=msvc-170)によって決まります。  
* [Format of a C decorated name](https://learn.microsoft.com/en-us/cpp/build/reference/decorated-names?view=msvc-170#FormatC)  
* [Microsoft Windowsの場合](https://ja.wikipedia.org/wiki/%E5%90%8D%E5%89%8D%E4%BF%AE%E9%A3%BE#Microsoft_Windows%E3%81%AE%E5%A0%B4%E5%90%88)  
* [Results of Calling Example | Microsoft Learn](https://learn.microsoft.com/en-us/cpp/cpp/results-of-calling-example?view=msvc-170)  

### extern

[extern (C++) | Microsoft Learn](https://learn.microsoft.com/ja-jp/cpp/cpp/extern-cpp?view=msvc-170)  
> extern キーワードは、グローバル変数、関数、テンプレート宣言に適用できます。これは、シンボルが外部リンケージを持っていることを指定します。  

よく使用するのはこちらの方法ですが、`"C"`をつけることで

> `extern "C"` は、関数が他の場所で定義され、C 言語呼び出し規則を使用することを指定します。 `extern "C"` 修飾子は、ブロック内の複数の関数宣言にも適用できます。 

C言語呼び出し規則を使用することを指定できます。  

[C 言語の実行形式で使う C++ 関数のエクスポート | Microsoft Learn](https://learn.microsoft.com/ja-jp/cpp/build/exporting-cpp-functions-for-use-in-c-language-executables?view=msvc-170)  

## __stdcall

[呼び出し規則](https://learn.microsoft.com/ja-jp/cpp/cpp/calling-conventions?view=msvc-170)の1つです。  

[__stdcall | Microsoft Learn](https://learn.microsoft.com/ja-jp/cpp/cpp/stdcall?view=msvc-170)  
> `__stdcall` 呼び出し規則は、Win32 API 関数の呼び出しに使用されます。  
> <戻り値の型> `__stdcall` <関数名> ...  

例えばC#からDllを呼び出す場合、  

```cs
[DllImport("getcolor.dll")]
public static extern uint GetCursorPointColor();
```

のように[DllImportAttribute クラス](https://learn.microsoft.com/ja-jp/dotnet/api/system.runtime.interopservices.dllimportattribute?view=net-8.0)を使用しますが、エントリ ポイントの呼び出し規約を示す[CallingConvention フィールドの既定はWinapi](https://learn.microsoft.com/ja-jp/dotnet/api/system.runtime.interopservices.dllimportattribute.callingconvention?view=net-8.0)になります。[CallingConvention 列挙型](https://learn.microsoft.com/ja-jp/dotnet/api/system.runtime.interopservices.callingconvention?view=net-8.0)によると`Winapi`の場合 [既定のプラットフォーム呼び出し規則](https://learn.microsoft.com/ja-jp/dotnet/standard/native-interop/calling-conventions#platform-default-calling-convention)を使用することになり、Windowsでは`stdcall`、Linuxでは`cdecl`になります。呼ばれる側のDllと呼ぶ側のWindowsアプリケーションとで呼び出し規則を合わせるために`__stdcall`を使用します。  

## DllMain

DLLへのエントリポイントです。  
* [DllMain エントリ ポイント (Process.h) - Win32 apps | Microsoft Learn](https://learn.microsoft.com/ja-jp/windows/win32/dlls/dllmain)  
* [Dynamic-Link ライブラリ Entry-Point関数 - Win32 apps | Microsoft Learn](https://learn.microsoft.com/ja-jp/windows/win32/dlls/dynamic-link-library-entry-point-function)  


# DLLの作成

## コンパイル

コンパイルしてソースファイルからオブジェクトファイルを作成します。  
```bat
g++ -c getcolor.cpp
```

`gcc`と`g++`については以下を参照。ヘルプを見る場合は`gcc -v --help`。  
* [Using the GNU Compiler Collection (GCC): Invoking GCC](https://gcc.gnu.org/onlinedocs/gcc-9.1.0/gcc/Invoking-GCC.html)  
* [Using the GNU Compiler Collection (GCC): Invoking G++](https://gcc.gnu.org/onlinedocs/gcc-9.1.0/gcc/Invoking-G_002b_002b.html#Invoking-G_002b_002b)  

## リンク

オブジェクトファイルをリンクしてDllを作成します。今回はライブラリとして  
* `User32.lib/User32.dll`（`libuser32.a/libuser32.so`）  
    [GetCursorPos](https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-getcursorpos)、[GetDC](https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-getdc)、[ReleaseDC](https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-releasedc)を使用するために必要。
* `Gdi32.lib/Gdi32.dll`（`libgdi32.a/libgdi32.so`）  
    [GetPixel](https://learn.microsoft.com/ja-jp/windows/win32/api/wingdi/nf-wingdi-getpixel)を使用するために必要。  

を使用しているので、これらのライブラリもリンクします。  

```bat
g++ -shared -o getcolor.dll getcolor.o -luser32 -lgdi32
```

   
|オプション|内容|
|:----|:----|
|[`-shared`](https://gcc.gnu.org/onlinedocs/gcc/Link-Options.html#index-shared)|共有オブジェクト（WindowsでいうDLL）を作成します。|
|`-o`|出力ファイル名を指定します。|
|[`-l`*`library`*](https://gcc.gnu.org/onlinedocs/gcc/Link-Options.html#index-Libraries)<br>|ライブラリをリンクします。例えば`-luser32`であれば<br>静的ライブラリ`libuser32.a`（アーカイブファイル）<br>動的ライブラリ`libuser32.so`（共有オブジェクト）<br>を探してリンクします。両方見つかったときは、動的ライブラリを優先してリンクします。|

* ライブラリのリンクについては以下を参照。特に[16.6](https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/7/html/developer_guide/gcc-using-libraries_using-both-static-dynamic-library-gcc)と[16.2](https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/7/html/developer_guide/gcc-using-libraries_understanding-static-dynamic-linking)あたりが分かりやすかったです。  
    [Chapter 16. Using Libraries with GCC Red Hat Enterprise Linux 7 | Red Hat Customer Portal](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/developer_guide/gcc-using-libraries#doc-wrapper)  

* [`dlltool`](https://sourceware.org/binutils/docs/binutils.html#dlltool)を使うのか`-shared`オプションを使うのかについては以下を参考にしました。  
    [dll - Looking for Information on dlltool --add-indirect (-a) - Stack Overflow](https://stackoverflow.com/questions/20565042/looking-for-information-on-dlltool-add-indirect-a)  

なお、コンパイルとリンクをまとめて実施することもできます。  
```bat
g++ -shared -o getcolor.dll getcolor.cpp -luser32 -lgdi32
```

# 補足

## C#コード

上記の`getcolor.dll`を使用したコード例です。フォーム起動時に背景をマウスカーソル位置の色にしています。  

```cs
/*
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」を起動した後、作業フォルダへ行き、

```bat
cl /LD getcolor.cpp User32.lib Gdi32.lib
```

[/MD、-MT、-LD (ランタイム ライブラリの使用) | Microsoft Learn](https://learn.microsoft.com/ja-jp/cpp/build/reference/md-mt-ld-use-run-time-library?view=msvc-170)  

DLL のエクスポート テーブルを表示するには、

```bat
DUMPBIN /EXPORTS getcolor.dll
```

[DLL からのエクスポート | Microsoft Learn](https://learn.microsoft.com/ja-jp/cpp/build/exporting-from-a-dll?view=msvc-170)  

## コードの共通化

MinGWとVisual Studioとでコードを共通化する場合は、`__MINGW32__`または`__MINGW64__`がdefineされているかで切り分けをします。  
* [Qt (c++)でWindowsとLinuxを認識するマクロ - スタック・オーバーフロー](https://ja.stackoverflow.com/questions/68248/qt-c%E3%81%A7windows%E3%81%A8linux%E3%82%92%E8%AA%8D%E8%AD%98%E3%81%99%E3%82%8B%E3%83%9E%E3%82%AF%E3%83%AD)  
* [UNIX向けソフトウェアをMinGWに移植する際のテクニック - なんとな～くしあわせ？の日記](https://nantonaku-shiawase.hatenablog.com/entry/2013/09/01/115014)  
* [Pre-defined Compiler Macros / Wiki / Compilers](https://sourceforge.net/p/predef/wiki/Compilers/#mingw-and-mingw-w64)  

```cpp
/*
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で共有したデータの寿命 - ぷるぷるの雑記](https://prupru-prune.hatenablog.com/entry/2023/12/01/075726)  
* [data_seg pragma | Microsoft Learn](https://learn.microsoft.com/ja-jp/cpp/preprocessor/data-seg?view=msvc-170)  
* [comment pragma | Microsoft Learn](https://learn.microsoft.com/ja-jp/cpp/preprocessor/comment-c-cpp?view=msvc-170)  
* [/SECTION (セクション属性の指定) | Microsoft Learn](https://learn.microsoft.com/ja-jp/cpp/build/reference/section-specify-section-attributes?view=msvc-170)  
```cpp
#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))](https://gcc.gnu.org/onlinedocs/gcc/Microsoft-Windows-Variable-Attributes.html)  
* [Common Variable Attributes (Using the GNU Compiler Collection (GCC))](https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#index-section-variable-attribute)  
```cpp
int counter __attribute__((section("shared"), shared)) = 0;
```


## GNU Binutils

* [GNU Binutils - Wikipedia](https://ja.wikipedia.org/wiki/GNU_Binutils)  
* [The GNU Binary Utilities: はじめに](https://web.sfc.wide.ad.jp/~sagawa/gnujdoc/binutils-2.16.1/index.html#SEC_Top)  
* [GNU Binary Utilities](https://sourceware.org/binutils/docs/binutils.html)  
* [システム・ダンプ・ツールの使用 - IBM Documentation](https://www.ibm.com/docs/ja/sdk-java-technology/8?topic=techniques-using-system-dump-tools)  

**objdump**  

オブジェクトファイルの情報を表示  
```
objdump -x getcolor.o
```

**nm**  

オブジェクトファイルのシンボル情報を表示
```
nm getcolor.o
```

**windres**  

リソースの作成  

アイコンファイル`app.ico`とexeファイル用の`main.c`と  
```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
```