![見出し画像](https://assets.st-note.com/production/uploads/images/120158791/rectangle_large_type_2_6ea6bd8a5569045fe2082253152537b0.png?width=800)
GDExtensionシンプルに環境構築するよ Pt.2
前回の続き
まずはMinGWのセットアップです。末端、呼ばれて使われる側から準備していきます。
MinGWについて
MinGWはコンパイラやリンカ、標準ライブラリからなる、プログラミングツール一式です。Unix系が本家的な存在みたいです。それのWindows版です。MinGWのWはWIndowsのW。C言語コンパイラがGCCという名前なので、そっちの呼び方もされるようです。MinGWはいわば"WindowsのGCC"という感じだと思います。
無料で使えて、インストール不要、ユーザ登録不要です。
ダウンロード
GitHubの<niXman/mingw-builds-binaries>からダウンロードできます。
ちゃんと公式Webからたどることができるリポジトリです。
【公式】
https://www.mingw-w64.org/
【GitHub】
https://github.com/niXman/mingw-builds-binaries
じゃあここからダウンロードするわけですが・・・
![](https://assets.st-note.com/img/1698378950971-uC4yiqefrg.png)
<https://github.com/niXman/mingw-builds-binaries/releases/tag/13.2.0-rt_v11-rev1>
どれ選んでいいか、ほんとにわからん!
ひとつひとつググって紐解いていきます。
i686 / x86_64
i686は2000年前後のIntel CPUのようです。要するにx86です。32bitです。
一方、x86_64はよく見る表記ですが、「x86と互換性のある64bit」という意味合いみたいです。歴史的経緯があってこう呼ぶみたいです。要するにx64です。64bitです。
2023年現在、新作ゲーム制作に32bitを選ぶ理由もないと思うので、x86_64を選択します。
posix / win32 / mcf
これはスレッドの実装方法です。
まずposixは、OSが違ってもできるだけソースコードを共通化するための規格らしいです。
次に、win32はWin32 APIが提供するスレッド機能を使っているようです。そのため速い。でも新しいC++(C++11)のthread機能に対応していないとのこと。
最後のmcfは、posixとwin32のいいとこどり。速いし、C++11のthreadに対応。
さて、Godot公式ドキュメントによると、「もしGodot本体を自前でコンパイルする際には、posixを選択せよ」とのことです。
"Be sure to install/configure it to use the posix thread model."
きっとGDExtensionでも同じことだと思います。なのでposixを選択します。
dwarf / seh
例外処理(tryとcatch)の実装方法のようです。dwarfは昔から存在する規格。32bitのみ対応。sehはWindowsの機能を使った実装のようです。
CPUに64bitを選択したので、こちらはsehが唯一の選択肢です。
ucrt / msvcrt
ucrtは「UniversalなCRT」で、MSの新しいCランタイムです。Windows10以降は標準でdllがインストールされてるようです。確かにWindows11のパソコンには、<C:/ Windows/ SysWOW64>にucrtbase.dllがあります。
msvcrtは昔からあるおなじみのMSVCRTです。VSインストールすると付いてくるやつです。
CRTは、C標準ライブラリ(stdioとか)のDLL実装のことだという認識です。
今回はひとまずmscvcrtを選択しました。なぜなら、Godot本体に合わせた方が確実だと思ったからです。godot-cppのC++コードが具体的に何やってるのかわかりませんが、もしGodot本体と同じライブラリを使ってる前提でデータをやり取りしていたら、齟齬が生じます。
Godotエディタ(Godot_v4.1.1-stable_win64.exe)が使用しているDLLを、解析ツールで調べてみると、MSVCRT.dllを使っていました。なのでここではMSVCRT版を選びます。
以上を踏まえて、今回は、
x86_64-13.2.0-release-posix-seh-msvcrt-rt_v11-rev1.7z
を選択しました。
ダウンロードする7zファイルは80MBですが、解凍すると700MB程度になります。(!?)
これをストレージの適当な場所に置きます。
![](https://assets.st-note.com/img/1698506743539-bmtJVqoy6r.png)
<mingw64/bin>フォルダの中に、C++コンパイラのg++.exeや、C++の機能を提供する7つのdllが入っています。
Pathの追加
環境変数"Path"に、MinGWのbinフォルダパスを追加します。必須です。
例: "C:\mingw64\bin\"
環境変数を追加する理由
[1]
コンパイラのg++.exe自体のために必要です。
g++.exeは、同フォルダ内のlibwinpthread-1.dllを、自分自身の処理のために使ってます。libwinpthread-1.dllは、ビルド成果物に使わせるためだけに同梱されているわけではないようです。
g++.exeは、
・特に外部からパスが与えられなくても
・カレントディレクトリがどこであっても
<mingw64/>内の他のexeを起動できますし、標準ライブラリのヘッダーファイルも<mingw64/>下から取ってきます。自分を基準にして相対パスで判断してるんだと思います。
でもlibwinpthread-1.dllだけは、環境変数"Path"でパスが与えられるか、binがカレントディレクトリになってないと、見つけ切らないようです。なんでなんでしょうね。
[2]
godot-cppにMinGWの場所を教えるために必要です。
godot-cppの提供するスクリプトは、環境変数Pathを取得しています。これで<mingw64/bin>の場所を特定して、SConsに通知してるんじゃないかな、と思ってます。違うかも。
godot-cppのSConstuctスクリプトの一部
env.PrependENVPath("PATH", os.getenv("PATH"))
[3]
SConsにMinGWの場所を教えるためにも必要です。
SConsは、MinGWの場所を"Path"から取るみたいです。スクリプト内のビルド設定で、tools = ['mingw']と指定すると、"Pathから"g++.exeを探そうとします。もしかしたらgodot-cppのスクリプト自体はMinGWを探してなくて、SConsに任せてるだけかもしれません。
[4]
g++.exeをコマンドプロンプト(cmd.exe)から起動するときに、少し便利になります。便利になるだけで、必須ではないです。
開発環境の動作テストのために、g++.exeでHello Worldプログラムなどをビルドしたくなるかもしれません。そのとき、カレントディレクトリがどこであっても、g++とコマンドを打つだけで起動できます。これはそもそもcmd.exeの機能です。
標準ライブラリの理解
MinGWで使う標準ライブラリについて、こういう風に理解しました。
あまり自信ないので話半分に聞いてください。
C++標準ライブラリ
・静的リンクライブラリファイル
→<mingw64/lib>下のaファイル(.libではない)
・DLL
→<mingw64/bin>中のdllファイル
厳密には、これらは標準ライブラリだけでなく、C++自体の機能(例外処理やスレッド)も受け持っています。
C標準ライブラリ
C++の場合と違い、Cの標準ライブラリの実装はMinGWの中に含まれてないように思います。OSプリインストールのMSCVRT(またはUCRT)を呼び出すことで実現してます。
また、C++のライブラリも最終的には、MSCVRT(またはUCRT)を呼び出しているようです。下図参照。
いわゆる入出力処理やOSとの連携処理(スレッドとか)の末端は、MS製DLLに委ねています。
![](https://assets.st-note.com/img/1698487991239-J7BaZWTH9P.png?width=800)
環境変数Path
知ってるようで知らない、環境変数"Path"の話。筆者はよくわかってませんでした。これを機にちゃんと把握しておこうと思いました。
"Path"という変数名の環境変数は、「なんらかの実行可能ファイルがこのフォルダリストのどこかにあるよ」くらいの意図で用意されています。めちゃゆるいです。
使われ方
[1]
Win32 APIに、DLLをロードする関数(LoadLibraryExA等)があります。これの関数は、引数の値次第では"Path"のパスリストを探索範囲に含めます。
また、DLLは依存関係を持ってます。DLLが他のDLLを呼ぶ場合は、そのdllファイルも検索されます。芋づる式に。この探索範囲にも、引数の値次第では"Path"のパスリストが含まれます。
ちなみにGDExtesionの場合は依存dllの探索に"Path"のパスを含めません。色々試しましたが、そういう動きしてます。
これについては後述。
[2]
Windowsアプリは一般的に、あてもなくexeやdllを探さないといけなくなったときに、この"Path"のパスリストを頼りに目的のファイルを探索することがあるようです。
godot-cppのスクリプトはこれによってMinGWとPythonのexeパスを取得します。なのでGDExtensionビルドに必須です。
[3]
また、コマンドプロンプト(cmd.exe)もまた"Path"を使います。コマンドが打たれると、"Path"のパスリストの中から「打たれたコマンド.exe」の名前のexeを探します。カレントディレクトリがどこにあっても、exe名だけで実行することができます。
そういうことだったんだなあ~~~。
[4]
他にもあるかもしれません。
DLLが見つからない?
この章的にはまだ先の話ですが、SConsで作ったDLLをGDExtensionとしてGodotプロジェクトに追加すると、次のようなエラーが出ることがあります。
Can't open dynamic library:
Error: Error 126: 指定されたモジュールが見つかりません。
全文
H:/GodotSpecTest/bin/libgdexample.windows.template_debug.x86_64.dll.
Error: Error 126: 指定されたモジュールが見つかりません。
.core/extension/gdextension.cpp:455
- GDExtension dynamic library not found: H:/GodotSpecTest/bin/libgdexample.windows.template_debug.x86_64.dll
Failed loading resource: res://bin/example.gdextension.
Make sure resources have been imported by opening the project in the editor at least once.
DLLが無い!? でもそこにあるはず・・・。
これは前述の、依存関係のせいです。たぶんそうです。大体そうです。
LoadLibraryExA関数は、依存関係がオールクリアなDLLを探します。
指定された名前のdllを見つけても、「依存先の依存先の依存先の依存先のDLL」が見つからなかったら、そのdllは拾われません。なので、依存関係をクリアしない限り、見つからなかったよ、と報告されるわけです。
で、前述の通り、GDExtensionでは依存関係の探索に環境変数"Path"のパスリストは含まれません。GDExtension対象のDLL自体は、.gdextensionファイル内でユーザの書いたres://パスを直接指定してロードされますが、そのDLLが内部で使うDLLは、"Path"から探されることはありません。
Godot本体のソースコードみると、GodotはLoadLibraryExAの引数に、LOAD_LIBRARY_SEARCH_DEFAULT_DIRSフラグを優先して使うようです。このフラグはMSのドキュメントによれば、『標準検索パス内のディレクトリは検索されません』とのこと。
これ、ハマりポイントだと思います。
MinGWでC++コードをオプション無しでコンパイルすると、<mingw64/bin>内のdllに依存したDLLになります。
これをそのままGDExtensionすると、前述のエラーが出るんです。
解決策としては、次のどれかだと思います。
1. C++ライブラリを静的にリンクするようにする。
2. DLLをゲームに含める。(再頒布していいものなのか不明)
3. プレイヤーのPCにインストールしてもらう。
Dependencies
exeやdllのDLL依存性を調べるには、lucasg/Dependenciesというツールが便利みたいです。
https://github.com/lucasg/Dependencies
インストール要らずで、軽量で、めちゃ簡単。dllをGUIにドラッグ&ドロップするだけですぐ使えます。
DLL内の公開関数一覧も見れます。
exeの中身も見れます。GodotのexeがMSVCRT.dllを使ってるのはこれで調べました。
この時点でのパソコンの状態の変化は次の通りです。
【手動で配置したツール】
C:\mingw64\bin
【環境変数 "Path"】
C:\mingw64\bin\
なので、パソコンを元に戻したければ、これらフォルダや"Path"を消せば元に戻れるはずです。
つづく・・・!
この記事が気に入ったらサポートをしてみませんか?