Symfoware

Symfowareについての考察blog

C# exeファイルに後から設定値を埋め込む手法の検討

プログラムで使用する何かしらの設定値を保存したい場合、
レジストリやiniファイルを使用することになると思います。

これだと、exeファイルだけコピーして別の端末で起動した時、設定値が失われてしまう。
なんとかコンパイル済のexeファイルに対して、後付で値を埋め込めないか試してみます。


最初のサンプルプログラム



文字列を出力する簡単なプログラムを作成します。

・test.cs


  1. using System;
  2. using System.IO;
  3. public class Test {
  4.         
  5.     [STAThread]
  6.     public static void Main(string[] args) {
  7.         Console.WriteLine("test");
  8.     }
  9. }




こんなバッチファイルを作成してコンパイルしました。

・build.bat


@echo off
set csc="C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe"
set opt=/nologo

%csc% %opt% /out:test.exe test.cs




出力されたtest.exeをメモ帳で開いてみると、xmlな文字列が確認できます。
735_01.png


この領域を使用して、定数値を保存できるのでは?






manifestの使用



コンパイルオプションに何も指定せずにビルドすると、
デフォルトのmanifestファイルが埋め込まれるようです。

/win32manifest (C# Compiler Options)

こちらを参考に、自分で作成したマニフェストファイルを指定してコンパイルしてみます。

・test.manifest


  1. <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
  2. <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  3. <assemblyIdentity version="1.0.0.0" name="Test.app"/>
  4. <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
  5.     <security>
  6.      <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
  7.         <requestedExecutionLevel level="asInvoker" uiAccess="false"/>
  8.      </requestedPrivileges>
  9.     </security>
  10. </trustInfo>
  11. </assembly>





ビルド用のバッチファイルはこうなりました。

・build.bat


@echo off
set csc="C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe"
set opt=/nologo /win32manifest:test.manifest

%csc% %opt% /out:test.exe test.cs




出力されたexeを確認すると、指定したmanifestが埋め込まれたようです。

735_02.png





manifestにタグの追加



manifestに値を保存しておくためのタグを追加してみます。
今回はdescriptionというタグを追加しました。

・test.manifest


  1. <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
  2. <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  3. <assemblyIdentity version="1.0.0.0" name="Test.app"/>
  4. <description>                             </description>
  5. <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
  6.     <security>
  7.      <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
  8.         <requestedExecutionLevel level="asInvoker" uiAccess="false"/>
  9.      </requestedPrivileges>
  10.     </security>
  11. </trustInfo>
  12. </assembly>




ポイントは、タグの内容に半角スペースをある程度含めておくことです。
ビルド後、exeのバイト数が変わると実行できなくなってしまうため、ある程度余白を設けておきます。

735_03.png


この領域に値を埋め込み、プログラムから読みだして使えば良さそうです。





値の埋め込みと読み取り



exeファイルを読み込み、descriptionタグの間に文字列を埋め込むプログラム。

・setting.cs


  1. using System;
  2. using System.IO;
  3. using System.Text;
  4. public class Test {
  5.         
  6.     [STAThread]
  7.     public static void Main(string[] args) {
  8.         
  9.         string param = args[0];
  10.         
  11.         // 元となるexeファイルの読み込み
  12.         byte[] binary = File.ReadAllBytes("test.exe");
  13.         
  14.         // descriptionタグの位置をバイナリデータから検索
  15.         int first = IndexOf("<description>", binary);
  16.         
  17.         if (first == -1) {
  18.             Console.WriteLine("descriptionタグが見つかりません。");
  19.             return;
  20.         }
  21.         
  22.         // <description>の文字数分インデックスをずらす
  23.         int binaryIndex = first + 13;
  24.         
  25.         // パラメーターの埋め込み
  26.         byte[] paramBytes = Encoding.ASCII.GetBytes(param);
  27.         for (int i = 0; i < paramBytes.Length; i++, binaryIndex++) {
  28.             binary[binaryIndex] = paramBytes[i];
  29.         }
  30.         
  31.         // パラメーターを埋め込んだexeファイル出力
  32.         File.WriteAllBytes("test_param.exe", binary);
  33.         
  34.     }
  35.     
  36.     private static int IndexOf(string target, byte[] binary) {
  37.         byte[] targetBytes = Encoding.ASCII.GetBytes(target);
  38.         
  39.         for (int i = 0; i < binary.Length - targetBytes.Length; i++) {
  40.             
  41.             bool allMatch = true;
  42.             for (int j = 0; j < targetBytes.Length; j++) {
  43.                 if (binary[i + j] != targetBytes[j]) {
  44.                     allMatch = false;
  45.                     break;
  46.                 }
  47.             }
  48.             
  49.             if (allMatch) {
  50.                 return i;
  51.             }
  52.             
  53.         }
  54.         
  55.         return -1;
  56.     }
  57. }




引数をdescriptionタグにtest.exeに埋め込み、test_param.exeとして出力します。


適当な値を埋め込んでみます。


> setting.exe test_value




出力されたtest_param.exeを見てみると、ちゃんと値が埋め込まれました。

735_04.png


test.exeを修正。埋め込まれた値を読み込めるようにします。


・test.cs


  1. using System;
  2. using System.IO;
  3. using System.Text.RegularExpressions;
  4. using System.Reflection;
  5. public class Test {
  6.         
  7.     [STAThread]
  8.     public static void Main(string[] args) {
  9.         
  10.         // 自分自身をファイルとして読み込み
  11.         var assembly = Assembly.GetExecutingAssembly();
  12.         string s;
  13.         using(StreamReader sr = new StreamReader(assembly.Location)) {
  14.             s = sr.ReadToEnd();
  15.         }
  16.         
  17.         // descriptionタグの内容を取得
  18.         Regex r = new Regex(@"<description>(.*?)</description>");
  19.         MatchCollection mc = r.Matches(s);
  20.         
  21.         string param = "";
  22.         foreach (Match m in mc) {
  23.             param = m.Groups[1].Value.Trim();
  24.         }
  25.         
  26.         if (string.IsNullOrEmpty(param)) {
  27.             Console.WriteLine("not found");
  28.             return;
  29.         }
  30.         
  31.         // 取得した値を出力
  32.         Console.WriteLine(param);
  33.         
  34.     }
  35. }





狙い通りの実行結果です。

735_05.png

関連記事

テーマ:プログラミング - ジャンル:コンピュータ

  1. 2017/03/25(土) 17:55:36|
  2. 備忘録
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集
<<Riot 最小構成のサンプルプログラム | ホーム | PythonからMariaDB(MySQL)に接続する(mysql-connector-python)>>

コメント

コメントの投稿


管理者にだけ表示を許可する

トラックバック

トラックバック URL
http://symfoware.blog68.fc2.com/tb.php/1968-24fa91f9
この記事にトラックバックする(FC2ブログユーザー)