それでも僕はWineでMonoを動かしたかった
Wine 上で動く Windows アプリを制御するにはどうしよう?と考えた結果、制御する側も Wine 上で動かせば Windows 上と同じように操作できるじゃないか!と思いやっていった記録。残念ながら、結局 Docker 上で動かすことはできなかったので、僕の意志を引き継いでくれる方、もしくは先行研究がありましたら教えてください。
というわけで、 Docker で動かすのを断念するまでに得た知見をまとめておきたいと思います。
wine-mono?それともスタンドアロンの Mono?
wine-mono は Mono のコアライブラリ + .NET Framework になりすますための mscoree.dll という構成になっています。つまり .NET の exe ファイルを起動しようとすると Mono を使ってくれる、という構成になります。
それに対して、 mono-project.com からダウンロードできる Mono の Windows 版は mono.exe を使ってアセンブリを実行する形をとります。つまり Linux で使っている Mono とまったく同じです。
これらふたつは共存することができるので、間接的に .NET Framework を必要とするアプリには wine-mono でうまくやっておいてもらいつつ、 .NET アセンブリだと最初からわかっているものは mono.exe で起動するという使い方ができるでしょう。
さて、最初は wine-mono で全部なんとかしようと思っていたのですが、厄介な問題にぶち当たりました。 wine-mono はアセンブリの厳密名を検証するようですが、回避するために App.config に <bindingRedirect>
を書いても読み込んでくれないようです。というわけで、基本的には mono.exe を使うべきだということがわかりました。
ちゃんとログに config 読み込めてないの書いてあったわ… pic.twitter.com/c1Lbo6zRcc
— 天はツイの上で敵を作らず (@azyobuzin) 2017年8月9日
つまりですよ、 Mono が mscoree として振舞うから面倒なのであって、 Windows 版の Mono を普通にインストールすればいいのでは?
— 天はツイの上で敵を作らず (@azyobuzin) 2017年8月9日
Mono を動かすために Winetricks でうまいことやる
Mono 5.2.0 を msiexec でインストールしたところ、問題なくインストールできましたが、大きいプログラムを動かそうとすると api-ms-win-crt-math-l1-1-0.dll
の未実装関数を踏み抜いてしまうようなので対策をします。
"Unhandled exception: unimplemented function api-ms-win-crt-math-l1-1-0.dll._Cbuild called in 32-bit code (0x7b43c69c)." これもうどうしようもなくないか
— 天はツイの上で敵を作らず (@azyobuzin) 2017年8月15日
Winetricks で Visual C++ 2015 ランタイムをインストールすると、この DLL もうまくオーバーライドされるようなので、それを利用していきます。 Ubuntu 17.04 の winetricks パッケージだと古いのでうまくやってくれないので GitHub からダウンロードしてください。で、
winetricks vcrun2015
でインストールできます。なお、これを実行すると OS バージョンが XP に設定されるので、この後に Mono をインストールするなら
winetricks win7
とかしてバージョンを上げておいてください。
あとユーザーが root の状態で Wine を初期化してしまうと winetricks vcrun2015
を実行したときに
wine cmd.exe /c echo '%ProgramFiles%' returned unexpanded string '%ProgramFiles%' ... this can be caused by a corrupt wineprefix, by an old wine, or by not owning /home/wine/.wine
のようなエラーが出ます。環境変数がおかしくなっているので root 以外で初期化してください(root 以外でやっても初回起動で msiexec すると同じ現象に出会ったので発生条件が分からない)。
DateTime.Now が InvalidTimeZoneException をスローする
まさか https://t.co/hF56Z3E7ZD が例外吐くとは思わなかったよね
— 天はツイの上で敵を作らず (@azyobuzin) 2017年8月15日
これが空だからタイムゾーンのルックアップに失敗したっぽい pic.twitter.com/GF8lRHSyRT
— 天はツイの上で敵を作らず (@azyobuzin) 2017年8月15日
wine reg add 'HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\TimeZoneInformation' /v TimeZoneKeyName /t REG_SZ /d 'Tokyo Standard Time' /f
みたいな感じで書き込んでおけばOKです。タイムゾーン名は HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones
下のキー名を指定します。
【未解決】DllImport の dllName に「.dll」を含むと死ぬ
また特殊環境例外を引き当ててしまった
— 天はツイの上で敵を作らず (@azyobuzin) 2017年8月16日
これはデスクトップ版 Ubuntu では発生せず、 Docker 上の Ubuntu 17.04 でのみ発生しています。
Console.CancelKeyPress
にイベントハンドラを追加したところ次のような例外が発生しました。
System.TypeInitializationException: The type initializer for 'System.ConsoleDriver' threw an exception. ---> System.EntryPointNotFoundException: GetStdHandle at (wrapper managed-to-native) System.WindowsConsoleDriver:GetStdHandle (System.Handles) at System.WindowsConsoleDriver..ctor () [0x00006] in <2b8d44e3c8984c9dab77e08aed77c96b>:0 at System.ConsoleDriver.CreateWindowsConsoleDriver () [0x00000] in <2b8d44e3c8984c9dab77e08aed77c96b>:0 at System.ConsoleDriver..cctor () [0x00019] in <2b8d44e3c8984c9dab77e08aed77c96b>:0 --- End of inner exception stack trace --- at System.Console.add_CancelKeyPress (System.ConsoleCancelEventHandler value) [0x00000] in <2b8d44e3c8984c9dab77e08aed77c96b>:0
で、実験してみたのですが
[DllImport("kernel32")] static extern int GetLastError();
だとうまく実行でき、
[DllImport("kernel32.dll")] static extern int GetLastError();
だと例外が発生するようです。
この Dockerfile で環境を作って extern メソッドを呼び出したり Console.CancelKeyPress
にイベントハンドラを追加するプログラムを書いて実行すれば再現すると思います。
この問題が突破できないので、安心して C# で Wine 向けコードを書くことができません。デスクトップ版で動かしている環境と何が違うのか全然わからん。
もう疲れたんだけど
— 天はツイの上で敵を作らず (@azyobuzin) 2017年8月16日
8/18 朝追記
C:\Program Files\Mono\etc\mono\config の削除で凌げるようですが、根本的に動く環境と動かない環境の違いがわかっていません。