DOBON.NET DOBON.NETプログラミング掲示板過去ログ

遅延バインディングでDLLエントリポイントの名前を変数で指定する方法

環境/言語:[Windows XP,VB.NET(一部c/c++),NET Framework 4.0]
分類:[.NET]

dobon.netはよくお世話になっています。管理人さんを始め関係者の皆さんには
お礼申し上げます。
さて、質問という訳ではないのですが、『こんなことも.netでできますよ』
レベルの投稿としてご覧ください。dobon.netの
http://dobon.net/vb/dotnet/links/extractarchive.htmlには下記の記載があります。
『Win32 APIのLoadLibrary関数とGetProcAddress関数により指定された
DLLの関数のアドレスは取得できますが、このアドレスで関数に
アクセスする方法が.NETにはありません。』
しかし、以下のような記述をすれば.netでも任意の関数を呼出すことが
できると思います(下記内容でUnlha32.dllを使用したテストでは問題なく
ファイルの解凍ができることは確認しました。ただ、さすがに動的に
パラメータまで指定するのは苦しいですが・・)。
ソースプログラムは上記URLに記載されているvb版の『遅延バインディングに
よる方法』をもとに変更しています。
確認環境:
Windows XP SP3
Visual Studio 2010 Pro
.NET Framework 4.0

Imports System.Runtime.InteropServices
Public Class ClassVB
'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のためのFunc
Private Delegate Function GetVersion( _
) As UInt16

Private Delegate Function GetRunning() As Boolean

Private Delegate Function CheckArchive( _
ByVal szFileName As String, _
ByVal iMode As Integer) As Boolean

Private Delegate Function sMain( _
ByVal hwnd As Integer, _
ByVal szCmdLine As String, _
ByVal szOutput As String, _
ByVal dwSize As Integer) As Integer

''' <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 IntPtr
'DLLのチェック
'関数のアドレスを取得
funcaddr = GetProcAddress(hmod, funcName + "GetVersion")
If funcaddr = 0 Then
Throw New ApplicationException( _
dllName + "がインストールされていません")
End If
Dim d As [Delegate] = Marshal.GetDelegateForFunctionPointer(funcaddr, GetType(GetVersion))
Dim getVersion As GetVersion = CType(d, GetVersion)
Dim ver As UInt16 = getVersion()
Debug.WriteLine("バージョン:{0}", ver)

'動作中かチェック
funcaddr = GetProcAddress(hmod, funcName + "GetRunning")
If funcaddr = 0 Then
Throw New ApplicationException( _
funcName + "GetRunningのアドレスが取得できませんでした")
End If
d = Marshal.GetDelegateForFunctionPointer(funcaddr, GetType(GetRunning))
Dim getRunning As GetRunning = CType(d, GetRunning)
If getRunning() Then
Throw New ApplicationException(dllName + "が動作中")
End If

'展開できるかチェック
funcaddr = GetProcAddress(hmod, funcName + "CheckArchive")
If funcaddr = 0 Then
Throw New ApplicationException( _
funcName + "CheckArchiveのアドレスが取得できませんでした")
End If
d = Marshal.GetDelegateForFunctionPointer(funcaddr, GetType(CheckArchive))
Dim checkArchive As CheckArchive = CType(d, CheckArchive)
If Not checkArchive(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
d = Marshal.GetDelegateForFunctionPointer(funcaddr, GetType(sMain))
Dim subMain As sMain = CType(d, sMain)
Dim ret As Integer = subMain(0, _
String.Format(command, archiveFile, extractDir), _
Nothing, 0)

'結果
If ret <> 0 Then
Throw New ApplicationException("書庫の展開に失敗しました")
End If

Catch ex As Exception
MsgBox(ex.Message)
Finally
'解放する
If hmod <> 0 Then
FreeLibrary(hmod)
End If
End Try
End Sub

End Class

また、これとは別に先のURLではRichard Birkbyさんが作成したInvoke.dllを
使用していますが、これはVisualStudio c/c++の下記インラインアセンブラで
代用できます。
Invoke.asmではecxとedxのレジスタを直接使用していますが、その後該当
レジスタの復旧をしていないので誤動作につながると困るなと思い、少し
ロジックを追加しています。これでもeaxレジスタは変更されますが、
コンパイラにより通常eaxには呼出し先関数の戻り値が設定されるので
問題はないと思います。
(下記内容で作成したCall.dllとUnlha32.dllを使用したテストでは問題なく
ファイルの解凍ができることは確認しました。)

// Call.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"

struct stackImage {
void *svecx;
void *svebx;
void *ret;
void *ptr;
};

extern "C" void __declspec(naked) Invoke(void *ptr)
{
__asm{
push ebx
push ecx
mov ebx,esp

mov eax,[ebx].ptr ; get jump adress

mov ecx,[ebx].ret ; ret -> ptr
mov [ebx].ptr,ecx

mov ecx,[ebx].svebx ; svebx -> ret
mov [ebx].ret,ecx

mov ecx,[ebx].svecx ; svecx -> svebx
mov [ebx].svebx,ecx

pop ecx ; dummy pop
pop ecx ; restore ecx
pop ebx ; restore ebx
jmp eax ; jump real module

pop ecx ; save return address
pop edx ; Get function pointer
push ecx ; Restore return address
jmp edx ; Transfer control to the function pointer
}
}

(処理には関係しませんがアセンブラの最後4ステップはRichard Birkbyさんの
オリジナルです)
お知らせいただき、ありがとうございました。

遅延バインディングによりアンマネージDLL関数を呼び出す
http://dobon.net/vb/dotnet/links/extractarchive.html

のコメントの方にも書かせていただきます。

また何かございましたら、よろしくお願いいたします。
管理人さんご覧頂きコメントありがうございます。

> お知らせいただき、ありがとうございました。

いえ、たまたま、同様のことをした経験があっただけです。
また、コメントの記載あわせてお礼申し上げます。

dobon.netは今後もお世話になると思いますので、
よろしくお願い致します。
解決済み!

DOBON.NET | プログラミング道 | プログラミング掲示板