SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

動的PInvokeによる統合アーカイバ仕様DLLを使用した書庫の展開と作成

統合アーカイバDLLのAPIをマネージコードから呼び出す


「Unlha32.dll」を使用した書庫の作成

 次は、圧縮の方法です。ここでは、新規に書庫ファイルを作成するものとします。

手順

  1. 作成する書庫と同じ名前のファイルが存在しないことを確認する。
  2. 同じ名前の書庫が既に存在していると、その書庫にファイルを追加してしまいます。
  1. 「Unlha32.dll」の存在をWin32 APIのSearchPath関数で確認する。
  1. 「Unlha32.dll」が実際にインストールされているか確認するために、UnlhaGetVersion関数を呼び出す。
  1. UnlhaGetRunning関数により、「Unlha32.dll」が実行されていないことを確認する。
  1. Unlha関数に渡すコマンドラインを作成する。
  2. Unlha関数により書庫を作成する場合は、コマンドとして「a」または「u」を使います。両者の違いは、書庫にファイルを追加する時に強制的に上書きするか、更新日時を確認するかです。よって、新規作成する場合は、どちらでもかまいません。下のコードではそれ以外に、パス情報を記録し、フォルダも書庫に格納するための「-d1」スイッチと、読み込み許可で開けなかったファイルをエラーとして処理する「-jso1」スイッチを付けています(コマンドやスイッチについて詳しくは、DLLのドキュメントをご覧ください)。
    さらに下のコードでは、基準ディレクトリを指定することにより、圧縮するファイルは基準ディレクトリからの相対パスとして指定しています。基準ディレクトリを指定せずに絶対パスを指定すると、ファイルは絶対パスで格納されます。例えば、「C:\Documents and Settings\user\デスクトップ\readme.txt」というファイルを絶対パスで圧縮すると、「/Documents and Settings/user/デスクトップ/readme.txt」というパスで格納されます。絶対パスで格納すると、展開時に問題が起こる可能性があるので、注意してください(「Unlha32.dll」では通常、どのように展開するか確認するダイアログが表示されます)。
    圧縮するファイルを指定する方法としては、レスポンスファイルを使った方法もありますが、これについては、後述します。
  1. Unlha関数を呼び出して書庫を作成する。
  1. 正しく書庫が作成されたか確認する。

コード

 以下に「Unlha32.dll」を使って書庫を作成するコードを紹介します。このコードも簡略化されていますので、サンプルの「LhaCompressor_Console」プロジェクトも併せてご覧ください。

C#の場合
using System;
using System.Text;
using System.IO;
using System.Runtime.InteropServices;

class Program
{
    //DLL の版の取得
    [DllImport("unlha32",
        EntryPoint = "UnlhaGetVersion",
        ExactSpelling = true)]
    private extern static ushort UnlhaGetVersion();
    //DLL の実行状況の取得
    [DllImport("unlha32",
        EntryPoint = "UnlhaGetRunning",
        ExactSpelling = true)]
    private extern static bool UnlhaGetRunning();
    //書庫操作一般
    [DllImport("unlha32",
        EntryPoint = "Unlha",
        CharSet = CharSet.Ansi,
        ExactSpelling = true)]
    private extern static int Unlha(
        IntPtr _hwnd,
        [MarshalAs(UnmanagedType.LPStr)]string _szCmdLine,
        [MarshalAs(UnmanagedType.LPStr)]StringBuilder _szOutput,
        uint _dwSize);

    //ファイルを検索
    [DllImport("kernel32",
        EntryPoint = "SearchPath",
        SetLastError = true,
        CharSet = CharSet.Auto,
        ExactSpelling = false)]
    private static extern uint SearchPath(
        [MarshalAs(UnmanagedType.LPTStr)]string lpPath,
        [MarshalAs(UnmanagedType.LPTStr)]string lpFileName,
        [MarshalAs(UnmanagedType.LPTStr)]string lpExtension,
        uint nBufferLength,
        [MarshalAs(UnmanagedType.LPTStr)]StringBuilder lpBuffer,
        out IntPtr lpFilePart);

    /// <summary>
    /// UNLHA32.DLLで書庫を作成する
    /// </summary>
    /// <param name="compressFiles">圧縮するファイルのパス</param>
    /// <param name="createArchive">作成するファイル名</param>
    /// <param name="baseDir">基準ディレクトリ</param>
    public static void LhaCompressFiles(
        string[] compressFiles, string createArchive, string baseDir)
    {
        //作成する書庫と同じ名前のファイルが無いか確認
        if (File.Exists(createArchive))
            throw new ApplicationException(
                "ファイル" + createArchive + "が既に存在しています。");

        //DLLの存在を確認
        IntPtr pathPtr;
        if (SearchPath(null, "unlha32.dll", null, 0, null, out pathPtr) == 0)
            throw new ApplicationException(
                "UNLHA32.DLLが見つかりません。");

        //DLLのチェック
        try
        {
            ushort ver = UnlhaGetVersion();
            Console.WriteLine("UNLHA32.DLLのバージョン:{0:0.00}", ver / 100f);
        }
        catch
        {
            throw new ApplicationException(
                "UNLHA32.DLLがインストールされていません。");
        }

        //動作中かチェック
        if (UnlhaGetRunning())
            throw new ApplicationException(
                "UNLHA32.DLLが現在動作中です。");

        //基準ディレクトリを修正
        if (!string.IsNullOrEmpty(baseDir) && !baseDir.EndsWith("\\"))
            baseDir += "\\";

        //コマンドラインとする圧縮するファイル名のリストを取得
        string compressFilePaths = "";
        foreach (string f in compressFiles)
        {
            string filePath = f;
            //相対パスとする
            if (!string.IsNullOrEmpty(baseDir))
            {
                if (f.StartsWith(baseDir))
                {
                    filePath = f.Substring(baseDir.Length);
                }
                else
                {
                    throw new ApplicationException(
                       "指定されたファイル'" + f + "'は基準ディレクトリ'" +
                       baseDir + "'の下にありません。");
                }
            }

            if (File.Exists(f))
            {
                //ファイルのとき
                compressFilePaths += "\"" + filePath + "\" ";
            }
            else if (Directory.Exists(f))
            {
                //フォルダのときは、
                //すべてのファイルを表すワイルドカードを追加
                compressFilePaths += "\"" + Path.Combine(filePath, "*") + "\" ";
            }
            else
            {
                //ファイルが存在しないとき
                throw new ApplicationException(
                    "指定されたファイル'" + f + "'が存在しません。");
            }
        }
        compressFilePaths = compressFilePaths.Trim();
        //基準ディレクトリを付加する
        if (!string.IsNullOrEmpty(baseDir))
            compressFilePaths = "\"" + baseDir + "\" " + compressFilePaths;
            compressFilePaths = "\"" + baseDir + "\" " + compressFilePaths;

        //ファイル名を修正
        createArchive = "\"" + createArchive + "\"";

        StringBuilder output = new StringBuilder(1024);

        //圧縮する
        int ret = Unlha(IntPtr.Zero,
            string.Format("a -d1 -jso1 {0} {1}",
                createArchive, compressFilePaths),
            output, (uint)output.Capacity);

        //結果
        if (ret != 0)
            throw new ApplicationException(
                "書庫の作成に失敗しました。");

        Console.WriteLine(output.ToString());
    }

    static void Main(string[] args)
    {
        //圧縮
        //作成する書庫名を決定
        string archive = Path.Combine(
            System.Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory),
            Path.GetFileNameWithoutExtension(args[0]) + ".lzh");
        //基準ディレクトリを決定
        string baseDir = Path.GetDirectoryName(args[0]);
        //圧縮
        try
        {
            LhaCompressFiles(args, archive, baseDir);
            Console.WriteLine("書庫'{0}'を作成しました。", archive);
        }
        catch (Exception ex)
        {
            Console.WriteLine("書庫の作成に失敗しました。");
            Console.WriteLine("ERROR:" + ex.Message);
        }

        Console.ReadLine();
    }
}
VB.NETの場合
Imports System
Imports System.Text
Imports System.IO
Imports System.Runtime.InteropServices

Class Program
    'DLL の版の取得
    <DllImport("unlha32", _
        EntryPoint:="UnlhaGetVersion", _
        ExactSpelling:=True)> _
    Private Shared Function UnlhaGetVersion() As UShort
    End Function
    'DLL の実行状況の取得
    <DllImport("unlha32", _
        EntryPoint:="UnlhaGetRunning", _
        ExactSpelling:=True)> _
    Private Shared Function UnlhaGetRunning() As Boolean
    End Function
    '書庫操作一般
    <DllImport("unlha32", _
        EntryPoint:="Unlha", _
        CharSet:=CharSet.Ansi, _
        ExactSpelling:=True)> _
    Private Shared Function Unlha( _
        ByVal _hwnd As IntPtr, _
        <MarshalAs(UnmanagedType.LPStr)> ByVal _szCmdLine As String, _
        <MarshalAs(UnmanagedType.LPStr)> ByVal _szOutput As StringBuilder, _
        ByVal _dwSize As UInteger) As Integer
    End Function

    'ファイルを検索
    <DllImport("kernel32", _
        EntryPoint:="SearchPath", _
        SetLastError:=True, _
        CharSet:=CharSet.Auto, _
        ExactSpelling:=False)> _
    Private Shared Function SearchPath( _
        <MarshalAs(UnmanagedType.LPTStr)> ByVal lpPath As String, _
        <MarshalAs(UnmanagedType.LPTStr)> ByVal lpFileName As String, _
        <MarshalAs(UnmanagedType.LPTStr)> ByVal lpExtension As String, _
        ByVal nBufferLength As UInteger, _
        <MarshalAs(UnmanagedType.LPTStr)> ByVal lpBuffer As StringBuilder, _
        ByRef lpFilePart As IntPtr) As UInteger
    End Function

    ''' <summary>
    ''' UNLHA32.DLLで書庫を作成する
    ''' </summary>
    ''' <param name="compressFiles">圧縮するファイルのパス</param>
    ''' <param name="createArchive">作成するファイル名</param>
    ''' <param name="baseDir">基準ディレクトリ</param>
    Public Shared Sub LhaCompressFiles( _
        ByVal compressFiles() As String, _
        ByVal createArchive As String, _
        ByVal baseDir As String)
        '作成する書庫と同じ名前のファイルが無いか確認
        If File.Exists(createArchive) Then
            Throw New ApplicationException( _
                "ファイル" + createArchive + "が既に存在しています。")
        End If

        'DLLの存在を確認
        Dim pathPtr As IntPtr
        If SearchPath(Nothing, "unlha32.dll", Nothing, 0, _
            Nothing, pathPtr) = 0 Then
            Throw New ApplicationException("UNLHA32.DLLが見つかりません。")
        End If

        'DLLのチェック
        Try
            Dim ver As UShort = UnlhaGetVersion()
            Console.WriteLine("UNLHA32.DLLのバージョン:{0}", ver)
        Catch
            Throw New ApplicationException( _
                "UNLHA32.DLLがインストールされていません。")
        End Try

        '動作中かチェック
        If UnlhaGetRunning() Then
            Throw New ApplicationException("UNLHA32.DLLが現在動作中です。")
        End If

        '基準ディレクトリを修正
        If Not String.IsNullOrEmpty(baseDir) AndAlso _
            Not baseDir.EndsWith("\") Then
            baseDir += "\"
        End If

        'コマンドラインとする圧縮するファイル名のリストを取得
        Dim compressFilePaths As String = ""
        For Each f As String In compressFiles
            Dim filePath As String
            '相対パスとする
            If Not String.IsNullOrEmpty(baseDir) Then
                If f.StartsWith(baseDir) Then
                    filePath = f.Substring(baseDir.Length)
                Else
                    Throw New ApplicationException( _
                        "指定されたファイル'" + f + "'は基準ディレクトリ'" + _
                        baseDir + "'の下にありません。")
                End If
            End If

            If File.Exists(f) Then
                'ファイルのとき
                compressFilePaths += """" + filePath + """ "
            Else
                If Directory.Exists(f) Then
                    'フォルダのときは、
                    'すべてのファイルを表すワイルドカードを追加
                    compressFilePaths += _
                        """" + Path.Combine(filePath, "*") + """ "
                Else
                    'ファイルが存在しないとき
                    Throw New ApplicationException( _
                        "指定されたファイル'" + f + "'が存在しません。")
                End If
            End If
        Next f
        compressFilePaths = compressFilePaths.Trim()
        '基準ディレクトリを付加する
        If Not String.IsNullOrEmpty(baseDir) Then
            compressFilePaths = """" + baseDir + """ " + compressFilePaths
        End If
        'ファイル名を修正
        createArchive = """" + createArchive + """"

        Dim output As New StringBuilder(1024)

        '圧縮する
        Dim ret As Integer = Unlha( _
            IntPtr.Zero, _
            String.Format("a -d1 -jso1 {0} {1}", _
                createArchive, compressFilePaths), _
            output, _
            CType(output.Capacity, UInteger))

        '結果
        If ret <> 0 Then
            Throw New ApplicationException("書庫の作成に失敗しました。")
        End If

        Console.WriteLine(output.ToString())
    End Sub

    Shared Sub Main(ByVal args() As String)
        '圧縮
        '作成する書庫名を決定
        Dim archive As String = Path.Combine( _
            System.Environment.GetFolderPath( _
                Environment.SpecialFolder.DesktopDirectory), _
            Path.GetFileNameWithoutExtension(args(0)) + ".lzh")
        '基準ディレクトリを決定
        Dim baseDir As String = Path.GetDirectoryName(args(0))
        '圧縮
        Try
            LhaCompressFiles(args, archive, baseDir)
            Console.WriteLine("書庫'{0}'を作成しました。", archive)
        Catch ex As Exception
            Console.WriteLine("書庫の作成に失敗しました。")
            Console.WriteLine(("ERROR:" + ex.Message))
        End Try

        Console.ReadLine()
    End Sub
End Class

レスポンスファイル

 「Unlha32.dll」では、コマンドラインに指定できるパラメータの最大数が2048までとなっています。よって、この数以上のファイルをコマンドラインに指定することはできません。もしこれ以上の数を指定したいのであれば、レスポンスファイルを使わなければなりません。

 レスポンスファイルは、コマンドラインの一部(またはすべて)を記述したテキストファイルです。通常はShift JISで、各パラメータを改行記号で区切って記述します。レスポンスファイルをUnlha関数のコマンドラインに指定するには、レスポンスファイル名の前に「@」を付けます(ただし、「Unarj32.dll」は「!」、「Unimp32.dll」は「:」となります)。

 常識的には、これだけ多くのファイルを一度に指定することはなさそうですが、「Unlha32.dll」のドキュメント「NOTE.TXT」には次のように書かれています。「基本的に、対応アプリとしては、渡すファイルの数に関係なくレスポンスファイルを介してファイルのリストを渡すようにしておくのが得策です」。また、「Cab32.dll」などは最大パラメータ数が256と少なめですので、油断はできません。

 私の調べた限りでは、ほとんどの統合アーカイバDLLがレスポンスファイルに対応しています。ただし、レスポンスファイル内でファイル名以外のコマンドやスイッチを記述できるか、あるいはファイル名しか記述できないかという点においては、統一されていません。以下に私が調べた結果をまとめておきます。

  • レスポンスファイルにファイル名以外のコマンドやスイッチを記述できるDLL
  • Unlha32.dll、Unarj32.dll、Unrar32.dll、Yz1.dll、Unzip32.dll、Unimp32.dll
  • レスポンスファイルにファイル名しか記述できない(リストファイルしか指定できない)DLL
  • 7-Zip32.dll、Bga32.dll、Cab32.dll、Ungca32.dll、Unacev2j.dll、Unhki32.dll
  • レスポンスファイルにファイル名を記述できることを実際に確認したDLL
  • Tar32.dll、Zip32j.dll
  • レスポンスファイルに対応していないDLL
  • Unbel32.dll
補足
 各DLLのドキュメントを参考にしましたが、記述が不明確なものもあり、すべてが正しいという自信はありません。「レスポンスファイルにファイル名を記述できることを実際に確認したDLL」は、ファイルのリストをレスポンスファイルに指定して実際に圧縮できることを確認したDLLです。コマンドやスイッチを記述できるかは確認していません。

 この結果から、レスポンスファイルとしてファイル名のリストを使うのが無難かと思われます。

 レスポンスファイルの記述法もそれぞれ細かい点で異なるようですが、以下の点はほぼ共通のようです。

  • Shift JISで記述する。
  • コマンド(ファイル名)を改行文字(CRLF)で区切る。
  • ファイル名にスペース文字が含まれる時は、「"」で括る(ただし、「Unhki32.dll」は例外。「Unhki32.dll」では、「"」で括られたファイル名は無視されてしまう)。
  • レスポンスファイルのネスト(レスポンスファイル内でレスポンスファイルを指定すること)はできない。よって、レスポンスファイル内では「@」で始まるファイル名はそのまま記述できる。

 サンプルの「LhaCompressor_Console」プロジェクトでは、圧縮するファイルの数が多い時は、レスポンスファイルを使用するようにしています。

次のページ
動的PInvokeによるアンマネージDLL関数の呼び出し

修正履歴

この記事は参考になりましたか?

特集記事連載記事一覧

もっと読む

この記事の著者

どぼん!(ドボン!)

DOBON.NET内で.NET Frameworkの機能を紹介したWebサイト.NET Tipsやメールマガジン「.NETプログラミング研究」の発行人。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/444 2006/07/24 20:04

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー