「未分類」 カテゴリーの記事です。
nuget-icon

.NET FrameworkでDLLファイル(国際化対応リソースを含む)をexeにマージする


前のバージョンまで、「アタッシェケース」ではインストールを行うと、以下のプログラムフォルダーには、本体と並んで、DLLファイルがいくつか並んでいました。

AttacheCase.exe
AtcSetup.exe
Microsoft.WindowsAPICodePack.dll
Microsoft.WindowsAPICodePack.Shell.dll
ja-JP\AttacheCase.resources.dll

このファイル構成は、あくまで開発者側の都合であって、ユーザーにとっては良く分からないでしょう。

実行ファイルを別の場所へ移動させて使いたいとき、このDLLファイルも同時に移動させなくてはいけないのか?と迷ったり、煩わしいと思ったりするかもしれません。

実際、付属DLLファイルによっては、実行ファイルのあるフォルダーに存在しないと、正常に動かないものもあり、ユーザーにとっては、ツールのポータビリティが低く、やや使いづらいのではないかと考えました。

一つの実行ファイルに、すべての付属ファイルをマージする

というわけで、章題。これを目標にしてみました。そこで考えられる方策をいつくかご紹介したいと思います。

ILMerge(ILRepack)

もっとも手軽で定番なのは、「ILMerge」でしょうか。

Microsoft謹製のツールです。リンク先にあるツールをダウンロードしてインストールすると、使えるようになります。いろいろなオプションがあって、それに従って設定すれば、指定の実行ファイルにDLLファイルをマージできるようになります。

コマンドの例としては、以下のような感じです。

ところが、これですと、言語ファイルである、ja-JP\AttacheCase.resources.dllが残ってしまいます。どうやら、国際化対応リソースのDLLは「サテライトアセンブリ」といって、マージのときに除外されるみたいです。

ちなみに「ILMerge」は、Microsoftのプロプライエタリなツールですが、これをオープンソースにして、機能を拡張したものも存在します。「ILRepack」です。

これも、ILMergeと基本的に使い方は同じです。たとえば、以下のようにします。

ただ、これも、肝心の言語ファイルがマージされません。

NuGet

では、より簡単で、すべてのDLLファイルを実行ファイル(exe)にマージできないか調べて見ると、Visual Studio 2012以降では標準で付属しているツールの「NuGet」で、あるパッケージ群をインストールすれば可能になるようです。

まず、NuGetとは何か? マイクロソフト公式のウェブサイトにも説明はありますが、以下のウィキペディアの説明の方が簡潔ですので、そちらを参照なさってください。

NuGet
https://ja.m.wikipedia.org/wiki/NuGet

NuGetは、基本的にVS全体にインストールするというよりも、プロジェクト毎にインストールするようになっています。ですので、以下の画面では、「アタッシェケース」のソリューション(その中に各プロジェクト)が開いている状態で、NuGetパッケージ管理画面を開こうとしています。

 

Fody

次に「参照」から「fody」を検索して、Fodyを見つけたら、NuGetからパッケージを必要とするプロジェクトにチェックを入れ、そこへ「インストール」します。

Fodyとは以下のサイトにも説明がある通り、「Extensible tool for weaving .net assemblies(.NET Frameworkにアセンブリを組み込むための拡張ツール)」です。

Fody
https://github.com/Fody/Fody/

ただ、これだけではまだ不完全です。さらに、次にあるFodyのアドインをインストールする必要があります。

Fody.Costura

再びNuGetで「Fody Costura」を検索してインストールします。

Costuraとは、プロジェクトで使うリソースをアプリケーション本体に埋め込むためのツールです。

Costura is an add-in for Fody
https://github.com/Fody/Costura/

しかし、インストールしただけでは正常に動作しません。その後に、設定ファイルを所定の場所に置かなくてはなりません。ファイル名は以下の通り、決まっています。

FodyWeavers.xml

というファイルを作り、プロジェクトファイルがあるディレクトリに配置しなくてはなりません。

そのXMLファイルの中身ですが、最低限の設定であれば、以下の内容だけでO.K.です。ちなみに、公式GitHubのウェブサイトを見れば、埋め込みを除外するDLLを指定することができたり、様々なオプションが用意されているので、カスタマイズする場合はそちらを参考になさってください。なお、XMLファイルの文字エンコーディングはUTF-8なのでご注意を。

これにより、
Microsoft.WindowsAPICodePack.dll
Microsoft.WindowsAPICodePack.Shell.dll
の二つが「AttacheCase.exe」埋め込まれて、以下のようにファイルが出力されるかと思います。

 

Resource.Embedder

しかし、これでも、まだ国際化対応リソース(言語ファイル)DLL、
ja-JP\AttacheCase.resources.dll が統合されておりません。

そこで、もう一つNuGetパッケージをインストールします。「Resource.Embedder」です。

Resource.Embedder
https://github.com/MarcStan/Resource.Embedder

インストールは、以下の通りです。

これにより、ビルドを行うと、すべてのDLLリソースが「AttacheCase.exe」に埋め込まれて、以下のように単一で出力されます。

 

ただし、.NET Framework 4.0 だと古いバージョンを使う

Fody パッケージ自体は、.NET Frameworkのバージョン依存関係はありませんが、Fodyのプラグインである Costura の最新版の方には、

.NET Framework 4.6
Fody (>= 3.2.6)

といった依存関係があります。

ですので、アタッシェケースは、一応WindowsXP上でも動作するように、.NET Frameworkは、“4.0” でビルドするという縛りを設けているため

Fody ver.2.5.0
Costura.Fody ver.1.6.2

と、あえて古いバージョンのパッケージをそれぞれ使っています。

以上です。

iTunes Connect icon

[Electron][MacOS]Your application still accesses the following location(s):でリジェクト



追記:2017/06/18に大幅修正を加えました。

Electronで絶賛、開発中の「OutlineText」ですが、バージョンアップ版を審査に提出したところ、タイトルのようなメッセージとともにリジェクトが。

正確には、

2. 4 Performance: Hardware Compatibility (macOS)
Guideline 2.4.5(i) – Performance

Your application still accesses the following location(s):

‘/Applications/OutlineText.app/Contents/Resources/app/locales/en.json.tmp’

The majority of developers encountering this issue are opening files in Read/Write mode instead of Read-Only mode, in which case it should be changed to Read-Only.

これを初めてもらったとき、「そうか、たしかに自分で自分のappにアクセスしてしまったっらダメだな」と思って、OutlineText.app内のディレクトリにはロックをかけるような仕組みを作り直して、審査に再提出。

ところが、またリジェクト。理由は、判で押したように上記のメッセージで返ってきました。いろいろ試行錯誤した結果、4回連続でリジェクトを頂戴するという羽目に(審査チームももう少しヒントをくれても良いのに・・・)。

そこまでやって、ふと思い当たったのは、ひょっとすると、Entitlementsの指定が誤っているのではないか?ということで、先の記事中では、Electron でビルドするときは、シェルスクリプトでやっていて、以下のように設定しておりました。

Electornは、単体アプリではなくヘルパーアプリも一緒に動くので、codesignは本体同様にすべてに行ってあげないといけません。でないと、審査以前にアプリをアップロードするときに、アップローダーに怒られます。

この辺りは、「Electron アプリを Mac App Store に登録する手順」が参考になるでしょう。

ここで、問題になっていたのは、parent.plist 9行目のEntitlementsに、com.apple.security.files.user-selected.read-write が指定されていること。

このせいでいくらい対策を講じても、アクセスは可能になっていたというわけかと納得。そこで、以下のようにcom.apple.security.files.user-selected.read-onlyだけの記述にして審査へ再提出したら、ようやく審査に通りました。

ところが、いったんは審査を通ったものの、今度は保存ダイアログボックスが出ないというバグが発生・・・(これは審査チームも見逃したようです)。

考えてもみれば、そりゃそうですよね。read-onlyなのですから、保存ダイアログが出ないのは当たり前です。

で、結局は以下のように、指定を元に戻して再提出しました。具体的には、Entitlementsの指定で、read-onlyを、read-writeに書き換えています。

これでまた一から振り出しに戻り、先のテンプレ文とリジェクトをいただくことに。。。

そこで業を煮やした僕は、審査チームに「アピール」を送信してみることにしました。

Writes in the location are restricted and be open for read-only already. Please tell me the detailed occurrence procedure.

画像も添付しました。

read-only

ちゃんと「読み取り専用」として開いているぞ、と。

すると、二日後に以下のようなメッセージが届きました。

Hello,

Thank you for your inquiry. To clarify, the app is opening files in Read/Write mode instead of Read-Only mode, in which case it should be changed to Read-Only.
The file being accessed is:
‘/Applications/OutlineText.app/Contents/Resources/app/locales/en.json.tmp’

これを読んで、ふと、思い当たる節がありました。ひょっとして、ファイルの読み書きのところで、意図せずread-writeで読み込んでいる箇所があるのではないか?と。

実際、ありました。

保存するとき(ファイルに書き込むとき)、前に保存したディレクトリが存在しているかどうかチェックをして、なければデフォルトは「書類」フォルダを指定するというコードにしていました。参考にしたのは、「Check synchronously if file/directory exists in Node.js – Stack Overflow」です。

ひょっとすると、fs.lstatSyncが、read-writeでアクセスしに行っているのではないか?と思い(Node.jsの公式ページでは確認できなかったのですが)、Stack Overflowのページをもう一度確認してみると、回答が大幅に書き換えられていて、以下が推奨となっていました。

そこで、この部分を上記で書き直して、再度Appleに提出。程なくして、「Ready for sale」の返答があり、ようやくバージョンアップ版がApp Storeに並ぶことになりました。

やれやれ・・・一件落着です。

ちなみに、Appleの審査チームのアピールですが、質問形式で送ると、即座にテンプレで「サポートか、フォーラムに投げてね」的な回答が返ってきますが、「再現の手順」を教えてくれ、といった具体的な質問に対しては、ヒントになるくらいの回答が得られました。アピールの仕方にも、少し工夫が必要ですね。

 

s