アジョブジ星通信

日常系バンザイ。

それでも僕はWineでMonoを動かしたかった

Wine 上で動く Windows アプリを制御するにはどうしよう?と考えた結果、制御する側も Wine 上で動かせば Windows 上と同じように操作できるじゃないか!と思いやっていった記録。残念ながら、結局 Docker 上で動かすことはできなかったので、僕の意志を引き継いでくれる方、もしくは先行研究がありましたら教えてください。

というわけで、 Docker で動かすのを断念するまでに得た知見をまとめておきたいと思います。

環境

  • Ubuntu 17.04
  • Wine 2.14 win32 prefix
  • Mono 5.2.0 x86

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 を使うべきだということがわかりました。

Mono を動かすために Winetricks でうまいことやる

Mono 5.2.0 を msiexec でインストールしたところ、問題なくインストールできましたが、大きいプログラムを動かそうとすると api-ms-win-crt-math-l1-1-0.dll の未実装関数を踏み抜いてしまうようなので対策をします。

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 をスローする

レジストリタイムゾーン情報が書かれていないので死にます。

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」を含むと死ぬ

これはデスクトップ版 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 向けコードを書くことができません。デスクトップ版で動かしている環境と何が違うのか全然わからん。

8/18 朝追記

C:\Program Files\Mono\etc\mono\config の削除で凌げるようですが、根本的に動く環境と動かない環境の違いがわかっていません。