0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

サテライトアセンブリ(en, zh 等)を単一の exe に埋め込みの例

Posted at

まだ未確認。。。

  1. 通常どおり .resx(Resources.resx:デフォルト日本語、Resources.en.resx、Resources.zh.resx)を用意してビルドすると、出力に en\YourAssemblyName.resources.dll や zh... のサテライト DLL が作られる。
    2. そのサテライト DLL をメイン EXE プロジェクトに Embedded Resource として追加する(Build Action = Embedded Resource)。
    3. アプリ起動時に CultureInfo.CurrentUICulture(※Windows の表示言語)を調べ、対応する埋め込みリソース(サテライト DLL)を Assembly.Load(byte[]) で読み込む。
    4. ResourceManager は通常どおり CurrentUICulture を参照するので、サテライトを読み込んでおけば Properties.Resources などからローカライズ文字列が取得できる。
    5. (補助)AppDomain.CurrentDomain.AssemblyResolve を使って、必要に応じて自動ロードする仕組みにしておくと安全。

以下、実際に使えるサンプルを示します(最小の手順+コード)。

Visual Studio / MSBuild 上の準備手順(簡潔)
1. メインプロジェクト(例:MyApp)に Resources.resx(デフォルト)を作る。Properties/Resources.resx に Hello = こんにちは(日本語) 等を入れる。
2. 同じく Properties に Resources.en.resx(Hello = Hello (English))、Resources.zh.resx(Hello = 你好(中文))を作る。
• ビルドすると bin\Debug\netX\en\MyApp.resources.dll 等が生成されます(これがサテライト)。
3. ビルド後に生成されるサテライト DLL(en\MyApp.resources.dll, zh\MyApp.resources.dll)をメインプロジェクトに「既存項目として追加」し、プロパティ → Build Action = Embedded Resource に変更する。
• (自動化したい場合は PostBuild イベント / MSBuild タスクでコピー→埋め込みする方法もありますが、まずは手動で確認することを推奨します。)

Program.cs
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Resources;
using System.Threading;

namespace MyApp
{
    class Program
    {
        // メインアセンブリのデフォルト名前空間(通常はプロジェクト名)
        const string DefaultNamespace = "MyApp";

        static void Main()
        {
            // 1) OS の表示言語に合わせる(Windows の表示言語が CurrentUICulture に反映されます)
            CultureInfo osUi = CultureInfo.CurrentUICulture;
            Console.WriteLine($"Detected CurrentUICulture: {osUi.Name} ({osUi.DisplayName})");

            // 2) 該当カルチャのサテライトを埋め込みリソースから読み込む
            //    見つからなければ親カルチャや neutral にフォールバックする
            LoadEmbeddedSatelliteForCulture(osUi);

            // 3) リソース使用例(Properties.Resources または ResourceManager で取得)
            //    ここでは ResourceManager を直接作って確認する例
            var rm = new ResourceManager("MyApp.Properties.Resources", Assembly.GetExecutingAssembly());
            // ResourceManager は CurrentUICulture を参照する
            Console.WriteLine("ResourceManager.CurrentUICulture: " + rm.GetString("Hello", CultureInfo.CurrentUICulture));

            Console.WriteLine("明示的に en を指定した結果: " + rm.GetString("Hello", new CultureInfo("en")));
            Console.WriteLine("明示的に zh を指定した結果: " + rm.GetString("Hello", new CultureInfo("zh")));

            Console.WriteLine("Press any key...");
            Console.ReadKey();
        }

        static void LoadEmbeddedSatelliteForCulture(CultureInfo startCulture)
        {
            // Prevent multiple registration
            AppDomain.CurrentDomain.AssemblyResolve -= CurrentDomain_AssemblyResolve;
            AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;

            // Try each culture up the parent chain
            var culture = startCulture;
            while (culture != CultureInfo.InvariantCulture)
            {
                if (TryLoadEmbeddedSatellite(culture))
                {
                    Console.WriteLine($"Loaded satellite for {culture.Name}");
                    return;
                }
                culture = culture.Parent;
            }

            // 最後に neutral を試す(存在すれば)
            if (TryLoadEmbeddedSatellite(CultureInfo.InvariantCulture))
            {
                Console.WriteLine("Loaded neutral satellite");
                return;
            }

            Console.WriteLine("No embedded satellite found for requested cultures. Using fallback (main assembly resources).");
        }

        // AssemblyResolve ハンドラ: 必要に応じて埋め込みリソースを返す
        private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            // args.Name には要求されたアセンブリ名が入る。サテライトは通常 "<AssemblyName>.resources"。
            // ここでは埋め込みリソース群を列挙し、該当するものがあればロードする。
            var requesting = Assembly.GetExecutingAssembly();
            var manifestNames = requesting.GetManifestResourceNames(); // debug 用に列挙可能

            // サテライト(resources.dll)を探す(例: MyApp.Sat.en.MyApp.resources.dll 等の名前で埋め込んでいる場合)
            // 当サンプルではリソース名に ".resources.dll" を含む埋め込みを探す
            var candidate = manifestNames.FirstOrDefault(n => n.EndsWith(".resources.dll", StringComparison.OrdinalIgnoreCase));
            if (candidate == null) return null;

            using (var s = requesting.GetManifestResourceStream(candidate))
            {
                if (s == null) return null;
                byte[] data;
                using (var ms = new MemoryStream())
                {
                    s.CopyTo(ms);
                    data = ms.ToArray();
                }
                try
                {
                    return Assembly.Load(data);
                }
                catch
                {
                    return null;
                }
            }
        }

        // 指定カルチャの埋め込みサテライトを探してロードする(成功すれば true)
        static bool TryLoadEmbeddedSatellite(CultureInfo culture)
        {
            // 期待する埋め込みリソース名の例(プロジェクトによって異なるため、実際の Manifest 名を列挙して確認すること)
            // ここでは "MyApp.Satellites.{culture}.{AssemblyName}.resources.dll" のように埋め込む想定
            var asm = Assembly.GetExecutingAssembly();
            var names = asm.GetManifestResourceNames();
            Console.WriteLine("Manifest resources:");
            foreach (var n in names) Console.WriteLine("  " + n);

            // 候補名パターン(簡易)
            string cultureSuffix = string.IsNullOrEmpty(culture.Name) ? "neutral" : culture.Name;
            // ここではユーザーが埋め込むリソース名のルールに合わせてチェックする:
            //  - 例1: MyApp.en.MyApp.resources.dll
            //  - 例2: MyApp.Satellites.en.MyApp.resources.dll
            var candidates = new[]
            {
                $"{DefaultNamespace}.{culture.Name}.{asm.GetName().Name}.resources.dll",
                $"{DefaultNamespace}.Satellites.{culture.Name}.{asm.GetName().Name}.resources.dll",
                $"{asm.GetName().Name}.resources.dll", // 最も単純な例
                $"{DefaultNamespace}.{asm.GetName().Name}.resources.dll"
            };

            // 実際に埋め込まれている manifest 名と突き合わせて見つける
            string found = names.FirstOrDefault(n => candidates.Contains(n, StringComparer.OrdinalIgnoreCase) ||
                                                    n.EndsWith($".{culture.Name}.{asm.GetName().Name}.resources.dll", StringComparison.OrdinalIgnoreCase) ||
                                                    n.EndsWith($".{culture.Name}.resources.dll", StringComparison.OrdinalIgnoreCase));

            if (found == null)
            {
                // 親文化や neutral を試す場合はここから呼び出し元でループする
                return false;
            }

            using (var s = asm.GetManifestResourceStream(found))
            {
                if (s == null) return false;
                byte[] bytes;
                using (var ms = new MemoryStream())
                {
                    s.CopyTo(ms);
                    bytes = ms.ToArray();
                }

                try
                {
                    Assembly.Load(bytes);
                    return true;
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"Failed to load embedded satellite: {ex}");
                    return false;
                }
            }
        }
    }
}

実際に埋め込むときのリソース名の確認方法

埋め込んだあと、Assembly.GetExecutingAssembly().GetManifestResourceNames() を使うと、実際のリソース名(大文字小文字含む)が列挙されます。上のサンプルでも起動時に表示するようにしているので、どの名前で埋め込まれているか必ず確認してください。名前が想定と違うことが最もよくあるトラブル原因です。

注意点・補足
• サテライトアセンブリは resources-only な DLL(例:MyApp.resources.dll)なので Assembly.Load(byte[]) で読み込めます。
• ResourceManager は CurrentUICulture を参照するため、サテライトをロードしておけば自動的に正しい言語の文字列を返します。
• 埋め込みリソース名の命名ルールはプロジェクトのデフォルト名前空間や追加方法に依存するので、GetManifestResourceNames() で実際名を必ずチェックしてください。
• 自動化:CI/ビルドパイプラインでサテライト生成→主プロジェクトへ埋め込み(MSBuild の に出力パスを指定)する方法も取れます。まずは手動で動作確認するとトラブルが少ないです。
• もしサテライトを物理ファイルに展開して CultureInfo 毎にフォルダ配置(en, zh\)したくない・単一 exe にしたい場合にこの「埋め込み+ランタイムロード」方式が有効です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?