【お知らせ】プログラミング記事の投稿はQiitaに移行しました。GAMEをC++に移植して、Interix上のクロス開発環境(id:n7shi:20091212)でNetWalker用にビルドしました。バイナリ(NetWalker/Windows)とソース(gcc/VC)を置いておきます。MizuGame-Native.zip実機がないため原作者のid:fslashtさんに動作確認をお願いしたところ、無事に動作したとのことです。NetWalker動かしてみました!バッチリ動いてますよ〜。性能不足でFPSは半分くらい・BGMが途切れがちです。いえ動作自体は完全移植ですね。マップエディターも動くとは。SDLUtils.cpp/hでSDL.NETとの差異を吸収ですか【追記】id:fslasht:20091213にスクリーンショット付きでご紹介いただきました。2009のレポート(id:fslasht:20091110)を読んで、今後ARM搭載デバイスが色々登場すれば面白そうだと思いました。スマートフォン以外でもARM搭載の非WindowsのNetBookが出てきそうです。この手のマシンとして先陣を切ったNetWalkerをリファレンスマシンに見立てて、開発手法を確立してみたくなりました。NetWalkerはUbuntuのためクロス開発もUbuntu上で行えば簡単ですが、Ubuntuを常用していなければ敷居が高いです。Windows上でクロス開発するため、開発用パッケージを集めて(id:n7shi:20091210)、クロスコンパイラを用意(id:n7shi:20091212)しました。環境を構築しても、実際にビルドしないと面白くありません。そこできっかけを頂いたid:fslashtさんの作品を移植してみました。将来的にはC#のネイティブコンパイラを作りたいとも考えているため、一度は手作業でのネイティブ化を経験しておこうとも思いました。当初はC#のクラスをひとつずつC++/CLIに書き換えて、常に動作確認をしながら移植しようと考えていました。C#とC++/CLIは基本的に同じILを別の表記にしたものなので、機械的に書き換えられます。常に動作確認する方針の重要性についてはid:aoisomeさんの素晴らしい記事があります。書き換える方針により、以下のステップを踏みました。GAME本体だけでなくSDL.NETもどうにかしないといけなかったためです。逆コンパイルC#からC++/CLIへの書き換えに着手したところ、クラス間の循環参照が多いため、ある程度一気に移植しなければ動作確認できないことに気付きました。一気に移植してから問題箇所を特定するような作業はid:n7shi:20091011で懲りていたため、別の方法に切り替えました。C#とC++/CLIはILを媒介に変換できるため、C#のバイナリを逆コンパイラでC++/CLIにできそうです。コメントやローカル変数名が失われますが、元のソースがあるので必要に応じて復元すれば良いと割り切りました。Reflectorを試したところ、C++/CLIはサポートされていませんでした。C++(以下MC++)はサポートされていたため、C++/CLIではなくMC++に変換しました。生成されたコードは宣言と定義が一緒になった.cppファイルだけで、そのままではコンパイルが通りません。Studioのフォームデザイナのように全部ヘッダに書くという荒技も使えません。仕方ないので手動で.hと.cppに分離しました。機械的に変換しただけあって、一部変換が不完全な部分を手直ししただけで動きました。GAME本体だけでなく、参照しているSDL.NETも何とかしないといけません。SDL.NETはSDLの単純なラッパーではなく、クラスライブラリとして使いやすいように再構築されているため、単純に生のSDLを使うコードに書き換えることができません。SDL.NETも逆コンパイラで変換できますが、そのままでは巨大過ぎて変換後の手直しが大変です。変換の前段階として使っていないクラスやメソッドを削りました。必要最低限に軽量化したSDL.NETをReflectorでMC++に変換して、ビルドが通るように修正しました。SDL.NETはP/InvokeラッパーのTao.Sdlを通してSDLを呼んでいます。Tao.Sdlは単純にP/InvokeでSDLをラップしただけのもので、クラス名を取り除くだけで直接SDLを呼べるようになりました。MizuGame-MC-SdlDotNet-Light.zipC++からSDLを呼ぶにはヘッダとインポートライブラリが必要のため、上記アーカイブに同梱して相対パスで参照しています。別途SDLを用意してパスを通す手間はありません。GAME本体に移して、必要なくなったSDL.NETのコードを削りながら、最終的にSDL.NETを完全に取り外しました。bitbltや文字列描画など直接SDLを呼び出すと冗長になる部分は関数を作って、id:fslashtさんのご指摘通りSDLUtils.cppにまとめました。GAME本体だけになったので、MC++をC++に書き換えれば完成です。__gcを外していくだけの簡単なお仕事です。C++化に伴いGCがなくなります。GAMEではオブジェクトの所有権が明確に構造化されて寿命がはっきりしていたため、自動変数をデストラクタで解放するRAIIの手法で対処できました。インスタンスをあちこち参照で取り回していたら、スマートポインタを持ち出す必要があったかもしれません。ネイティブ化により、WindowsではVC++だけでなくgccでもビルドして動くようになりました。バグ修正ARMのクロスコンパイラでもビルドできたので、これで完成かと思いました。念のため手元にあったFreeBSDで動作確認したところ、問題が発生しました。文字が1文字しか描画されない(ワイド文字問題)タイトルからゲームに移るときに落ちる(ゼロ除算問題)今までは機械的な変換作業だけで順調でしたが、ここに来て初めて移植に伴うバグに遭遇しました。ちなみにオリジナルのC#版はFreeBSDでもmonoで動きます。ワイド文字問題SDL_ttfの描画関数TTF_RenderUNICODE_Blended()は文字列として16bit整数型の配列を要求します。当初はWindows上で作業していたため、単純にL"ABC"のようにワイド文字列を渡していました。FreeBSDではwchar_tが32bitのため、単純にワイド文字列を渡すと1文字目の上位ワードが'\0'として解釈され、最初の文字しか表示されません。gccのオプション(-fshort-wchar)でwchar_tを16bitにしたところ、文字列のフォーマットに使用していたstd::wostringstreamで落ちるようになりました。libstdc++がビルドされたときのwchar_tのサイズと矛盾するのが原因です。std::basic_ostringstreamでも改善しなかったので、オプションでの対処は諦めました。GAMEで使用する文字はUnicodeの基本多言語面に収まっているため、単純にwchar_tの配列を16bitの配列にコピーすることで対応しました。念のため基本多言語面に収まらない文字は'?'で置換しています。buf;#endifこのコードではワイド文字列がUnicodeではない環境で問題が起きますが、今はWindowsとFreeBSDとNetWalkerで動けば良いので、気にしないことにします・・・。ゼロ除算問題gdbで追ったところ、SDL_mixerのMix_PlayMusic()内でSIGFPEが発生していました。zeroが発生しました。余談ですが、Wineで落ちた後に起動するデバッガではPEとELFが混ざってとんでもないことになります。スクリーンショットそれでゼロ除算ですが、実は心当たりがありました。SDL.NETを取り外すときに例外処理は全部無視しましたが、Audio/Music.csに以下の記述があったのです。}}※MusicPlayer.Play()でMix_PlayMusic()が呼ばれています。SDL.NETでも諦めているくらいなので、原因は調査しないで無視することにしました。ret;#endif}この修正により、FreeBSDでも動作するようになりました。同じソースをNetWalker用にクロスコンパイルしたものも動作したそうです。SIGFPESIGFPEで検索に引っ掛かるかもしれないので、参考までに試したことを書いておきます。初めはSIGFPEをSIG_IGNで無視しようとしたのですが、SIG_IGNが無視されてコアダンプしたので大域脱出で逃げました。検索しても無視の定石のようなものは出てこなかったのですが、ハンドラを設定すると無限に呼ばれ続けました。Nextではなく再試行で、エラーの原因を取り除かないと無限ループになるため、SIGFPEは無視できないようです。本気で無視しようとするならレジスタ書き換えなどで計算結果を改竄するしかないのですが、今回はx86だけでなくARMでも動かす必要があるので諦めました。無視してINT_MAXを返してもSDL_mixerが誤動作するだけで意味はなさそうです。今回の移植では私自身色々と勉強になりました。いう機会でもなければ経験できないことばかりです。きっかけをくださったid:fslashtさんに改めて御礼申し上げます。ありがとうございました。(1)はてなブログをはじめよう!n7shiさんは、はてなブログを使っています。ブログを報告するスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。