SHOEISHA iD

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

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

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

特集記事

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

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


「Unlha32.dll」を使用した書庫の展開

 まずは、「Unlha32.dll」を使って書庫ファイルを展開する方法を説明します。統合アーカイバDLLの代表である「Unlha32.dll」の使い方を覚えれば、他の統合アーカイバDLLにも応用が利きます。もし他の統合アーカイバDLLとの相違点があれば、それも併せて説明します。

手順

 「Unlha32.dll」を使って書庫ファイル内のすべてのファイルを指定したフォルダに展開する手順は、次のとおりです。説明のためにかなり手間をかけていますが、必要がない処理は省略してもかまいません。

  1. 書庫ファイルの存在をFile.Existsメソッドで確認する。
  1. 「Unlha32.dll」の存在をWin32APIのSearchPath関数で確認する。
  2. 「Unlha32.dll」は通常、システムフォルダに入れることになっていますが、それ以外の場所にある可能性もあります。「Unlha32.dll」がパスの通ったフォルダにあるか、Win32APIのSearchPath関数を使って確認します。.NET Frameworkには同様の機能を持つメソッドが用意されていませんので、もしSearchPath関数を使わないのであれば、パスの通ったフォルダを一つずつ探すことになるでしょう。
  1. UnlhaGetVersion関数を呼び出し、バージョンを確認する。
  2. 「Unlha32.dll」の存在が確認できればまずインストールされていると思って間違いないですが、同名の別のDLLである可能性もあります。念のためにUnlhaGetVersion関数を呼び出せるか確かめた方が良いかもしれません。バージョンにより動作の大きく異なるDLLが多いという意味でも、DLLのバージョンを確認しておいた方が良いでしょう。
    ただ、統合アーカイバDLLのGetVersion関数を正常に呼び出せたからといって、DLLが正しくインストールされているとは判断できないようです。「Unrar32.dll」では、必要なDLLである「UnRAR.DLL」が存在しなければUnrarGetVersion関数は0を返しますが、「Unacev2j.dll」では「UNACEV2.DLL」がなくてもUnAceGetVersion関数は正常な値を返します。
  1. UnlhaGetRunning関数により、「Unlha32.dll」が実行されていないことを確認する。
  2. 「Unlha32.dll」では、UnlhaGetRunning関数がtrueを返す時は、APIを実行できません。「Unlha32.dll」のドキュメント「NOTE2.TXT」によると、UnlhaGetRunningがtrueの時は、APIを実行しないようになっているようです。また、「API.TXT」には、「同時に実行される可能性がある場合はアプリ側でAPIを呼び出す前にチェックを行うようにしてください」とあります。
    「Unlha32.dll」以外の統合アーカイバDLLでは、GetRunning関数がtrueの時はファイルアクセスを伴うAPIが使用できないとなっているものが多いようですが、「UnImp32.dll」はドキュメント(Api.txt)に「いずれのAPIも使用する前に必ずUnImpGetRunning()を使用して「UnImp32.dll」が動作中でないことを確認してから使用するようにしてください」とあります。
補足
 「Unbel32.dll」にはそもそもUnbelGetRunning関数がありません。
  1. UnlhaCheckArchive関数により、展開が可能な書庫ファイルか調べる。
  2. ただし、UnlhaCheckArchive関数がtrueを返したからといって実際に必ず展開できるわけではありません。それどころか、チェックモードによっては、falseを返しても展開できる可能性もあるようです。
  1. 展開先フォルダを作成する。
  2. 「Unlha32.dll」は展開時に展開先のフォルダが存在しなければ自動的にフォルダを作成してくれます。しかし、すべての統合アーカイバDLLがそうするわけではありません(例えば、「7-Zip32.dll」はフォルダを作成せずにエラーを返します)。よって、展開先フォルダを事前に作っておくべきでしょう。下の例では、展開先フォルダを作成していませんが、サンプルでは作成するようにしています。
  1. Unlha関数に渡すコマンドラインを作成する。
  2. ここが一番難しいところかもしれません。「Unlha32.dll」をはじめ、統合アーカイバDLLには、書庫内のファイルを指定した場所に展開したり、ファイルを圧縮して書庫ファイルを作成する専用のAPIが用意されていません。これらの処理は、Unlha関数にコマンドラインを指定して行います。厄介なのは、同じ処理を行うにも、DLLによってコマンドが大きく異なる点です。よって、「Unlha32.dll」でファイルの展開で使用したコマンドラインが別のDLLでも使用できるということはまずなく、DLLごとに適当なコマンドラインを作成しなければなりません。コマンドに関する詳細は、各DLLと同梱されているドキュメント(大抵は「COMMAND.TXT」)に記述があるでしょう。
    「Unlha32.dll」で書庫を展開する場合に使用するコマンドは、「e」か「x」です。「e」がパス情報を無視してすべてのファイルを同一フォルダに展開するのに対して、「x」はパス情報を有効にします。また、「x」はファイルの上書きやフォルダの作成などの確認を行わないという違いもあります。(詳しくは、「Unlha32.dll」のドキュメントをご覧ください)。
    また、コマンドラインにファイル名をどのように指定するかも難しい問題です。ファイル名にスペースが含まれる時は、「"」で括る必要があります(すべてのファイルを「"」で括っても問題ありません)。また、展開先のフォルダ名は、最後が「\」か「:」で終わっている必要があります。「-」や「@」で始まるファイル名にはさらに注意が必要ですが、これは後述します。すべてのファイルを表すワイルドカード表記についても後述します。
  1. Unlha関数を呼び出して書庫を展開する。
  2. _hwndパラメータには、親ウィンドウとするウィンドウのハンドラを指定します。ダイアログがこのウィンドウを親として表示されるようになるだけでなく、親ウィンドウは一切操作できなくなります。ウィンドウが存在しないか、親ウィンドウを指定する必要がなければ、IntPtr.Zeroを指定します。
    _szOutputパラメータには、「Unlha32.dll」が結果を出力するバッファを指定します。具体的な使い方は、下の例をご覧ください。結果を取得する必要がなければ、_szOutputにNothing(C#では、null)、_dwSizeに0を指定します。
  1. 正しく展開されたか確認する。
  2. Unlha関数は失敗するとエラーコード(0以外の整数)を返しますが、エラーコードを返した場合でも正常に終了している可能性があるようです(ほとんどは0x8000以下になるそうです)。よって、エラーコードが返った場合でも、完全に展開に失敗したとは判断できません。
    本当に正しく展開されたか判断するには、事前に展開されるべきファイルを調べ、実際に展開されたか確認するという方法になりそうですが、ここではそこまでやっていません。

コード

 以下に「Unlha32.dll」を使って書庫を展開するコードを紹介します。このコードはアウトラインを示すものであり、多くの部分で簡略化されています。より完成されたコードについては、サンプルの「LhaExtractor_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 = "UnlhaCheckArchive",
        CharSet = CharSet.Ansi,
        ExactSpelling = true)]
    private extern static bool UnlhaCheckArchive(
        [MarshalAs(UnmanagedType.LPStr)]string _szFileName,
        int _iMode);
    //書庫操作一般
    [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="archiveFile">書庫ファイル名</param>
    /// <param name="extractTo">展開先のフォルダ名</param>
    public static void LhaExtractArchive(
        string archiveFile, string extractTo)
    {
        //指定されたファイルがあるか調べる
        if (!System.IO.File.Exists(archiveFile))
            throw new ApplicationException(
                "指定されたファイル'" + archiveFile + "'が存在しません。");

        //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}", ver);
        }
        catch
        {
            throw new ApplicationException(
                "UNLHA32.DLLがインストールされていません。");
        }

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

        //展開できるかチェック
        if (!UnlhaCheckArchive(archiveFile, 0))
            throw new ApplicationException(
                "UNLHA32.DLLでは展開できません。");

        //ファイル名とフォルダ名を修正する
        archiveFile = "\"" + archiveFile + "\"";
        //フォルダ名は必ず\で終わる必要がある
        if (!extractTo.EndsWith("\\"))
            extractTo += "\\";
        extractTo = "\"" + extractTo + "\"";

        StringBuilder output = new StringBuilder(1024);

        //展開する
        int ret = Unlha(IntPtr.Zero,
            string.Format("x {0} {1} *", archiveFile, extractTo),
            output, (uint)output.Capacity);

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

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

    static void Main(string[] args)
    {
        //展開
        foreach (string f in args)
        {
            //展開先を決定
            string extractDir = System.Environment.GetFolderPath(
                Environment.SpecialFolder.DesktopDirectory);
            extractDir = Path.Combine(extractDir,
                Path.GetFileNameWithoutExtension(f));
            //展開
            try
            {
                LhaExtractArchive(f, extractDir);
                Console.WriteLine("'{0}'を'{1}'に展開しました。", f, extractDir);
            }
            catch (Exception ex)
            {
                Console.WriteLine("'{0}'の展開に失敗しました。", f);
                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:="UnlhaCheckArchive", _
        CharSet:=CharSet.Ansi, _
        ExactSpelling:=True)> _
    Private Shared Function UnlhaCheckArchive( _
        <MarshalAs(UnmanagedType.LPStr)> ByVal _szFileName As String, _
        ByVal _iMode As Integer) 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="archiveFile">書庫ファイル名</param>
    ''' <param name="extractTo">展開先のフォルダ名</param>
    Public Shared Sub LhaExtractArchive( _
        ByVal archiveFile As String, ByVal extractTo As String)
        '指定されたファイルがあるか調べる
        If Not System.IO.File.Exists(archiveFile) Then
            Throw New ApplicationException( _
                "指定されたファイル'" + archiveFile + "'が存在しません。")
        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 UnlhaCheckArchive(archiveFile, 0) Then
            Throw New ApplicationException("UNLHA32.DLLでは展開できません。")
        End If

        'ファイル名とフォルダ名を修正する
        archiveFile = """" + archiveFile + """"
        'フォルダ名は必ず\で終わる必要がある
        If Not extractTo.EndsWith("\") Then
            extractTo += "\"
        End If
        extractTo = """" + extractTo + """"

        Dim output As New StringBuilder(1024)

        '展開する
        Dim ret As Integer = Unlha( _
            IntPtr.Zero, _
            String.Format("x {0} {1} *", archiveFile, extractTo), _
            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)
        '展開
        For Each f As String In args
            '展開先を決定
            Dim extractDir As String = _
                System.Environment.GetFolderPath( _
                    Environment.SpecialFolder.DesktopDirectory)
            extractDir = Path.Combine(extractDir, _
                Path.GetFileNameWithoutExtension(f))
            '展開
            Try
                LhaExtractArchive(f, extractDir)
                Console.WriteLine("'{0}'を'{1}'に展開しました。", _
                    f, extractDir)
            Catch ex As Exception
                Console.WriteLine("'{0}'の展開に失敗しました。", f)
                Console.WriteLine(("ERROR:" + ex.Message))
            End Try
        Next f

        Console.ReadLine()
    End Sub
End Class

「-」や「@」で始まるファイル名をコマンドラインに指定する

 「-」や「@」で始まるファイル名をコマンドに指定するときは、注意が必要です。「Unlha32.dll」では「-」ではじめるコマンドはスイッチと判断され、「@」で始まるコマンドはレスポンスファイル(詳しくは後述)と判断されます。これは、「"」で括っても解決されません。このような動作は、他の統合アーカイバDLLでも同じようです。

 これを解決する方法として「Unlha32.dll」には、「-gb」というスイッチが用意されています。「-」ではじまるファイル名の前に「-gb」を書くと、スイッチではなくファイル名として扱われます。「-」ではじまらないファイル名の前に「-gb」スイッチをつけても問題ありませんので、すべてのファイル名に「-gb」をつけるのも一つの手です。

 また「@」ではじまるファイル名が普通のファイルとして扱われるようにするには、「--」スイッチを使います。「--」を指定すると、それ以降のコマンドは「@」で始まっていてもレスポンスファイルとして扱われなくなります。

 「Unlha32.dll」にはこのような解決法が用意されていますが、その他の総合DLLにはこのような方法が用意されていないものがほとんどです。私が調べた限りでは、次のような状況でした。

  • 「--」以降は「-」で始まるファイルを指定できるDLL
  • 7-Zip32.dll、Unrar32.dll、Unimp32.dll
  • 「--」以降は「@」で始まるファイルを指定できるDLL
  • Unlha32.dll、Bga32.dll、Unrar32.dll、Yz1.dll、Unhki32.dll
  • 「-!」以降は「!」で始まるファイルを指定できるDLL
  • Unarj32.dll

 このような回避法が用意されていないDLLでは、「-」や「@」ではじまるファイル名の前に「.\」を付けるという方法があります。サンプルの「LhaExtractor_Console」プロジェクトでは「Unlha32.dll」以外の統合アーカイバDLLも考慮して、この方法を使っています。

すべてのファイルを表すワイルドカード

 「Unlha32.dll」では、すべてのファイルを表すワイルドカードは、「*.*」または「*」です。しかし、他の統合アーカイバDLLではそうではない場合があります。私の調べた限りでは、次のようになっているようです(太字のDLLは、著者が実際に試して確認したDLLです。これらのDLLのドキュメントには明確な記述が見つかりませんでした)。

  • 「*.*」と「*」の両方ともすべてのファイルを表すDLL
  • Unlha32.dll、Unzip32.dll、Zip32j.dll、Cab32.dll、Tar32.dll、Unarj32.dll、Ungca32.dllBga32.dll、Unacev2j.dll、Unhki32.dll
  • 「*」のみがすべてのファイルを表し、「*.*」は拡張子を持つファイルを表すDLL
  • 7-Zip32.dll、Unrar32.dll、Yz1.dll、Unimp32.dll

 つまり、すべてのファイルを表す表記は、「*.*」ではなく、「*」とすべきでしょう。

次のページ
「Unlha32.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」など、さまざまなカンファレンスを企画・運営しています。

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

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

メールバックナンバー