C# - 自己解凍書庫 - 定義ファイル追加
はじめに
C# - 自己解凍書庫 - 基本機能 の続編です。
テスト環境
ここに記載した情報/ソースコードは、Visual Studio Community 2022 を利用した下記プロジェクトで生成したモジュールを Windows 11 24H2 で動作確認しています。
- Windows Forms - .NET Framework 4.8
素材
Windows Forms アイコンとして下記を利用させて頂きました。
-
フリーの圧縮フォルダアイコン | ICOOON MONO
- rgb(255, 102, 0)
定義ファイル追加
「自己解凍書庫ランチャー」は、さまざな用途で自己解凍書庫を作成時に、再利用できることが望ましいので、汎用的な単機能とすべきです。
まず、「自己解凍書庫ランチャー」に対する個別の情報は、定義ファイルで設定可能とします。
次に、特殊な処理(簡易的なインストーラ機能)が必要な場合は、該当処理専用の実行可能形式ファイルを作成して、ZIPアーカイブ内に格納、解凍後、自動起動する形態とします。
このような動作を前提とした、定義ファイルを自己解凍書庫に追加します。
定義ファイル
定義ファイル設定項目として、考えられる項目を記載します。
- 初期表示ダイアログの「タイトル」「説明文」
- 初期表示ダイアログは利用しない(解凍後、実行可能形式ファイルを実行を前提)
- 解凍先フォルダの既定値
- 解凍後、自動起動する実行可能形式ファイル(解凍フォルダからの相対パス指定)
- 解凍後、解凍データを削除するか否か
本記事では、初期表示ダイアログの「タイトル」「説明文」を、XML で下記のように指定するケースをサンプルとします。
<?xml version="1.0" encoding="utf-8"?>
<Settings>
<Title>〇〇サンプルプロジェクト</Title>
<Message>〇〇サンプルプロジェクトを解凍します。
解凍フォルダを指定して、「解凍実行」ボタンを押してください。</Message>
</Settings>
自己解凍書庫の構造
「自己解凍書庫ランチャー」「動作定義ファイル」「ZIPアーカイブ」の3ファイルから自己解凍書庫を作成しますが、単純に3ファイルを結合すると「動作定義ファイル」の終端確認が困難です。
このため、「動作定義ファイル」のサイズを固定長ファイルに出力して、下記4ファイルを結合することにします。
- 「自己解凍書庫ランチャー」
- 「動作定義ファイル」のサイズ(本記事では、8バイト固定ファイルとします)
- 「動作定義ファイル」
- 「ZIPアーカイブ」
上記手順に基づいた Windows バッチファイルを作成して実行します。
> MakeJoinFile ZipMelt.exe hoge.xml hoge.zip hoge.exe
echo off
@rem 遅延関数のおまじない
setlocal enabledelayedexpansion
@rem 第二引数(設定ファイル)ファイルサイズを6桁0埋め値に整形
@rem echo (\r\n付与)で8バイトファイルを作成
for %%i in (%2) do set SIZE=%%~zi
set NUMBER=000000%SIZE%
set NUMBER=!NUMBER:~-6,6!
echo %NUMBER%>%2.txt
@rem ファイル結合
copy /b %1 + %2.txt + %2 + %3 %4
サンプルコード
スタートアップルーチン
スタートアップルーチンで下記情報を作成して、Form コンストラクタに情報をセットする形態とします。
- 「定義ファイル」 XML をワークファイルとして作成
- 「ZIPアーカイブ」をワークファイルとして作成
static void Main()
{
string myself = Assembly.GetExecutingAssembly().Location; // 自プログラム
string xmlFile = "Piyo.xml"; // ユニークなワークファイルとしてください
string zipFile = "Piyo.zip"; // ユニークなワークファイルとしてください
using(var fs = new FileStream(myself, FileMode.Open, FileAccess.Read))
{
// PEヘッダーからプログラムサイズ算出
long size = GetSizeFromPeHeader(fs);
if (size < 0)
{
// ERROR - TODO
}
// FileStream 位置をプログラム末尾に設定
fs.Seek(size, SeekOrigin.Begin);
// FileStream 現在位置から定義ファイルを抽出
long xmlsize = MyApp2XmlFile(fs, xmlFile);
if (xmlsize <= 0)
{
// ERROR - TODO
}
// FileStream 位置を定義ファイル末尾に設定
fs.Seek(size + xmlsize, SeekOrigin.Begin);
// FileStream 現在位置からファイル末尾までをZIPファイルに抽出
MyApp2ZipFile(fs, zipFile);
}
// メインフォーム起動
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1(xmlFile, zipFile));
// 後処理
if (File.Exists(xmlFile))
{
File.Delete(xmlFile);
}
if (File.Exists(zipFile))
{
File.Delete(zipFile);
}
}
// FileStream 現在位置から定義ファイルを抽出
private static int MyApp2XmlFile(FileStream fs, string xmlFile)
{
int tagsize = 8; // TODO
var tagbufs = new byte[tagsize];
int xmlsize = -1;
// 定義ファイルのサイズ取得
fs.Read(tagbufs, 0, tagsize);
int.TryParse(Encoding.UTF8.GetString(tagbufs), out xmlsize);
// 定義ファイル抽出
if (xmlsize > 0)
{
var xmlbufs = new byte[xmlsize];
using (var fsxml = new FileStream(xmlFile, FileMode.Create, FileAccess.Write))
{
fs.Read(xmlbufs, 0, xmlsize);
fsxml.Write(xmlbufs, 0, xmlsize);
}
// 先頭で取得したサイズ情報 8バイトを追加
xmlsize += tagsize;
}
return xmlsize;
}
メインフォーム
コンストラクタで、xmlFile、zipFile を内部変数に格納した後、xmlFile をロードして、ダイアログ表示内容を変更します。
private string MyXmlFile = null;
private string MyZipFile = null;
public Form1(string xmlFile, string zipFile)
{
InitializeComponent();
// 内部変数保持
this.MyXmlFile = xmlFile;
this.MyZipFile = zipFile;
// XML → オブジェクト
var obj = LoadFromFile<XmlSettings>(MyXmlFile);
// ダイアログ表示項目を XML 指定値で更新
this.Text = obj?.Title ?? this.Text;
lblMessage.Text = obj?.Message ?? lblMessage.Text;
}
// XML → オブジェクト
private T LoadFromFile<T>(string filePath) where T : class
{
T obj = null;
XmlSerializer serializer = new XmlSerializer(typeof(T));
// ファイル確認
if (File.Exists(filePath))
{
// デシリアライズ
using (var fs = new FileStream(filePath, FileMode.Open))
{
obj = serializer.Deserialize(fs) as T;
}
}
return obj;
}
[Serializable]
[XMLRoot Name="Settings"]
public class XmlSettings
{
[XmlElement]
public string Title { get; set; }
[XmlElement]
public string Message { get; set; }
}
次に「解凍実行」Click イベントハンドラで、ZIP解凍を行います。
private void btnMelt_Click(object sender, EventArgs e)
{
string targetFolder = txtTargetFolder.Text.Trim(); // ダイアログ指定値
// ZIP解凍
using (var arc = ZipFile.OpenRead(this.MyZipFile))
{
arc.ExtractToDirectory(targetFolder);
}
}
出典
本記事は、2025/02/10 Qiita 投稿記事の転載です。
Discussion