「Unlha32.dll」を使用した書庫の展開
まずは、「Unlha32.dll」を使って書庫ファイルを展開する方法を説明します。統合アーカイバDLLの代表である「Unlha32.dll」の使い方を覚えれば、他の統合アーカイバDLLにも応用が利きます。もし他の統合アーカイバDLLとの相違点があれば、それも併せて説明します。
手順
「Unlha32.dll」を使って書庫ファイル内のすべてのファイルを指定したフォルダに展開する手順は、次のとおりです。説明のためにかなり手間をかけていますが、必要がない処理は省略してもかまいません。
- 書庫ファイルの存在をFile.Existsメソッドで確認する。
- 「Unlha32.dll」の存在をWin32APIのSearchPath関数で確認する。
SearchPath関数を使って確認します。.NET Frameworkには同様の機能を持つメソッドが用意されていませんので、もしSearchPath関数を使わないのであれば、パスの通ったフォルダを一つずつ探すことになるでしょう。- UnlhaGetVersion関数を呼び出し、バージョンを確認する。
UnlhaGetVersion関数を呼び出せるか確かめた方が良いかもしれません。バージョンにより動作の大きく異なるDLLが多いという意味でも、DLLのバージョンを確認しておいた方が良いでしょう。GetVersion関数を正常に呼び出せたからといって、DLLが正しくインストールされているとは判断できないようです。「Unrar32.dll」では、必要なDLLである「UnRAR.DLL」が存在しなければUnrarGetVersion関数は0を返しますが、「Unacev2j.dll」では「UNACEV2.DLL」がなくてもUnAceGetVersion関数は正常な値を返します。- UnlhaGetRunning関数により、「Unlha32.dll」が実行されていないことを確認する。
UnlhaGetRunning関数がtrueを返す時は、APIを実行できません。「Unlha32.dll」のドキュメント「NOTE2.TXT」によると、UnlhaGetRunningがtrueの時は、APIを実行しないようになっているようです。また、「API.TXT」には、「同時に実行される可能性がある場合はアプリ側でAPIを呼び出す前にチェックを行うようにしてください」とあります。GetRunning関数がtrueの時はファイルアクセスを伴うAPIが使用できないとなっているものが多いようですが、「UnImp32.dll」はドキュメント(Api.txt)に「いずれのAPIも使用する前に必ずUnImpGetRunning()を使用して「UnImp32.dll」が動作中でないことを確認してから使用するようにしてください」とあります。UnbelGetRunning関数がありません。- UnlhaCheckArchive関数により、展開が可能な書庫ファイルか調べる。
UnlhaCheckArchive関数がtrueを返したからといって実際に必ず展開できるわけではありません。それどころか、チェックモードによっては、falseを返しても展開できる可能性もあるようです。- 展開先フォルダを作成する。
- Unlha関数に渡すコマンドラインを作成する。
Unlha関数にコマンドラインを指定して行います。厄介なのは、同じ処理を行うにも、DLLによってコマンドが大きく異なる点です。よって、「Unlha32.dll」でファイルの展開で使用したコマンドラインが別のDLLでも使用できるということはまずなく、DLLごとに適当なコマンドラインを作成しなければなりません。コマンドに関する詳細は、各DLLと同梱されているドキュメント(大抵は「COMMAND.TXT」)に記述があるでしょう。- Unlha関数を呼び出して書庫を展開する。
_hwndパラメータには、親ウィンドウとするウィンドウのハンドラを指定します。ダイアログがこのウィンドウを親として表示されるようになるだけでなく、親ウィンドウは一切操作できなくなります。ウィンドウが存在しないか、親ウィンドウを指定する必要がなければ、IntPtr.Zeroを指定します。_szOutputパラメータには、「Unlha32.dll」が結果を出力するバッファを指定します。具体的な使い方は、下の例をご覧ください。結果を取得する必要がなければ、_szOutputにNothing(C#では、null)、_dwSizeに0を指定します。- 正しく展開されたか確認する。
Unlha関数は失敗するとエラーコード(0以外の整数)を返しますが、エラーコードを返した場合でも正常に終了している可能性があるようです(ほとんどは0x8000以下になるそうです)。よって、エラーコードが返った場合でも、完全に展開に失敗したとは判断できません。コード
以下に「Unlha32.dll」を使って書庫を展開するコードを紹介します。このコードはアウトラインを示すものであり、多くの部分で簡略化されています。より完成されたコードについては、サンプルの「LhaExtractor_Console」プロジェクトをご覧ください。
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(); } }
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
- 「--」以降は「@」で始まるファイルを指定できるDLL
- 「-!」以降は「!」で始まるファイルを指定できるDLL
このような回避法が用意されていないDLLでは、「-」や「@」ではじまるファイル名の前に「.\」を付けるという方法があります。サンプルの「LhaExtractor_Console」プロジェクトでは「Unlha32.dll」以外の統合アーカイバDLLも考慮して、この方法を使っています。
すべてのファイルを表すワイルドカード
「Unlha32.dll」では、すべてのファイルを表すワイルドカードは、「*.*」または「*」です。しかし、他の統合アーカイバDLLではそうではない場合があります。私の調べた限りでは、次のようになっているようです(太字のDLLは、著者が実際に試して確認したDLLです。これらのDLLのドキュメントには明確な記述が見つかりませんでした)。
- 「*.*」と「*」の両方ともすべてのファイルを表すDLL
- 「*」のみがすべてのファイルを表し、「*.*」は拡張子を持つファイルを表すDLL
つまり、すべてのファイルを表す表記は、「*.*」ではなく、「*」とすべきでしょう。
