GDCメモ
Table of Contents
1 はじめに
GDCをCygwin/MinGWで使用した際に、色々ハマった点をなんとなく解決したメモです。 この先ずっと同じ方法が使えるかも知れませんし、使わなくても良いように なるかも知れません。
2 一般的にはまりそうな事
何がマズいかすぐに判らないけど、判ってみるとなーんだって事とか。
2.1 import するモジュールを変更した時は上位モジュールもコンパイルしなおす
クラスをfoo.dというファイルに記述し、main.dでfoo.dに記述されたクラスを インスタンス化して使用するような場面は普通にあります。この時、クラスの 内容を変更したのでfoo.dをコンパイルしてfoo.oを生成し、main.dには 変更が加わっていないので リコンパイルしてないmain.oを使用すると、リンクでエラー する事無く、実行ファイルが生成されてしまうことがあります。所が実行すると いきなりSegmentation fault でずっこける場合があります。 特に、makeを使って変更のあったファイルだけをコンパイル対象にしていると陥ります。 一度make cleanした後、makeし直してみれば、恐らくmain.dを コンパイルした時に、メソッドの引数が違っているなどのエラーが出るので気づくでしょう。
2.2 ガベージコレクトされない場合がある
Phobosのガベージコレクタ(GC)は「ConservativeGC(保守的GC)」を採用しています。 この方式ではスタック上の値を全てメモリへのポインタとみなしています。もしGCで管理して いるメモリ領域を指している場合はメモリ回収の対象にしません。 例えば、次のようなコードでは未使用となっているハズのメモリが回収されません。
// // GCされない例 // import std.stdio ; class Storage{ private byte[] array ; this(int size){ array.length = size ; } } int main(string args[]) { Storage storage ; int[4096] adlist ; for( int i=0 ; i<adlist.length ; i++ ){ adlist[i]=0x00100000*i ; } writef("===loop start===============================================================\n") ; for( int i=0 ; i<4096 ; i++ ){ storage = new Storage(0x00100000) ; //(i>0)の時(i-1)で確保したインスタンスは未使用になる core.memory.GC.collect() ; } writef("===loop end=================================================================\n") ; return(0) ; }
この例では、スタック上に確保される静的配列 adlistに1MBおきの(ポインタではない)単なる値を保持しています。 ここで 1MB以上のメモリを確保すると、adlistの値のいずれかが必ず確保したメモリブロック を指してしまう事になります。この為、実際には未使用になっているメモリ領域 内を指している値があると判断して、そのメモリ領域は回収の対象にならないという流れです。 解決方法はいくつか考えられます。
- adlistをスタック上に確保される静的配列ではなく動的配列に変更する。
- 確保した領域が不要になったと判断できる所で、明示的にdelete文で 開放する(C言語で言うところのmalloc()に対応するfree()を実行する感じでしょうか)。 このとき、例で言うと クラスStorageのデストラクタに「delete array ;」を加える必要があります。 core.memory.GC.collect() ;の手前に「delete storage ;」を加えるだけでは足りません。 インスタンス自体は削除されますが、そこから参照しているarrayまで開放するという意味には ならないからです。
これはGDCだけでなくオリジナルのDMDでも言える事です。
3 Windowsアプリを書く時にハマった事
GDCでもWindowsアプリケーションを作成する事はできます。 所が一見正しいコードの様に見えるのに、何故か謎のアクセス例外などが 出て、うまく動かない事がありました。その時に解決した方法のメモです。 中には何故それで良いのか理由が判らないけど解決になっているものとか あるのがへっぽこなのですが(^^;、そういうものだという事で。
3.1 DLLとリンクする際は extern(Windows) を使用する
DLL内関数とリンクをする際、単純にextern(C)で宣言するとリンクに失敗します。 extern(Windows)宣言するとリンク可能になります。 かなり古いGDCではextern(Windows)宣言が使用できませんでした。その為、 C言語で書いたラッパーを噛ませて、リンクする必要がありました。
3.2 WinMain()から始めずにmain()内でメッセージループを回す
DMDのWindowsアプリを書く例の通りにすると、何故かうまく動いたり動かなかったり 挙動不審な実行ファイルが生成されました。WinMainで始めると、スレッド自体が ガベージコレクタの管理下に無い状態で実行されるようで、ガベージコレクタの 管理が壊れてしまう場合があるようです。単一スレッドで動作する事を保証する為に、 main()内でメッセージループを回すという方法で安定して動作するようになりました。
3.3 プロシージャ関数はextern(Windows)宣言する
メッセージを受け取るプロシージャ関数はextern(Windows)宣言する必要があります。 WNDCLASSのlpfnWndProcメンバーに WNDPROC型で入れる場合、キャストすると それとなくコンパイルできてしまうのですが、動かない実行ファイルができあがります。
3.4 Window描画の際使用するPAINTSTRUCTは static宣言しておく必要がある
WM_PAINTメッセージ処理に於いて、PAINTSTRUCT 変数を使用するのは定石なのですが、
一時変数として使用する際、static宣言する必要がありました。
case WM_PAINT: static PAINTSTRUCT ps; hdc = BeginPaint(hwnd, &ps); : EndPaint(hwnd, &ps); break ;
static宣言が無いと、動いたり動かなかったりする場合もあれば、動かない場合に
Windows98ではシステムフリーズしてしまうという致命傷に陥りました。
2010/11/03追記:
スレッド生成の方法がまずかったのが原因だったのだろうと思われます。 (gdc 0.24, using dmd 2.014)ではひとまず問題無さそうです。 逆にstatic宣言すると複数のインスタンスで共用されてしまいますので、 マズい場合があるかも知れません。
4 そもそも何故GDCを使うの?
TANEがD言語を最初に使用したのは2004年10月頃でした(8年経ってたのに今気づいた(^^;; @2012/12現在)。 そのときにCygwinパッケージにgdcが存在していた為、単純にそれを使用したというのがきっかけでした。 CygwinではOpenGLなどのCライブラリが多く存在していた事もあり、単純にC言語の関数を リンクできるので特に抵抗無く使えました。 むしろDMDでリンクできるライブラリ群を全く持っていなかったので、そちらの環境を構築 する方がコストが高かった訳です。
出来合いのgdcバイナリを少しの間だけ使っていたのですが、最新版に追従している訳では なかった為、すぐに野良ビルドを始めます。いつもphobos由来の不具合に見舞われる感じ だったので、コンパイルオプションを変えながらデバッグを行えたのが GDCを使う最大の利点だったように思います。
また、SourceForgeでメンテされていた頃はPowerPCのLinuxなどをターゲット としてビルドできました。実際にPlayStation3上のLinuxでgdcをネイティブビルドしてちゃんと 実行バイナリを生成&実行できたのには驚きました。 2012年12月現在はPowerPC対応は無い事になってます。ただ、コンパイラ本体は PowerPCをターゲットとしてビルドする事は可能で、単にphobosやdruntimeがメンテナンス されていないので事実上使用できないという感じです。 最近はARM/Androidがターゲットに追加されているようです。
そんな訳で、ちょろっとかじって、ふーんって感じで終わるならばGDCを使用する利点は 全く無いかも知れません。しかし、ガッツリ遊ぶという点ではGDCは選択肢の一つとして アリかな?と思います。ただ、最新のフィーチャーでいち早く遊びたいとなるとDMDを 使うしかないでしょう。
5 その他
D言語の利点の一つにC言語で書かれた関数をそのままリンクできるという点が あります。しかし実際には結構ドはまりする点が多くあり、そのデバッグに苦労して みました。Dの場合、Javaなどと違って、強力なコンポーネントの上で使用するもの ではなく、本当にコンパイラだけしか存在していないという状況です。 それを貧弱と言うか、しがらみが無いと言うかは人それぞれだと思いますが、 「割としょーもない事でハマる=使えねー」は寂しいので、こんなメモを作ってみた次第です。