┏第16号━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃         .NETプログラミング研究         ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ 〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜メニュー ■ピンポイントリンク ・遅延バインディングによりアンマネージDLL関数を呼び出す 〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜 ─────────────────────────────── ■ピンポイントリンク ─────────────────────────────── ●遅延バインディングによりアンマネージDLL関数を呼び出す ここでは.NETで外部のDLLの関数(アンマネージDLL関数)を呼び出す 方法を考えます。しかもここでの最終目標は遅延バインディングによ る方法です。その具体例として、MiccoさんのUNLHA32.DLLを使っ て書庫を展開(解凍)するコードを書いてみることにします。最終的 には「統合アーカイバプロジェクト」に対応したあらゆるDLLを使って 書庫を展開できるアプリケーションを作成することを目標とします。 ・統合アーカイバプロジェクト http://www.madobe.net/archiver/index.html ・Micco's HomePage http://www2.nsknet.or.jp/~micco/micindex.html ★Unlha32.dllを使って書庫を展開する方法 DLL関数の呼び出し方の説明は後にして、まずはUnlha32.dllを使って 書庫を展開する方法をごく簡単に説明します。 Unlha32.dllはMiccoさん作の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の知識は身に付いたものとしま しょう。 ★DllImportによるアンマネージDLL関数の呼び出し .NETでアンマネージDLL関数を呼び出す方法としては、DllImportを使 う他ないでしょう(VB.NETのDeclareステートメントも結局は同じこと です)。 この記事はその方法を紹介することが目的ではないため、DllImportに 関する説明はいたしません。これらに関する知識はヘルプ(下に参考 になりそうなURLを紹介しておきます)でご確認ください。 ・アンマネージ DLL 関数の処理 http://www.microsoft.com/japan/msdn/library/ja/cpguide/html/cpconconsumingunmanageddllfunctions.asp ・プラットフォーム呼び出しによるデータのマーシャリング http://www.microsoft.com/japan/msdn/library/ja/cpguide/html/cpconmarshalingdatawithplatforminvoke.asp 以降、これらの知識がすでにあるものとして話を進めさせていただき ます。 ★書庫を展開する 以上の知識を基に、いよいよ実際に書庫を展開するコードを書いてみ ましょう。ここではUNLHA32.DLLで書庫を展開するためのメソッドを作 ることにします。このメソッド(LhaExtractArchiveとしましょう)に 書庫ファイル名と展開先のフォルダ名を渡すことにより、書庫が展開 できます。 '[VB.NET]・・・・・・・・・・・・・・・・・・・・・・・・・・ 'Imports System.Runtime.InteropServices 'が宣言されているものとする 'DLL の版の取得 _ Private Shared Function UnlhaGetVersion() As UInt16 End Function 'DLL の実行状況の取得 _ Private Shared Function UnlhaGetRunning() As Boolean End Function '書庫のチェック _ Private Shared Function UnlhaCheckArchive( _ ByVal szFileName As String, _ ByVal iMode As Integer) As Boolean End Function '書庫操作一般 _ Private Shared Function Unlha( _ ByVal hwnd As Integer, _ ByVal szCmdLine As String, _ ByVal szOutput As String, _ ByVal dwSize As Integer) As Integer End Function '/ '/ UNLHA32.DLLで書庫を展開する '/ '/ 書庫ファイル名 '/ 展開先のフォルダ名 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 '・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・ //[C#]・・・・・・・・・・・・・・・・・・・・・・・・・・・・ //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); /// /// UNLHA32.DLLで書庫を展開する /// /// 書庫ファイル名 /// 展開先のフォルダ名 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名 の"Unlha"の部分を"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」をご覧ください。) ・Visual C++ の概念: 機能の追加 - DLL http://www.microsoft.com/japan/msdn/library/ja/vccore/html/_core_DLL_Topics.asp .NETでも同じことが出来ればよいのですが、果たして可能でしょうか? ★遅延バインディングによる方法 その答えが、Richard Birkbyさんが書いた「The Code Project - Late binding on native DLLs with C#」にあります。 ・The Code Project - Late binding on native DLLs with C# http://www.codeproject.com/csharp/dyninvok.asp 詳しくはこのページを読んでいただきたいのですが、つまりは次のよ うなことのようです。(英語に自信がないので、正確かどうかわかり ません。) 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を作成し ます。もちろんこのコードはInvoke.dllがなければ動きません。 '[VB.NET]・・・・・・・・・・・・・・・・・・・・・・・・・・ 'Imports System.Runtime.InteropServices 'が宣言されているものとする 'DLLモジュールをマップ _ Private Shared Function LoadLibrary( _ ByVal lpLibFileName As String) As Integer End Function 'マップを解除 _ Private Shared Function FreeLibrary( _ ByVal hLibModule As Integer) As Boolean End Function '関数のアドレスを取得 _ Private Shared Function GetProcAddress( _ ByVal hModule As Integer, ByVal lpProcName As String) As Integer End Function '以下使用するAPIのためのInvokeFunc _ Private Shared Function InvokeGetVersion( _ ByVal funcptr As Integer) As UInt16 End Function _ Private Shared Function InvokeGetRunning( _ ByVal funcptr As Integer) As Boolean End Function _ Private Shared Function InvokeCheckArchive( _ ByVal funcptr As Integer, _ ByVal szFileName As String, _ ByVal iMode As Integer) As Boolean End Function _ 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で書庫を展開する '/ '/ DLLファイル名 '/ APIの頭に付く文字列 '/ 展開のためのコマンド '/ 書庫ファイル名 '/ 展開先のフォルダ名 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 '・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・ //[C#]・・・・・・・・・・・・・・・・・・・・・・・・・・・・ //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); /// /// 統合アーカイバ仕様のDLLで書庫を展開する /// /// DLLファイル名 /// APIの頭に付く文字列 /// 展開のためのコマンド /// 書庫ファイル名 /// 展開先のフォルダ名 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"という名前で保存されて いるものとします。 unlha32 Unlha x {0} {1} * unzip32 UnZip -x {0} {1} * cab32 Cab -x {0} {1} * 次の例はコンソールアプリです。コマンドライン引数に展開したいフ ァイルを指定すると、「デスクトップ + 書庫ファイルの名前」という フォルダに展開します。 '[VB.NET]・・・・・・・・・・・・・・・・・・・・・・・・・・ '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モジュールをマップ _ Private Shared Function LoadLibrary( _ ByVal lpLibFileName As String) As Integer End Function 'マップを解除 _ Private Shared Function FreeLibrary( _ ByVal hLibModule As Integer) As Boolean End Function '関数のアドレスを取得 _ Private Shared Function GetProcAddress( _ ByVal hModule As Integer, _ ByVal lpProcName As String) As Integer End Function '以下使用するAPIのためのInvokeFunc _ Private Shared Function InvokeGetVersion( _ ByVal funcptr As Integer) As UInt16 End Function _ Private Shared Function InvokeGetRunning( _ ByVal funcptr As Integer) As Boolean End Function _ Private Shared Function InvokeCheckArchive( _ ByVal funcptr As Integer, _ ByVal szFileName As String, _ ByVal iMode As Integer) As Boolean End Function _ 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 '/ '/ 統合アーカイバ仕様のDLLで書庫を展開する '/ '/ 書庫ファイル名 '/ 展開先のフォルダ名 '/ 展開できたらTrue 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 fs As New System.IO.FileStream(dllInfoFile, _ System.IO.FileMode.Open) Dim dllInfos() As ArchiverDllInfo dllInfos = CType(serializer.Deserialize(fs), _ ArchiverDllInfo()) 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 '・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・ //[C#]・・・・・・・・・・・・・・・・・・・・・・・・・・・・ 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; //展開のためのコマンド } /// /// 統合アーカイバ仕様のDLLで書庫を展開する /// /// 書庫ファイル名 /// 展開先のフォルダ名 /// 展開できたらTrue 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.FileStream fs = new System.IO.FileStream(dllInfoFile, System.IO.FileMode.Open); ArchiverDllInfo[] dllInfos; dllInfos = (ArchiverDllInfo[]) serializer.Deserialize(fs); 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; } } } //・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・ 今回は予定外の大作になってしまいましたが、遅延バインディングの 面白さが分かっていただけたでしょうか? =============================== ■このマガジンの購読、購読中止、バックナンバー、説明に関しては  次のページをご覧ください。  http://www.mag2.com/m/0000104516.htm ■発行人・編集人:どぼん!  http://dobon.net  dobon@bigfoot.com ■ご質問等はメールではなく、掲示板へお願いいたします。  http://dobon.net/bbs ■上記メールアドレスへのメールは確実に読まれる保障はありません  (スパム、ウィルス対策です)。メールは下記URLのフォームメール  から送信してください。  http://dobon.net/mail.html Copyright (c) 2003 DOBON! All rights reserved. ===============================