Cでプログラムを作ってWindowsで実行したい。
Windowsで開発するのが一番簡単だと思うけれど、環境をあまり複雑にしたくないし、ライセンスの問題も考えたくなかったので、Ubuntuでやることにした。
仮想環境でUbuntu 24.04 desktopを立ち上げて、Visual Studio Codeをインストールした。
この環境で開発・デバッグをして、完成したプログラムをWindowsに持っていく。
基本的な開発環境
開発に必要と思うパッケージをインストールする。
クロスコンパイル環境に必須というわけでもないと思うのだけれど、makeコマンドも使いたいのでインストールした。
$ sudo apt install build-essential
このようなパッケージがインストールされる模様。
$ apt show build-essential
Package: build-essential
...
Depends: libc6-dev | libc-dev, gcc (>= 4:12.3), g++ (>= 4:12.3), make, dpkg-dev (>= 1.17.11)
...
hello, worldをコンパイルしてみた。
$ gcc hello.c -o hello
$ file hello
hello: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=c12dbaf42581518e7e2d2f51cd618461006f2be2, for GNU/Linux 3.2.0, not stripped
できあがったバイナリーをUbuntu上で実行して、hello, worldが表示されることを確認した。
クロスコンパイル環境
ビルドした実行形式ファイルはWindows 11で動作させたいので、64ビットのプログラムを作ろうと思う。
だいぶ前にCygwinを試したことはあったが、正直なところ当時UNIX系OSの知識がなさ過ぎて、よく分からなかった。
そのCygwinからフォークしたMinGW、MinGWからフォークしたmingw-w64というのがあるとのこと。
Mingw-w64のインストール
公式ページの説明に沿ってCとC++をインストール。
$ sudo apt install g++-mingw-w64-x86-64 gcc-mingw-w64-x86-64
hello, worldをコンパイルし、プログラムをWindowsに持っていったら、そのまま動いた。
基本的なライブラリはスタティックに結合されるのかもしれない。
$ x86_64-w64-mingw32-gcc hello.c -o hello.exe
$ file hello.exe
hello.exe: PE32+ executable (console) x86-64, for MS Windows, 19 sections
環境変数を見てみたけれど、よく分からない。
でも、stdio.hを引き込めている。
環境の確認
コマンドがいくつかインストールされているけれど、README.Debian等を見ているとalternativesで切り替えて使うことが想定されている模様。
$ update-alternatives --get-selections | grep "mingw"
x86_64-w64-mingw32-g++ auto /usr/bin/x86_64-w64-mingw32-g++-win32
x86_64-w64-mingw32-gcc auto /usr/bin/x86_64-w64-mingw32-gcc-win32
$ sudo update-alternatives --config x86_64-w64-mingw32-g++
alternative x86_64-w64-mingw32-g++ (/usr/bin/x86_64-w64-mingw32-g++ を提供) には 2 個の選択肢があります。
選択肢 パス 優先度 状態
------------------------------------------------------------
* 0 /usr/bin/x86_64-w64-mingw32-g++-win32 60 自動モード
1 /usr/bin/x86_64-w64-mingw32-g++-posix 30 手動モード
2 /usr/bin/x86_64-w64-mingw32-g++-win32 60 手動モード
現在の選択 [*] を保持するには <Enter>、さもなければ選択肢の番号のキーを押してください:
どうやらスレッディングモデル(Windows, POSIX)の切り替えができるということのようだ。
Geminiによれば、インクルードパスの違いは、バイナリーから出力させることで調べることができるとのこと。
いい加減な比較ではあるけれど、これで使い分けがされているのか…と分かる程度の違いを黄色で示してみた。
$ x86_64-w64-mingw32-gcc -E -v - </dev/null
Using built-in specs.
COLLECT_GCC=x86_64-w64-mingw32-gcc
Target: x86_64-w64-mingw32
Configured with: ../../src/configure --build=x86_64-linux-gnu --prefix=/usr --includedir='/usr/include' --mandir='/usr/share/man' --infodir='/usr/share/info' --sysconfdir=/etc --localstatedir=/var --disable-option-checking --disable-silent-rules --libdir='/usr/lib/x86_64-linux-gnu' --libexecdir='/usr/lib/x86_64-linux-gnu' --disable-maintainer-mode --disable-dependency-tracking --prefix=/usr --enable-shared --enable-static --disable-multilib --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --libdir=/usr/lib --enable-libstdcxx-time=yes --with-tune=generic --with-headers --enable-version-specific-runtime-libs --enable-fully-dynamic-string --enable-libgomp --enable-languages=c,c++,fortran,objc,obj-c++,ada --enable-lto --enable-threads=win32 --program-suffix=-win32 --program-prefix=x86_64-w64-mingw32- --target=x86_64-w64-mingw32 --with-as=/usr/bin/x86_64-w64-mingw32-as --with-ld=/usr/bin/x86_64-w64-mingw32-ld --enable-libatomic --enable-libstdcxx-filesystem-ts=yes --enable-dependency-tracking SED=/bin/sed
Thread model: win32
Supported LTO compression algorithms: zlib zstd
gcc version 13-win32 (GCC)
COLLECT_GCC_OPTIONS='-E' '-v' '-mtune=generic' '-march=x86-64'
/usr/lib/gcc/x86_64-w64-mingw32/13-win32/cc1 -E -quiet -v -U_REENTRANT - -mtune=generic -march=x86-64 <色々> -dumpbase -
ignoring nonexistent directory "/usr/lib/gcc/x86_64-w64-mingw32/13-win32/../../../../x86_64-w64-mingw32/sys-include"
#include "..." search starts here:
#include <...> search starts here:
/usr/lib/gcc/x86_64-w64-mingw32/13-win32/include
/usr/lib/gcc/x86_64-w64-mingw32/13-win32/include-fixed
/usr/lib/gcc/x86_64-w64-mingw32/13-win32/../../../../x86_64-w64-mingw32/include
End of search list.
# 0 "<stdin>"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "<stdin>"
COMPILER_PATH=/usr/lib/gcc/x86_64-w64-mingw32/13-win32/:/usr/lib/gcc/x86_64-w64-mingw32/13-win32/:/usr/lib/gcc/x86_64-w64-mingw32/:/usr/lib/gcc/x86_64-w64-mingw32/13-win32/:/usr/lib/gcc/x86_64-w64-mingw32/:/usr/lib/gcc/x86_64-w64-mingw32/13-win32/../../../../x86_64-w64-mingw32/bin/
LIBRARY_PATH=/usr/lib/gcc/x86_64-w64-mingw32/13-win32/:/usr/lib/gcc/x86_64-w64-mingw32/13-win32/../../../../x86_64-w64-mingw32/lib/
COLLECT_GCC_OPTIONS='-E' '-v' '-mtune=generic' '-march=x86-64'
$ gcc -E -v - </dev/null
Using built-in specs.
COLLECT_GCC=gcc
OFFLOAD_TARGET_NAMES=nvptx-none:amdgcn-amdhsa
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 13.3.0-6ubuntu2~24.04' --with-bugurl=file:///usr/share/doc/gcc-13/README.Bugs --enable-languages=c,ada,c++,go,d,fortran,objc,obj-c++,m2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-13 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/libexec --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-bootstrap --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-libstdcxx-backtrace --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --enable-libphobos-checking=release --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --enable-cet --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none=/build/gcc-13-fG75Ri/gcc-13-13.3.0/debian/tmp-nvptx/usr,amdgcn-amdhsa=/build/gcc-13-fG75Ri/gcc-13-13.3.0/debian/tmp-gcn/usr --enable-offload-defaulted --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu --with-build-config=bootstrap-lto-lean --enable-link-serialization=2
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 13.3.0 (Ubuntu 13.3.0-6ubuntu2~24.04)
COLLECT_GCC_OPTIONS='-E' '-v' '-mtune=generic' '-march=x86-64'
/usr/libexec/gcc/x86_64-linux-gnu/13/cc1 -E -quiet -v -imultiarch x86_64-linux-gnu - -mtune=generic -march=x86-64 -fasynchronous-unwind-tables -fstack-protector-strong -Wformat -Wformat-security -fstack-clash-protection -fcf-protection -dumpbase -
ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/13/include-fixed/x86_64-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/13/include-fixed"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/include"
#include "..." search starts here:
#include <...> search starts here:
/usr/lib/gcc/x86_64-linux-gnu/13/include
/usr/local/include
/usr/include/x86_64-linux-gnu
/usr/include
End of search list.
# 0 "<stdin>"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 0 "<command-line>" 2
# 1 "<stdin>"
COMPILER_PATH=/usr/libexec/gcc/x86_64-linux-gnu/13/:/usr/libexec/gcc/x86_64-linux-gnu/13/:/usr/libexec/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/13/:/usr/lib/gcc/x86_64-linux-gnu/
LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/13/:/usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/13/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/13/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-E' '-v' '-mtune=generic' '-march=x86-64'
ということで、これらのディレクトリのファイルがインクルードされたり、ライブラリーがリンクされたりする、ということが分かった。
OpenSSLライブラリ
MinGWのOpenSSLライブラリはパッケージが提供されていなかった。
Geminiさんに相談したところ、自分でコンパイルすれば使えるとのこと。
すべての機能を構築
まずは動作させられるの?というところが知りたかったので、すべての機能を構築してみた。
$ git clone https://github.com/openssl/openssl.git
$ cd openssl
$ git branch origin/OpenSSL_1_1_1-stable
$ git switch origin/OpenSSL_1_1_1-stable
$ ./Configure mingw64 --cross-compile-prefix=x86_64-w64-mingw32-
$ make
テストプログラムの構築でエラーが発生するけど、ライブラリ自体はできあがった。
構築したライブラリをスタティックリンクするには、以下のようなパラメーターを渡せばOK。
暗号化されたファイルを復号化する機能を実装したが、これをスタティックリンクしたところ8MBを超えるサイズになった。
$ make windows
x86_64-w64-mingw32-gcc -Wall -Wextra program.c -static \
-o program.exe \
-I/path/to/library/openssl/include \
-L/path/to/library/openssl \
-Wl,-Bstatic -lcrypto -lcrypt32 -lws2_32
すべての機能を構築した場合は、DLLを一緒に配布するなどした方が良さそうな気がした。
必要な機能に絞って構築
実行形式ファイル1つコピーすれば動作するようにしたかったので、復号化に関わる部分以外をできるだけ外した形でライブラリを構築することにした。
$ make clean
$ ./Configure mingw64 \
--cross-compile-prefix=x86_64-w64-mingw32- \
no-shared \
no-ssl \
no-ssl3 \
no-tls1 \
no-tls1_1 \
no-tls1_2 \
no-comp \
no-zlib \
no-psk \
no-srp \
no-cms \
no-engine \
no-ui \
no-err \
no-rc2 \
no-rc4 \
no-idea \
no-bf \
no-cast \
no-gost \
no-seed \
no-whirlpool \
no-ec \
no-dsa \
no-dh \
no-camellia \
no-des \
no-asm
$ make
上記パラメーターはGeminiさんに相談して作ってもらったもの。
外したライブラリについては、以下の通り解説してくれた。
| オプションカテゴリー | オプション | 目的 |
|---|---|---|
| ビルドタイプ | no-shared | 静的ライブラリ (.a) のみ作成。DLLのビルドを停止。 |
| no-providers | OpenSSL 3.0+のプロバイダー機能を無効化し、DLLビルド時の複雑な依存関係を根絶。 | |
| no-asm | CPU特有のアセンブリ最適化を無効化し、Cコードのみを使用することでサイズを削減。 | |
| プロトコル | no-ssl, no-tls1* | SSL/TLSプロトコル関連のコードを完全に削除。 |
| 不要な暗号 | no-ec, no-dsa, no-dh, no-des, no-camellia, no-idea, etc. | 楕円曲線、DSA、DH、TDESなど、使用しない公開鍵・対称鍵暗号を完全に削除。 |
| ユーティリティ | no-comp, no-zlib, no-engine, no-err, no-ui | 圧縮、外部エンジン、エラー文字列などの周辺機能を削除。 |
生成されたライブラリをスタティックリンクしたところ、当然ながらサイズが小さくなった。
その後、試しに新しい環境(ディレクトリ)で上記オプションを使って新規構築してみた。
するとライブラリのサイズが更に小さくなり、最終的にできあがった実行形式ファイルは 8.5MB → 5.8MB になった。
本来はcleanすれば同じ効果が得られるのだろうけれども、実際には過去に構築した何かが影響しているようだった。
開発メモ
作ったプログラムを管理する、デバッグする、といった基本的なところもGeminiさんに聞きながらやっている。
統合開発環境での経験しかないようなレベルなので、デバッガーの使い方すらもよく分からなかったが、ある程度分かったところでVisual Studio Codeでかなりいい感じにラッピングされていることもわかり、当時の感覚でデバッグができた。
Git
宅内でGiteaが稼働しているので、ソースはそこで管理。
立ち上げたばかりの環境なので、初期設定。
$ git config --global init.defaultBranch main
$ git config --global user.name hoge
$ git config --global user.email hoge@hogeserver.hogeddns.jp
開発用のディレクトリで初期化、ファイルを追加。
$ git init
$ git add .
後は、Giteaで新しくリポジトリーを作り、そこに書かれた方法で連携させればOK。
GDB
GNUデバッガーを使ってみる。
gdb <module.name>
> b module.c:<line number>
> run <parameter>
Visual Studio Codeでデバッグするためには、2つのファイルを書けば良いみたい。
Geminiさんが教えてくれたものを、ほぼそのまま使った。
.vscode/tasks.json
{
"tasks": [
{
"label": "Build: make debug", // タスク名 (わかりやすい名前に変更)
"type": "shell", // シェルコマンドを実行するように指定
"command": "make", // 実行するコマンド
"args": [
"debug" // 実行する Makefile のターゲット名 (例: debug-linux)
],
"options": {
// Makefile があるプロジェクトのルートディレクトリで実行
"cwd": "${workspaceFolder}"
},
"group": {
"kind": "build",
"isDefault": true // デフォルトのビルドタスクに設定
},
"problemMatcher": [
"$gcc" // エラーメッセージをGCC形式でパース
],
"detail": "Makefileの 'debug' ターゲットを実行します。"
}
],
"version": "2.0.0"
}
.vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "(gdb) Launch", // この設定の名前
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/path/to/binary", // 1. ここを実行ファイル名に変更
"args": [
"parameter1"
],
"stopAtEntry": true,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Set Disassembly Flavor to Intel",
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
}
],
// 2. ビルド前タスクの名前を指定
"preLaunchTask": "Build: make debug"
}
]
}
このファイルを追加したところ、昔懐かしいVisual Studioでのデバッガーのように簡単にブレークポイントを設定して、変数の中身を見ることができた。
Python
参考にしたPythonのプログラムがどのように動作しているのかトレースしようとした。
python3 -m pdb <module.name>
> b <line number>
16進数が格納されたバッファの表示
> p format(<value>, 'x')
> p hoge.hex()
GDBの設定があんな感じなので、これも同じようにVisual Studio Codeでデバッグできそうな気がするけれど、今回はそれ程深く見なかったのでエディターでソースを見ながらブレークポイントを設定し、実行しては変数の中身を表示して確認、といった操作で内容を理解した。
さいごに
Visual Studio Codeは重たいな…と思って、Zedというのを試してみたのだけれど、仮想環境ではグラフィックス性能が出なかった。
vulkanでは駄目なのかな?とOpenGLで動くようにしてみようと思い、ソースからビルドしてみたものの、
- コンパイルのためにディスク容量をかなり使う。
- コンパイルのためにメモリーをかなり使う。
という状況でなかなか大変だったし、最終的にビルドが完了させられなかった。難しいものですなぁ…
ということで、今回のメモはここまで。


コメントはこちらから お気軽にどうぞ ~ 投稿に関するご意見・感想・他