ここでは.NETで外部のDLLの関数(アンマネージDLL関数)を呼び出す方法を考えます。しかもここでの最終目標は遅延バインディングによる方法です。その具体例として、MiccoさんのUNLHA32.DLLを使って書庫を展開(解凍)するコードを書いてみることにします。最終的には「統合アーカイバプロジェクト」に対応したあらゆるDLLを使って書庫を展開できるアプリケーションを作成することを目的とします。
注意:.NET Framework 2.0からは.NET Frameworkの機能だけでここで紹介していることと同じことができるようになりました。詳しくは、「動的PInvokeによる統合アーカイバ仕様DLLを使用した書庫の展開と作成」をご覧ください。また、統合アーカイバAPI仕様の各DLLのコマンドやスイッチの違いに関して、「統合アーカイバDLLの相違」にまとめてあります。
DLL関数の呼び出し方の説明は後にして、まずはUnlha32.dllを使って書庫を展開する方法をごく簡単に説明します。
Unlha32.dllはLHA(.lzh)形式の書庫を操作(圧縮、解凍など)するためのDLLです。これを使うことにより、簡単に対応書庫の展開が出来るようになります。Unlha32.dllの使い方に関する詳しい説明は、Unlha32.dllのアーカイブに同梱されている各種ドキュメントに詳しく書かれていますので、興味のある方はそちらをお読みください。
Unlha32.dllで書庫を展開するには、通常Unlha関数を使います(Unlha32.dllのアーカイブ内の"API.TXT"に説明があります)。Unlha関数は、コマンド文字列により書庫を操作するもので、そのコマンドはLHA.EXE互換となっています。例えば書庫内のファイルをすべて指定されたフォルダに展開するには、
x 書庫名 解凍先フォルダ名 *
などとします。これにオプション等を付加していけば、展開方法を細かく変えることも出来ます。(コマンドに関してはUnlha32.dllのアーカイブ内の"COMMAND.TXT"に詳しい説明があります。)
展開に関してはこれだけですが、余計なエラーがないように、その前にいくつかのチェックをしておきましょう。まず、展開する前には、Unlha32.dllが本当にインストールされているか、UnlhaGetVersion関数で調べます。また、UNLHA32.DLL は複数同時実行に対応していないので、UnlhaGetRunning関数で実行中でないことを確認します。ついでにUnlhaCheckArchive関数で指定されたファイルがUnlha32.dllで扱える書庫かどうか確認しておきます。
これで書庫の展開に関するUnlha32.dllの知識は身に付いたものとしましょう。
.NETでアンマネージDLL関数を呼び出す方法としては、DllImportを使う他ないでしょう(VB.NETのDeclareステートメントも結局は同じことです)。
ここではこの方法を紹介することが目的ではないため、DllImportに関する説明いたしません。これらに関する知識はヘルプの「アンマネージ DLL 関数の処理」や「プラットフォーム呼び出しによるデータのマーシャリング」などでご確認ください。
以降、これらの知識がすでにあるものとして話を進めさせていただきます。
以上の知識を基に、いよいよ実際に書庫を展開するコードを書いてみましょう。ここではUNLHA32.DLLで書庫を展開するためのメソッドを作ることにします。このメソッド(LhaExtractArchiveとしましょう)に書庫ファイル名と展開先のフォルダ名を渡すことにより、書庫が展開できます。
'Imports System.Runtime.InteropServices 'がソースファイルの一番上に書かれているものとする 'DLL の版の取得 <DllImport("unlha32")> _ Private Shared Function UnlhaGetVersion() As UInt16 End Function 'DLL の実行状況の取得 <DllImport("unlha32")> _ Private Shared Function UnlhaGetRunning() As Boolean End Function '書庫のチェック <DllImport("unlha32")> _ Private Shared Function UnlhaCheckArchive( _ ByVal szFileName As String, _ ByVal iMode As Integer) As Boolean End Function '書庫操作一般 <DllImport("unlha32")> _ Private Shared Function Unlha( _ ByVal hwnd As Integer, _ ByVal szCmdLine As String, _ ByVal szOutput As String, _ ByVal dwSize As Integer) As Integer End Function ''' <summary> ''' UNLHA32.DLLで書庫を展開する ''' </summary> ''' <param name="archiveFile">書庫ファイル名</param> ''' <param name="extractDir">展開先のフォルダ名</param> Public Shared Sub LhaExtractArchive( _ ByVal archiveFile As String, _ ByVal extractDir As String) '指定されたファイルがあるか調べる If Not System.IO.File.Exists(archiveFile) Then Throw New ApplicationException("ファイルが見つかりません") End If 'DLLのチェック Try Dim ver As UInt16 = UnlhaGetVersion() Console.WriteLine("バージョン:{0}", ver) Catch Throw New _ ApplicationException("Unlha32.dllがインストールされていません") End Try '動作中かチェック If UnlhaGetRunning() Then Throw New ApplicationException("DLLが動作中") End If '展開できるかチェック If Not UnlhaCheckArchive(archiveFile, 0) Then Throw New ApplicationException("対応書庫ではありません") End If 'ファイル名とフォルダ名を修正する If archiveFile.IndexOf(" "c) > 0 Then archiveFile = """" + archiveFile + """" End If If Not extractDir.EndsWith("\") Then extractDir += "\" End If If extractDir.IndexOf(" "c) > 0 Then extractDir = """" + extractDir + """" End If '展開する Dim ret As Integer = Unlha(0, _ String.Format("x {0} {1} *", archiveFile, extractDir), _ Nothing, 0) '結果 If ret <> 0 Then Throw New ApplicationException("書庫の展開に失敗しました") End If End Sub
//using System.Runtime.InteropServices; //がソースファイルの一番上に書かれているものとする //DLL の版の取得 [DllImport("unlha32")] private extern static UInt16 UnlhaGetVersion(); //DLL の実行状況の取得 [DllImport("unlha32")] private extern static bool UnlhaGetRunning(); //書庫のチェック [DllImport("unlha32")] private extern static bool UnlhaCheckArchive(string szFileName, int iMode); //書庫操作一般 [DllImport("unlha32")] private extern static int Unlha(int hwnd, string szCmdLine, string szOutput, int dwSize); /// <summary> /// UNLHA32.DLLで書庫を展開する /// </summary> /// <param name="archiveFile">書庫ファイル名</param> /// <param name="extractDir">展開先のフォルダ名</param> public static void LhaExtractArchive( string archiveFile, string extractDir) { //指定されたファイルがあるか調べる if (!System.IO.File.Exists(archiveFile)) throw new ApplicationException("ファイルが見つかりません"); //DLLのチェック try { UInt16 ver = UnlhaGetVersion(); Console.WriteLine("バージョン:{0}", ver); } catch { throw new ApplicationException("Unlha32.dllがインストールされていません"); } //動作中かチェック if (UnlhaGetRunning()) throw new ApplicationException("DLLが動作中"); //展開できるかチェック if (!UnlhaCheckArchive(archiveFile, 0)) throw new ApplicationException("対応書庫ではありません"); //ファイル名とフォルダ名を修正する if (archiveFile.IndexOf(' ') > 0) archiveFile = "\"" + archiveFile + "\""; if (!extractDir.EndsWith("\\")) extractDir += "\\"; if (extractDir.IndexOf(' ') > 0) extractDir = "\"" + extractDir + "\""; //展開する int ret = Unlha(0, string.Format("x {0} {1} *", archiveFile, extractDir), null, 0); //結果 if (ret != 0) throw new ApplicationException("書庫の展開に失敗しました"); }
うまく行きましたか?普通ならこれで、
「書庫が解凍できました。めでたし、めでたし。」
となって終わるところでしょうが、それでは面白くもなんともありません。さあ、いよいよこれからが本番です。
私はこの記事の一番初めに
最終的には「統合アーカイバプロジェクト」に対応したあらゆるDLLを使って書庫を展開できるアプリケーションを作成することを目的とします。
と書きました。さて、UNLHA32.DLL以外のDLLを使って書庫を展開するにはどうすればよいのでしょうか?単純に考えれば、それにはこれらすべてのDLLごとに書庫を展開するためのコードを書いていけばいいとなります。
例えば"UNZIP32.DLL"(shoda T.さん作)の場合は、先のUNLHA32.DLLの例の "UnlhaGetVersion" を "UnZipGetVersion" に、 "UnlhaGetRunning" を "UnZipGetRunning" に、 "UnlhaCheckArchive" を "UnZipCheckArchive" にし、さらに展開のコマンドを "-x 書庫名 解凍先フォルダ名 *" とすればOKでしょう。
"CAB32.DLL"(宮内邦昭さん作)の場合も同様に、UNLHA32.DLLのAPI名の"Unzip"の部分を"Cab"に変え、展開のコマンドを"-x 書庫名 解凍先フォルダ名 *"とします。
"UNZIP32.DLL"と"CAB32.DLL"の例を示しましたが、実は統合アーカイバ仕様のDLLは他も同様に、API名の先頭の文字列とコマンドを変えるだけで同じように扱うことが出来るのです。それがすなわち、「統合アーカイバ仕様」ということです。
このような仕様のおかげで、統合アーカイバ仕様のDLLを使用するコードは、一つを理解すれば後は簡単に書くことが出来ます。しかしそれだけでよいものでしょうか?
今までの考察からすれば、DLLファイル名と、そのDLLのAPIの頭に付く文字列と、展開のためのコマンドが分かれば、そのDLLを使って書庫を展開できうることが分かります。つまり、未知のDLLであってもそれが統合アーカイバ仕様であれば、例えばユーザーから必要な情報を提供されれば、使うことが出来るということになります。これがすなわち、「統合アーカイバプロジェクトに対応したあらゆるDLLを使って書庫を展開できるアプリケーション」ということです。
もしDllImportでDLLファイル名と関数の名前(呼び出すDLLエントリポイントの名前)を変数で指定することができれば、この問題は解決します。しかし残念ながらそれは出来ません。つまり、アプリケーション作成時に使うことを明確にしたDLLしか使うことが出来ない、事前バインディングしか使えないということです。
.NETでは無理ですが、実はVC++ではこれは造作ないことです。LoadLibrary、GetProcAddress関数により動的リンク(ダイナミックリンク)を使うことによって、プロセスの実行コードに含まれない関数を呼び出すことができます。(詳しくはヘルプ「Visual C++ の概念: 機能の追加 - DLL」をご覧ください。)
.NETでも同じことが出来ればよいのですが、果たして可能でしょうか?
その答えが、Richard Birkbyさんが書いた「The Code Project - Late binding on native DLLs with C#」にあります。
詳しくはこのページを読んでいただきたいのですが、つまりは次のようなことのようです。(英語に自信がないので、正確かどうかわかりません。)
Win32 APIのLoadLibrary関数とGetProcAddress関数により指定されたDLLの関数のアドレスは取得できますが、このアドレスで関数にアクセスする方法が.NETにはありません。そこでこの部分は他のDLLでやってもらうことにします。それがここではInvoke.dllのInvokeFunc関数ということになります。ちなみにこのInvokeFunc関数と同じ機能のものに、msjava.dllのcall関数がありますが、msjava.dllはWindows XPにはインストールされていないとのことです。
上記のページからダウンロードできるアーカイバにはソースしか含まれていませんので、Invoke.dllは自分でコンパイルして作成します。makefileがありますので、それをnmake.exe(例えば"C:\Program Files\Microsoft Visual Studio .NET 2003\SDK\v1.1\Bin"にあります)に渡せばコンパイルできます。(私の環境ではそれだけではコンパイルできず、さらにいくつかのディレクトリにパスを通す必要がありました。)
それでは今度はこの方法で先のコードを書き換えてみましょう。展開する書庫名、展開先のほかに、使用するDLL、APIの頭に付く文字列、展開のためのコマンドを引数とするメソッドExtractArchiveを作成します。
'Imports System.Runtime.InteropServices 'がソースファイルの一番上に書かれているものとする 'DLLモジュールをマップ <DllImport("kernel32")> _ Private Shared Function LoadLibrary( _ ByVal lpLibFileName As String) As Integer End Function 'マップを解除 <DllImport("kernel32")> _ Private Shared Function FreeLibrary( _ ByVal hLibModule As Integer) As Boolean End Function '関数のアドレスを取得 <DllImport("kernel32")> _ Private Shared Function GetProcAddress( _ ByVal hModule As Integer, ByVal lpProcName As String) As Integer End Function '以下使用するAPIのためのInvokeFunc <DllImport("Invoke", EntryPoint:="InvokeFunc")> _ Private Shared Function InvokeGetVersion( _ ByVal funcptr As Integer) As UInt16 End Function <DllImport("Invoke", EntryPoint:="InvokeFunc")> _ Private Shared Function InvokeGetRunning( _ ByVal funcptr As Integer) As Boolean End Function <DllImport("Invoke", EntryPoint:="InvokeFunc")> _ Private Shared Function InvokeCheckArchive( _ ByVal funcptr As Integer, _ ByVal szFileName As String, _ ByVal iMode As Integer) As Boolean End Function <DllImport("Invoke", EntryPoint:="InvokeFunc")> _ Private Shared Function InvokeMain( _ ByVal funcptr As Integer, _ ByVal hwnd As Integer, _ ByVal szCmdLine As String, _ ByVal szOutput As String, _ ByVal dwSize As Integer) As Integer End Function ''' <summary> ''' 統合アーカイバ仕様のDLLで書庫を展開する ''' </summary> ''' <param name="dllName">DLLファイル名</param> ''' <param name="funcName">APIの頭に付く文字列</param> ''' <param name="command">展開のためのコマンド</param> ''' <param name="archiveFile">書庫ファイル名</param> ''' <param name="extractDir">展開先のフォルダ名</param> Public Shared Sub ExtractArchive( _ ByVal dllName As String, _ ByVal funcName As String, _ ByVal command As String, _ ByVal archiveFile As String, _ ByVal extractDir As String) '指定されたファイルがあるか調べる If Not System.IO.File.Exists(archiveFile) Then Throw New ApplicationException("ファイルが見つかりません") End If 'DLLをロード Dim hmod As Integer = LoadLibrary(dllName) If hmod = 0 Then Throw New ApplicationException( _ dllName + "のロードに失敗しました") End If Try Dim funcaddr As Integer 'DLLのチェック '関数のアドレスを取得 funcaddr = GetProcAddress(hmod, funcName + "GetVersion") If funcaddr = 0 Then Throw New ApplicationException( _ dllName + "がインストールされていません") End If Dim ver As UInt16 = InvokeGetVersion(funcaddr) Console.WriteLine("バージョン:{0}", ver) '動作中かチェック funcaddr = GetProcAddress(hmod, funcName + "GetRunning") If funcaddr = 0 Then Throw New ApplicationException( _ funcName + "GetRunningのアドレスが取得できませんでした") End If If InvokeGetRunning(funcaddr) Then Throw New ApplicationException(dllName + "が動作中") End If '展開できるかチェック funcaddr = GetProcAddress(hmod, funcName + "CheckArchive") If funcaddr = 0 Then Throw New ApplicationException( _ funcName + "CheckArchiveのアドレスが取得できませんでした") End If If Not InvokeCheckArchive(funcaddr, archiveFile, 0) Then Throw New ApplicationException( _ archiveFile + "は対応書庫ではありません") End If 'ファイル名とフォルダ名を修正する If archiveFile.IndexOf(" "c) > 0 Then archiveFile = """" + archiveFile + """" End If If Not extractDir.EndsWith("\") Then extractDir += "\" End If If extractDir.IndexOf(" "c) > 0 Then extractDir = """" + extractDir + """" End If '展開する funcaddr = GetProcAddress(hmod, funcName) If funcaddr = 0 Then Throw New ApplicationException( _ funcName + "のアドレスが取得できませんでした") End If Dim ret As Integer = InvokeMain(funcaddr, 0, _ String.Format(command, archiveFile, extractDir), _ Nothing, 0) '結果 If ret <> 0 Then Throw New ApplicationException("書庫の展開に失敗しました") End If Finally '解放する If hmod <> 0 Then FreeLibrary(hmod) End If End Try End Sub
//using System.Runtime.InteropServices; //がソースファイルの一番上に書かれているものとする //DLLモジュールをマップ [DllImport("kernel32")] private extern static int LoadLibrary(string lpLibFileName); //マップを解除 [DllImport("kernel32")] private extern static bool FreeLibrary(int hLibModule); //関数のアドレスを取得 [DllImport("kernel32")] private extern static int GetProcAddress(int hModule, string lpProcName); //以下使用するAPIのためのInvokeFunc [DllImport("Invoke", EntryPoint="InvokeFunc")] privatelic extern static UInt16 InvokeGetVersion(int funcptr); [DllImport("Invoke", EntryPoint="InvokeFunc")] private extern static bool InvokeGetRunning(int funcptr); [DllImport("Invoke", EntryPoint="InvokeFunc")] private extern static bool InvokeCheckArchive(int funcptr, string szFileName, int iMode); [DllImport("Invoke", EntryPoint="InvokeFunc")] private extern static int InvokeMain(int funcptr, int hwnd, string szCmdLine, string szOutput, int dwSize); /// <summary> /// 統合アーカイバ仕様のDLLで書庫を展開する /// </summary> /// <param name="dllName">DLLファイル名</param> /// <param name="funcName">APIの頭に付く文字列</param> /// <param name="command">展開のためのコマンド</param> /// <param name="archiveFile">書庫ファイル名</param> /// <param name="extractDir">展開先のフォルダ名</param> public static void ExtractArchive(string dllName, string funcName, string command, string archiveFile, string extractDir) { //指定されたファイルがあるか調べる if (!System.IO.File.Exists(archiveFile)) throw new ApplicationException("ファイルが見つかりません"); //DLLをロード int hmod = LoadLibrary(dllName); if (hmod == 0) throw new ApplicationException(dllName + "のロードに失敗しました"); try { int funcaddr; //DLLのチェック //関数のアドレスを取得 funcaddr = GetProcAddress(hmod, funcName + "GetVersion"); if (funcaddr == 0) throw new ApplicationException( dllName + "がインストールされていません"); UInt16 ver = InvokeGetVersion(funcaddr); Console.WriteLine("バージョン:{0}", ver); //動作中かチェック funcaddr = GetProcAddress(hmod, funcName + "GetRunning"); if (funcaddr == 0) throw new ApplicationException( funcName + "GetRunningのアドレスが取得できませんでした"); if (InvokeGetRunning(funcaddr)) throw new ApplicationException(dllName + "が動作中"); //展開できるかチェック funcaddr = GetProcAddress(hmod, funcName + "CheckArchive"); if (funcaddr == 0) throw new ApplicationException( funcName + "CheckArchiveのアドレスが取得できませんでした"); if (!InvokeCheckArchive(funcaddr, archiveFile, 0)) throw new ApplicationException( archiveFile + "は対応書庫ではありません"); //ファイル名とフォルダ名を修正する if (archiveFile.IndexOf(' ') > 0) archiveFile = "\"" + archiveFile + "\""; if (!extractDir.EndsWith("\\")) extractDir += "\\"; if (extractDir.IndexOf(' ') > 0) extractDir = "\"" + extractDir + "\""; //展開する funcaddr = GetProcAddress(hmod, funcName); if (funcaddr == 0) throw new ApplicationException( funcName + "のアドレスが取得できませんでした"); int ret = InvokeMain(funcaddr, 0, string.Format(command, archiveFile, extractDir), null, 0); //結果 if (ret != 0) throw new ApplicationException("書庫の展開に失敗しました"); } finally { //解放する if (hmod != 0) FreeLibrary(hmod); } }
これでユーザーがこれらの情報を与えることですべての統合アーカイバ仕様のDLLに対応したアプリが作れるようになりました。
一応の目標は達成したといえますが、せっかくここまで来たので、もう少しまともなアプリを作りましょう。
書庫の展開に必要なDLLに関する情報はユーザーがXML形式のファイルで与えるものとし、アプリは書庫を展開する時にその情報を読み込み、どのDLLで展開するのが適当かアプリ自身が判断するようにします。
ユーザーが与えるXMLファイルは例えば次のように記述するものとします。このようにすれば、アプリでは逆シリアル化により、簡単に情報を読み込むことが出来るようになります。このファイルは実行ファイルと同じフォルダにファイル名"dlls.config"という名前でUTF-8で保存されているものとします。
<?xml version="1.0" encoding="utf-8"?> <ArrayOfArchiverDllInfo xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <ArchiverDllInfo> <FileName>unlha32</FileName> <FunctionName>Unlha</FunctionName> <CommandToExtract>x {0} {1} *</CommandToExtract> </ArchiverDllInfo> <ArchiverDllInfo> <FileName>unzip32</FileName> <FunctionName>UnZip</FunctionName> <CommandToExtract>-x {0} {1} *</CommandToExtract> </ArchiverDllInfo> <ArchiverDllInfo> <FileName>cab32</FileName> <FunctionName>Cab</FunctionName> <CommandToExtract>-x {0} {1} *</CommandToExtract> </ArchiverDllInfo> </ArrayOfArchiverDllInfo>
次の例はコンソールアプリです。コマンドライン引数に展開したいファイルを指定すると、「デスクトップ + 書庫ファイルの名前」というフォルダに展開します。
'C#のコードを'C# to VB.NET Translator'で変換し、修正したコードです 'http://www.aspalliance.com/aldotnet/examples/translate.aspx Imports System Imports System.Runtime.InteropServices Namespace Dobon.Sample.File Public Class ExtractArchiveWithDll Public Shared Sub Main(ByVal args() As String) Dim arg As String For Each arg In args '展開先のフォルダ名を決める 'ここではデスクトップ上の書庫ファイル名のフォルダとする Dim extractDir As String = _ System.Environment.GetFolderPath( _ System.Environment.SpecialFolder.DesktopDirectory) extractDir += "\" + _ System.IO.Path.GetFileNameWithoutExtension(arg) '存在しないフォルダ名を探す If System.IO.Directory.Exists(extractDir) Then Dim n As Integer = 1 While System.IO.Directory.Exists( _ extractDir + n.ToString()) n += 1 End While extractDir += n.ToString() End If Console.WriteLine("""{0}""を""{1}""に展開します...", _ arg, extractDir) '展開する Try If ExtractArchiveEx(arg, extractDir) Then Console.WriteLine("展開しました。") 'フォルダを開く System.Diagnostics.Process.Start(extractDir) Else Console.WriteLine("展開できませんでした。") End If Catch ex As Exception Console.WriteLine(("エラー:" + ex.Message)) End Try Next arg Console.ReadLine() End Sub 'DLLモジュールをマップ <DllImport("kernel32")> _ Private Shared Function LoadLibrary( _ ByVal lpLibFileName As String) As Integer End Function 'マップを解除 <DllImport("kernel32")> _ Private Shared Function FreeLibrary( _ ByVal hLibModule As Integer) As Boolean End Function '関数のアドレスを取得 <DllImport("kernel32")> _ Private Shared Function GetProcAddress( _ ByVal hModule As Integer, _ ByVal lpProcName As String) As Integer End Function '以下使用するAPIのためのInvokeFunc <DllImport("Invoke", EntryPoint:="InvokeFunc")> _ Private Shared Function InvokeGetVersion( _ ByVal funcptr As Integer) As UInt16 End Function <DllImport("Invoke", EntryPoint:="InvokeFunc")> _ Private Shared Function InvokeGetRunning( _ ByVal funcptr As Integer) As Boolean End Function <DllImport("Invoke", EntryPoint:="InvokeFunc")> _ Private Shared Function InvokeCheckArchive( _ ByVal funcptr As Integer, _ ByVal szFileName As String, _ ByVal iMode As Integer) As Boolean End Function <DllImport("Invoke", EntryPoint:="InvokeFunc")> _ Private Shared Function InvokeMain( _ ByVal funcptr As Integer, _ ByVal hwnd As Integer, _ ByVal szCmdLine As String, _ ByVal szOutput As String, _ ByVal dwSize As Integer) As Integer End Function 'DLLの情報 Public Structure ArchiverDllInfo Public FileName As String 'DLLファイル名 Public FunctionName As String 'APIの頭に付く文字列 Public CommandToExtract As String '展開のためのコマンド End Structure 'ArchiverDllInfo ''' <summary> ''' 統合アーカイバ仕様のDLLで書庫を展開する ''' </summary> ''' <param name="archiveFile">書庫ファイル名</param> ''' <param name="extractDir">展開先のフォルダ名</param> ''' <returns>展開できたらTrue</returns> Public Shared Function ExtractArchiveEx( _ ByVal archiveFile As String, _ ByVal extractDir As String) As Boolean '指定されたファイルがあるか調べる If Not System.IO.File.Exists(archiveFile) Then Throw New ApplicationException("ファイルが見つかりません") End If 'DLL情報ファイルの存在を確認 Dim dllInfoFile As String = GetAppPath() + "\dlls.config" If Not System.IO.File.Exists(dllInfoFile) Then Throw New ApplicationException( _ "DLL情報ファイルが見つかりません") End If 'DLLの情報を読み込む Dim serializer As _ New System.Xml.Serialization.XmlSerializer( _ GetType(ArchiverDllInfo())) Dim sr As New System.IO.StreamReader( _ fileName, System.Text.Encoding.UTF8) Dim dllInfos() As ArchiverDllInfo dllInfos = CType(serializer.Deserialize(sr), _ ArchiverDllInfo()) sr.Close() If dllInfos Is Nothing Or dllInfos.Length = 0 Then Throw New ApplicationException( _ "DLL情報が読み込めませんでした") End If Dim di As ArchiverDllInfo For Each di In dllInfos Dim dllName As String = di.FileName Dim funcName As String = di.FunctionName 'DLLをロード Dim hmod As Integer = LoadLibrary(dllName) If hmod = 0 Then GoTo ContinueForEach1 End If Try Dim funcaddr As Integer 'DLLのチェック '関数のアドレスを取得 funcaddr = GetProcAddress(hmod, _ funcName + "GetVersion") If funcaddr = 0 Then GoTo ContinueForEach1 End If Dim ver As UInt16 = InvokeGetVersion(funcaddr) '展開できるかチェック funcaddr = GetProcAddress(hmod, _ funcName + "CheckArchive") If funcaddr = 0 Then GoTo ContinueForEach1 End If If Not InvokeCheckArchive(funcaddr, _ archiveFile, 0) Then GoTo ContinueForEach1 End If Console.WriteLine("対応DLLは{0}です", dllName) '動作中かチェック funcaddr = GetProcAddress(hmod, _ funcName + "GetRunning") If funcaddr = 0 Then GoTo ContinueForEach1 End If If InvokeGetRunning(funcaddr) Then Throw New ApplicationException( _ dllName + "が動作中です") End If 'ファイル名とフォルダ名を修正する If archiveFile.IndexOf(" "c) > 0 Then archiveFile = """" + archiveFile + """" End If If Not extractDir.EndsWith("\") Then extractDir += "\" End If If extractDir.IndexOf(" "c) > 0 Then extractDir = """" + extractDir + """" End If '展開する funcaddr = GetProcAddress(hmod, funcName) If funcaddr = 0 Then Throw New ApplicationException( _ funcName + "のアドレスが取得できませんでした") End If Dim ret As Integer = InvokeMain(funcaddr, 0, _ String.Format(di.CommandToExtract, archiveFile, _ extractDir), Nothing, 0) '結果 If ret <> 0 Then Throw New ApplicationException( _ dllName + "での書庫の展開に失敗しました") Else Return True End If Finally '解放する If hmod <> 0 Then FreeLibrary(hmod) End If End Try ContinueForEach1: Next di Return False End Function Private Shared Function GetAppPath() As String Dim fi As New System.IO.FileInfo( _ System.Reflection.Assembly.GetExecutingAssembly().Location) Return fi.DirectoryName End Function End Class End Namespace
using System; using System.Runtime.InteropServices; namespace Dobon.Sample.File { public class ExtractArchiveWithDll { public static void Main(string[] args) { foreach (string arg in args) { //展開先のフォルダ名を決める //ここではデスクトップ上の書庫ファイル名のフォルダとする string extractDir = System.Environment.GetFolderPath( System.Environment.SpecialFolder.DesktopDirectory); extractDir += "\\" + System.IO.Path.GetFileNameWithoutExtension(arg); //存在しないフォルダ名を探す if (System.IO.Directory.Exists(extractDir)) { int n = 0; while (System.IO.Directory.Exists(extractDir + (++n).ToString())); extractDir += n.ToString(); } Console.WriteLine("\"{0}\"を\"{1}\"に展開します...", arg, extractDir); //展開する try { if (ExtractArchiveEx(arg, extractDir)) { Console.WriteLine("展開しました。"); //フォルダを開く System.Diagnostics.Process.Start(extractDir); } else Console.WriteLine("展開できませんでした。"); } catch (Exception ex) { Console.WriteLine("エラー:" + ex.Message); } } Console.ReadLine(); } //DLLモジュールをマップ [DllImport("kernel32")] private extern static int LoadLibrary(string lpLibFileName); //マップを解除 [DllImport("kernel32")] private extern static bool FreeLibrary(int hLibModule); //関数のアドレスを取得 [DllImport("kernel32")] private extern static int GetProcAddress( int hModule, string lpProcName); //以下使用するAPIのためのInvokeFunc [DllImport("Invoke", EntryPoint="InvokeFunc")] private extern static UInt16 InvokeGetVersion(int funcptr); [DllImport("Invoke", EntryPoint="InvokeFunc")] private extern static bool InvokeGetRunning(int funcptr); [DllImport("Invoke", EntryPoint="InvokeFunc")] private extern static bool InvokeCheckArchive( int funcptr, string szFileName, int iMode); [DllImport("Invoke", EntryPoint="InvokeFunc")] private extern static int InvokeMain( int funcptr, int hwnd, string szCmdLine, string szOutput, int dwSize); //DLLの情報 public struct ArchiverDllInfo { public string FileName; //DLLファイル名 public string FunctionName; //APIの頭に付く文字列 public string CommandToExtract; //展開のためのコマンド } /// <summary> /// 統合アーカイバ仕様のDLLで書庫を展開する /// </summary> /// <param name="archiveFile">書庫ファイル名</param> /// <param name="extractDir">展開先のフォルダ名</param> /// <returns>展開できたらTrue</returns> public static bool ExtractArchiveEx( string archiveFile, string extractDir) { //指定されたファイルがあるか調べる if (!System.IO.File.Exists(archiveFile)) throw new ApplicationException( "ファイルが見つかりません"); //DLL情報ファイルの存在を確認 string dllInfoFile = GetAppPath() + "\\dlls.config"; if (!System.IO.File.Exists(dllInfoFile)) throw new ApplicationException( "DLL情報ファイルが見つかりません"); //DLLの情報を読み込む System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer( typeof(ArchiverDllInfo[])); System.IO.StreamReader sr = new System.IO.StreamReader( fileName, System.Text.Encoding.UTF8); ArchiverDllInfo[] dllInfos; dllInfos = (ArchiverDllInfo[]) serializer.Deserialize(sr); sr.Close(); if (dllInfos == null || dllInfos.Length == 0) throw new ApplicationException( "DLL情報が読み込めませんでした"); foreach (ArchiverDllInfo di in dllInfos) { string dllName = di.FileName; string funcName = di.FunctionName; //DLLをロード int hmod = LoadLibrary(dllName); if (hmod == 0) continue; try { int funcaddr; //DLLのチェック //関数のアドレスを取得 funcaddr = GetProcAddress( hmod, funcName + "GetVersion"); if (funcaddr == 0) continue; UInt16 ver = InvokeGetVersion(funcaddr); if (ver == 0) continue; //展開できるかチェック funcaddr = GetProcAddress( hmod, funcName + "CheckArchive"); if (funcaddr == 0) continue; if (!InvokeCheckArchive(funcaddr, archiveFile, 0)) continue; Console.WriteLine("対応DLLは{0}です", dllName); //動作中かチェック funcaddr = GetProcAddress( hmod, funcName + "GetRunning"); if (funcaddr == 0) continue; if (InvokeGetRunning(funcaddr)) throw new ApplicationException( dllName + "が動作中です"); //ファイル名とフォルダ名を修正する if (archiveFile.IndexOf(' ') > 0) archiveFile = "\"" + archiveFile + "\""; if (!extractDir.EndsWith("\\")) extractDir += "\\"; if (extractDir.IndexOf(' ') > 0) extractDir = "\"" + extractDir + "\""; //展開する funcaddr = GetProcAddress(hmod, funcName); if (funcaddr == 0) throw new ApplicationException( funcName + "のアドレスが取得できませんでした"); int ret = InvokeMain(funcaddr, 0, string.Format(di.CommandToExtract, archiveFile, extractDir), null, 0); //結果 if (ret != 0) throw new ApplicationException( dllName + "での書庫の展開に失敗しました"); else return true; } finally { //解放する if (hmod != 0) FreeLibrary(hmod); } } return false; } private static string GetAppPath() { System.IO.FileInfo fi = new System.IO.FileInfo( System.Reflection.Assembly.GetExecutingAssembly().Location); return fi.DirectoryName; } } }
(この記事はメールマガジン「.NETプログラミング研究」の第16号で紹介したものを書き直したものです。)