C#は電気羊の夢を見るか?(引っ越し中)

dtiブログが閉鎖とのことでhttp://tanocs.blog.fc2.com/へ引っ越します。

C#で、ネイティブなEXEを作成する

通常であればC#で開発したアプリケーションは.Net Frameworkが
インストールされた端末か、Monoが有る環境でしか実行できない。

しかし、Monoを使用してmkbundleというコマンドにてネイティブ化を行うことで
Monoや.Net Frameworkのインストールがされていない端末でも、
インストール作業なしで実行を行うことが可能になります。


この手法、海外のサイトを調べてみるとcygwinを使用した方法が主流ですが、
根っからの窓っ子の自分にはどうもとっつきにくい。
調べていくと、cygwinを使わずとも出来ることが解ったので、cygwinは抜きの手順です。


尚、出来上がるexeの実績については、SharpDXを使い1000本ほど配布を行いましたが、
WinXP、Vista、7にて32、64bitを問わず、実行できなかったという回答をもらっていないことから、
割と多くの端末で動作しているものと思われます。



記事を起こした時点での動作確認は以下のバージョンにて行なっています
・[Mono] 2.10.9
・[gcc for Windows] 4.7.2
・[GNU utilities for Win32] Last Update:2011-04-05


以下に長々と書いていますが、スムーズに行けば
準備3ステップ、実行3ステップの単純なものです。

◆準備
Monoをインストール
gccコンパイラをインストール
GNU utilities for Win32を配置

◆実行
mkbundleを実行
⊇侏莨紊った[temp.c]を編集
コンパイルを行う


■準備編
Monoインストーラをダウンロードする[mono-2.10.9-gtksharp-2.12.11-win32-0.exe]
http://www.mono-project.com/Main_Page

Mono for Windows, Gtk#, and XSPをダウンロードして、インストールしてください。
この時、インストールしたフォルダはコマンド等で頻繁に使用しますので、覚えておいてください。

★これ以下では、例として[C:Mono]にインストールを行ったものとして記事を書きますので、
別の場所に保存した方は適宜読み替えてください。



Win32の『32bit版』のgccコンパイラをインストールする[gcc-4.7.2-32.exe]
http://www.equation.com/servlet/equation.cmd?fa=fortran

!!64bit版のOSであっても、必ず32bit版をインストールしてください!!

☆ユーザー名が日本語の方はフォルダ名が化けているので修正してください。
 この時、日本語が含まれていないほうが望ましいと思われます。



GNU utilities for Win32を配置する[UnxUtils.zip]
http://sourceforge.net/projects/unxutils/
[UnxUtils.zip]を解凍し中に入っている[sh.exe]を[c:monobin]に配置する

☆後述しますが、このファイルは今回の目的にはかなりリッチなモノです。
また、実行時ウィンドウを小さくするバグを持っていて、最適とは言いがたいです。
詳細は[[脱線]]を御覧ください。



ず撞動して準備完了
!!環境変数を反映させるため手動での再起動が必要です!!


[[脱線開始]]
ここで必要なのは[sh.exe]という名前で[as -o temp.o temp.s]を実行してくれるアセンブリです。

.Netアセンブリのexeでも問題なく動作しますので、良く解らない物を使いたくないという人は、
適当な[HelloWorld.exe]を[sh.exe]にリネームして配置してしまうのも手です。
この場合mkbundle終了後cmdより[as -o temp.o temp.s]を手動で実行する必要があります。

ベストなのは、HelloWorldではなく[as -o temp.o temp.s]をキックするアセンブリであるのは
言うまでもありません。

さらに、mkbundleはオープンソースになっているため、
自作にて[sh.exe]を使わないようにカスタムする手もあります(後述)
[[脱線終了]]




■実行編
mkbundleを実行してgccアセンブラソースとmonoオブジェクトを取得する
【1.スタートメニューから「Mono-2.10.9 Command Prompt」を実行】


【2.ネイティブ化したい実行ファイルのあるフォルダに移動する】


【3.mkbundleを実行する】
・DLLなし
 mkbundle -c -o temp.c --deps --keeptemp hoge.exe

・DLLあり
 mkbundle -c -o temp.c --deps --keeptemp hoge.exe foo.dll bar.dll

・SharpDXを使う場合の具体例
mkbundle -c -o temp.c --deps --keeptemp Sample.exe SharpDX.Direct3D9.dll SharpDX.dll


!!ウィンドウが小さくなった後も引き続き[as -o temp.o temp.s]が実行されるので、
ウィンドウの×で終了しないでください!!

☆GNU utilities for Win32を使用するとウィンドウが小さくなってしまい、
 mkbundleのログが見えなくなってしまいます。

☆[as -o temp.o temp.s]は一緒に入れ込むdll次第では長時間となる場合があります

☆ログメッセージの
warning: pointer targets in passing argument 2 of 'mono_register_co
nfig_for_assembly' differ in signedness [-Wpointer-sign]
については後述。


【4.[exit]コマンドで終了】
ウィンドウが小さくなっていて見づらいですが、
[as -o temp.o temp.s]の実行が終了して入力が可能になるのを待って入力してください。


【上手くいかない時は】
☆SharpDX等インターネットからダウンロードしたdllなどのアセンブリを使用する場合、ブロックの解除が必要

☆署名有りDLLはダメなことがあるので、署名なしのものを利用してください。
 ここで通ってしまうこともありますが、後々問題を引き起こすので、署名なしのものを使ってください。

☆mkbundle実行時、exeの作成時と違うバージョンのdllを同梱しようとするとFileNotFoundになるので注意
 ソリューションをバッチビルドでリビルトしてやれば直る



⊇侏莨紊った[temp.c]を編集
#ifdef _WIN32
#include
#endif

//【以下2行を追加】
#include<direct.h> //<>を半角にしてください
#undef _WIN32

/////////////////////////////////////////////////

int main (int argc, char* argv[])
{

//【以下5行を追加】
char Path[4];
if(0 != GetModuleFileName( NULL, Path, 4)) {
Path[3] = '¥0'; //¥を半角にしてください
chdir(Path);
}


"#undef _WIN32"を書かないとコンパイル時にエラーが出て先に進めない
他は日本語を含むパスから実行するときにmono-2.0.dllが見つかるようになる魔法の呪文



コンパイルを行う
gcc -g -o AssemblyName -Wall temp.c -mms-bitfields -mwindows -IC:/Mono/include/mono-2.0 -mms-bitfields -mwindows -LC:/Mono/lib -lmono-2.0 -lws2_32 -lpsapi -lole32 -lwinmm -loleaut32 -ladvapi32 -lversion temp.o

!!以下の引数についてMONOのインストールディレクトリに適宜を記述を変更すること!!
-IC:/Mono/include/mono-2.0
-LC:/Mono/lib


☆「アクセスが拒否されました」とかメッセージが出る場合
cygwinがすでにインストールとかされてて、インストールしたgccより優先されてしまっているので、
環境変数の順番変えてインストールしたwin32のgccが実行されるようにしてくりゃれ。


☆cannot find -lmono-2.0とかってメッセージが出てる場合
このメッセージが出力されるのは64bit版gccをインストールしているから
Mono-2.0は32bitアセンブリのため、読み込むことが出来ずエラーメッセージが出力される。

解決するにはインストール時に登録されたgccの環境変数を手動で消して、
インストールしたフォルダを消して32bit版を再インストールしてください。

[蛇足]64bit版で実行しても速度に大きな改善はないので、面倒の少ない32bit版で行うのが良いと思う


☆最適化オプションについて
ある程度、最適化オプションを自由に設定できるが、思ったほど効果が上がらなかった。
なにか良いオプションを知っている方がいたら、ご一報ください。



exeが出力されて完成
出来上がったexeと[Mono-2.0.dll]を同じフォルダにいれれば、.Netのない環境でも実行できる。

配布時、exeのみとする方法もありますが
『mkbundle実行時に--staticフラグ付けて静的リンクしたら、お前のプログラムもLGPLだかんね』
http://www.mono-project.com/Mono:Runtime

とのことなので、おとなしく[Mono-2.0.dll]を外に出しておくのが無難だと思います。
「LGPL?上等!逆アセンブルなんて怖くねーぜッ!」って方はご自由に。



★★★★★実行がうまくできない時は★★★★★
=SharpDXでエラーが出る時は=
・最新版のDX入れてね☆(ゝω・)
http://www.microsoft.com/ja-jp/download/details.aspx?id=35


=実行しても、うんともすんとも言わないとき=
・同梱したdllのバージョンが、アセンブリ生成時と違ってたりする事があるのでリビルトを試す

・署名がついてるdllが混ざっていないか確認する

経験則で理解できてはいないけどmkbundle実行時、アセンブリの記述順で良くなったりダメになったりする事がある。
コンパイルは通ってexeは出来るけど、実行時うんともすんとも言わないパターンなので容赦ならない。


=署名関係について=
署名付きのdllしか提供されていない場合Mono付属の[sn.exe]で署名の上書きが出来るらしいが、権利的に怪しい。
出来る事ならソースコードをダウンロードして、自分で署名なしのバイナリを作るのが望ましい。

オープンソースじゃないのなら、作者に嘆願するか諦めるか個人の範囲で楽しみましょう。


=mkbundle実行時のメッセージについて=
temp.c: In function 'install_dll_config_files':
temp.c:81:2: warning: pointer targets in passing argument 2 of 'mono_register_co
nfig_for_assembly' differ in signedness [-Wpointer-sign]
In file included from temp.c:3:0:
C:/Mono/include/mono-2.0/mono/metadata/assembly.h:101:15: note: expected 'const
char *' but argument is of type 'const unsigned char *'

mkbundle実行時このメッセージが出力されていても、
致命的では無いようで大丈夫な時は大丈夫。
出ない時もある、よく解からん(´・ω・`)

内容は署名が違うとか言ってるっぽい。書き換えてやれば黙るのかは未確認。



◆mkbundleを自作する
・mkbundleのソースコード
https://github.com/mono/mono/tree/master/mcs/tools/mkbundle

自作したmkbundleを通して出来上がったexeを実行して
this application has requested the runtime to terminate it in an usual way
Please contact the application's support team for more information.
とかってエラーが出たら
MkBundleが実行時、Monoの環境ではなくMS.Net環境で実行されてる。


このエラーはMS謹製のmscorlib.dllを参照したためmono-2.0.dllの実行時に落ちてると思われる。
mkbundle時のログを見れば、MS製のmscorlib.dllを参照しているはず。

Mono用のコマンドプロンプトから"Mono"キーワードをアセンブリ名の前につけて
"Mono Mkbundle.exe[引数略]"とかってして実行してくりゃれ。


未確認な手法ですが、mscorlib.dll等の関連するファイルの署名をsn.exeで上書きすると
mscorlib.dllがMS版のままmkbundleが出来るかもしれない。

しかし、これは許諾以外の使用方法で、権利的に非常に怪しいと思うので、
個人の趣味の範囲でどうぞ。


=出来なかったこと=
mkbundleを単体で動作できるようにしたかったが、どうにも上手くいかなかった。
何か上手い事GUI化出来た方がいたらご一報ください。



◆おまけ
この手法でネイティブ化出来るのはMonoで実装されている機能だけなので、
全ての.Netアセンブリがネイティブ化出来るわけではない。
それを調べるためにMomaというフリーウェアがあるが、コレも割とめんどくさい。

そこで、下記の手法をつかってVS上からあぶり出すことが可能。
バージョンが違うので、dllは自力で入れ替える必要があるが、割と便利だったのでご紹介。
http://jpobst.blogspot.jp/2009/06/mono-in-visual-studio-2010.html


この方法で恐らくIronPythonから出力した.Netアセンブリもネイティブに動かせるんじゃないかと思う、という蛇足。蛇だけに。



◆最後に
この手法は、足がかりに過ぎず、ただ出来ているという状態を超えていません。
よりよい手法がありましたら、一つコメントをいただけると助かります。


この手法が、どなたかの助けとなれば幸いです。
このエントリーをはてなブックマークに追加
LINEで送る

FXAAをXNA4.0で使う

シェーダーコードをボチボチ最適化して、日本語コメントをつけたので公開。
HLSLのSM2.0に対応しているので、他にも使い道があるように思う。

FXAA.fx


使い方は


後はスプライトバッチで全画面描画してくりゃれ。
このエントリーをはてなブックマークに追加
LINEで送る

.NetのRandomクラスから出力される値のムラについて

さんざんゲームを作り、システムを作ってきたはずなのに、
こんな基本的な落とし穴にはまってしまったのでメモ。


Randomクラスなのに使い方次第では完全なRandomにならない

Seedを揃えればそうなるのは仕様だけれど、
意図せずにこんな事があるとは、恐ろしい・・・


まず、Randomをインスタンスする際コンストラクタ引数なしの場合
SeedがEnvironment.TickCountを使用する設定になっている。

よって、連続して複数のインスタンスをほぼ同時に行うと、 同じ結果しか吐き出さなくなる。
というのはここらへんを見ると解ると思う


しかしながら、上記の対応方法でも問題がある。
Seedの値がいくらか違っても、どういうわけか結果に顕著なムラがある。

元々ムラがあるのは内部的に採用しているアルゴリズム上、仕方ないのだが、
それ以上に結果のムラを強く感じる。

検証ソース


結果


このレベルのムラはゲームでは致命的にはならないが、
GAなどでは結果に大きく影響するため、意識的に避けたい。

そういうわけで、複数のインスタンスを作成するより、
ひとつのインスタンスを使いまわす方が結果が優秀であるため、
スタティックでグローバルなクラスを作って、 そこから取得するのが良いのではないかと思う。


以下適当ソース


取得方法


結果サンプル


なんか、ジェネリックとかで、他にもっとうまい方法で出来るような気がするんだけど、
とりあえずこんな感じで。

求ム、最適解。


2012/11/22 追記
RandomをRondomと何度も何度も連呼しているという、
ソーシャルなウィンドーが全開の、まっこと恥ずかしい誤記を指摘していただいた、
通りすがりのご親切な方、誠に誠にありがとうございました。
このエントリーをはてなブックマークに追加
LINEで送る

 | HOME | 

PAGE TOP ▲

Appendix

■春条

■春条

生息地は愛知
車と甘い物が好きな31歳
特技は無限昼寝

MONOからSharpDXを使う
変態的な手法で、
.NET Frameworkを使わずに
ゲーム作りやってます。

Search

Calender

« | 2012-11 | »
S M T W T F S
- - - - 1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 -

Twitter

Recent Entries

DTIブログポータルへ
このブログを通報
Report Abuse

利用規約