LoginSignup
1
1

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

Last updated at Posted at 2024-03-31

はじめに

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 CodeC/C++ Extension Packです。

全体像

フローについて

flow.png

ファイルの種類について

分類 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

ソースコードの記述

例としてマウスカーソル位置の色を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 - Wikipedia

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++形式で名前装飾しますが、標準的な仕様がなく、どのような名前になるかは実装依存になるため呼び出すのが困難になります。

名前装飾について

名前修飾 - Wikipedia

...サブルーチン(関数)名などに対する内部名を、その表層的な名前のみならず、関数であればその引数の型や返戻値の型などといった意味的な情報を含めて修飾した(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 | Microsoft Learn

__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へのエントリポイントです。

DLLの作成

コンパイル

コンパイルしてソースファイルからオブジェクトファイルを作成します。

g++ -c getcolor.cpp

gccg++については以下を参照。ヘルプを見る場合はgcc -v --help

リンク

オブジェクトファイルをリンクしてDllを作成します。今回はライブラリとして

  • User32.lib/User32.dlllibuser32.a/libuser32.so
    GetCursorPosGetDCReleaseDCを使用するために必要。
  • Gdi32.lib/Gdi32.dlllibgdi32.a/libgdi32.so
    GetPixelを使用するために必要。

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

g++ -shared -o getcolor.dll getcolor.o -luser32 -lgdi32
オプション 内容
-shared 共有オブジェクト(WindowsでいうDLL)を作成します。
-o 出力ファイル名を指定します。
-llibrary
ライブラリをリンクします。例えば-luser32であれば
静的ライブラリlibuser32.a(アーカイブファイル)
動的ライブラリlibuser32.so(共有オブジェクト)
を探してリンクします。両方見つかったときは、動的ライブラリを優先してリンクします。

なお、コンパイルとリンクをまとめて実施することもできます。

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されているかで切り分けをします。

/*
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

#pragma data_seg("Shared")
int counter = 0;
#pragma data_seg()
#pragma comment(linker, "/Section:Shared,rws")

MinGW

int counter __attribute__((section("shared"), shared)) = 0;

GNU Binutils

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
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1