.NETプログラミング研究 第16号 †
ピンポイントリンク †
遅延バインディングによりアンマネージDLL関数を呼び出す †
ここでは.NETで外部のDLLの関数(アンマネージDLL関数)を呼び出す方法を考えます。しかもここでの最終目標は遅延バインディングによる方法です。その具体例として、MiccoさんのUNLHA32.DLLを使って書庫を展開(解凍)するコードを書いてみることにします。最終的には「統合アーカイバプロジェクト」に対応したあらゆるDLLを使って書庫を展開できるアプリケーションを作成することを目的とします。
Unlha32.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の知識は身に付いたものとしましょう。
DllImportによるアンマネージDLL関数の呼び出し †
.NETでアンマネージDLL関数を呼び出す方法としては、DllImportを使う他ないでしょう(VB.NETのDeclareステートメントも結局は同じことです)。
ここではこの方法を紹介することが目的ではないため、DllImportに関する説明いたしません。これらに関する知識はヘルプ(下に参考になりそうなURLを紹介しておきます)でご確認ください。
以降、これらの知識がすでにあるものとして話を進めさせていただきます。
書庫を展開する †
以上の知識を基に、いよいよ実際に書庫を展開するコードを書いてみましょう。ここではUNLHA32.DLLで書庫を展開するためのメソッドを作ることにします。このメソッド(LhaExtractArchiveとしましょう)に書庫ファイル名と展開先のフォルダ名を渡すことにより、書庫が展開できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
| |
<DllImport("unlha32")> _
Private Shared Function UnlhaGetVersion() As UInt16
End Function
<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
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
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
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
| |
[DllImport("unlha32")]
private extern static UInt16 UnlhaGetVersion();
[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);
public static void LhaExtractArchive(
string archiveFile, string extractDir)
{
if (!System.IO.File.Exists(archiveFile))
throw new ApplicationException("ファイルが見つかりません");
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を作成します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
| |
<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
<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
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
Dim hmod As Integer = LoadLibrary(dllName)
If hmod = 0 Then
Throw New ApplicationException( _
dllName + "のロードに失敗しました")
End If
Try
Dim funcaddr As Integer
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
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
| |
[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);
[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);
public static void ExtractArchive(string dllName, string funcName,
string command, string archiveFile, string extractDir)
{
if (!System.IO.File.Exists(archiveFile))
throw new ApplicationException("ファイルが見つかりません");
int hmod = LoadLibrary(dllName);
if (hmod == 0)
throw new ApplicationException(dllName + "のロードに失敗しました");
try
{
int funcaddr;
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"という名前で保存されているものとします。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| <?xml version="1.0"?>
<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>
|
次の例はコンソールアプリです。コマンドライン引数に展開したいファイルを指定すると、「デスクトップ + 書庫ファイルの名前」というフォルダに展開します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
| | 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
<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
<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
Public Structure ArchiverDllInfo
Public FileName As String Public FunctionName As String Public CommandToExtract As String End Structure
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
Dim dllInfoFile As String = GetAppPath() + "\dlls.config"
If Not System.IO.File.Exists(dllInfoFile) Then
Throw New ApplicationException( _
"DLL情報ファイルが見つかりません")
End If
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
Dim hmod As Integer = LoadLibrary(dllName)
If hmod = 0 Then
GoTo ContinueForEach1
End If
Try
Dim funcaddr As Integer
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
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
| | 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.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;
}
}
}
|
今回は予定外の大作になってしまいましたが、遅延バインディングの面白さが分かっていただけたでしょうか?
コメント †
|