SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

VBAでの関数ポインタの利用方法

cdecl呼び出し規約のDLL関数をVBAから利用する方法

ANSI(≒SJIS)文字列を用いる関数を実行する例

 つぎに、VBAのString型と親和性の低いwsprintfA関数で試してみます。文字列バッファはすべてANSI文字列(≒SJIS文字列)である必要があるため、Unicode文字列バッファを持つString型を処理するには、コード上で明示的にUnicodeとANSIの相互変換を行う必要があります。

 これは本質的にはDispCallFuncとは別の話ですが、cdecl呼び出し体系を使うケースではANSI文字列体系を使ったレガシーなモジュールとのやり取りが多いと予想されるので、ここで取り上げておきます。

 なお、VBAのDeclare Functionでは、As Stringとした場合に、この変換を自動的に行ってくれています。

Option Explicit

Private Declare Function LoadLibrary Lib "kernel32.dll" Alias "LoadLibraryA" _
    (ByVal lpFileName As String) As Long

Private Declare Function GetProcAddress Lib "kernel32.dll" _
    (ByVal hModule As Long, ByVal lpProcName As String) As Long

Private Declare Function FreeLibrary Lib "kernel32.dll" _
    (ByVal hModule As Long) As Long

Private Declare Function DispCallFunc Lib "OleAut32.dll" _
    (ByVal pvInstance As Long, _
        ByVal oVft As Long, _
        ByVal cc As Long, _
        ByVal vtReturn As Integer, _
        ByVal cActuals As Long, _
        ByVal prgvt As Long, _
        ByVal prgpvarg As Long, _
        ByVal pvargResult As Long) As Long
   
Enum tagCALLCONV
    CC_FASTCALL = 0
    CC_CDECL = 1
    CC_MSCPASCAL = CC_CDECL + 1
    CC_PASCAL = CC_MSCPASCAL
    CC_MACPASCAL = CC_PASCAL + 1
    CC_STDCALL = CC_MACPASCAL + 1
    CC_FPFASTCALL = CC_STDCALL + 1
    CC_SYSCALL = CC_FPFASTCALL + 1
    CC_MPWCDECL = CC_SYSCALL + 1
    CC_MPWPASCAL = CC_MPWCDECL + 1
    CC_MAX = CC_MPWPASCAL
End Enum


Public Sub Test4()
    'Test1,2,3で説明した注釈は省いてあります。
    
    Dim lDispCallFuncResult As Long
    Dim vFuncResult As Variant

    vFuncResult = Empty
    
    Dim lLibraryHandle As Long
    lLibraryHandle = LoadLibrary("user32.dll")
    If lLibraryHandle = 0 Then
        Debug.Print "LoadLibrary 失敗"
        Exit Sub
    End If
    
    Dim lProcAddress As Long
    'Test3と異なり、wsprintfA関数へのポインタを保持します。
    lProcAddress = GetProcAddress(lLibraryHandle, "wsprintfA")
    If lProcAddress = 0 Then
       FreeLibrary lLibraryHandle
       Debug.Print "GetProcAddress 失敗"
       Exit Sub
    End If
    
    'wsprintfA は 次の引数を持っています。
    'LPTSTR lpOut , LPCTSTR lpFmt , ...
    
    'wsprintfは、C言語のsprintfとほぼ同等の働きをします。
    
    '第1引数に整形後の文字列を格納する文字列バッファ
    '第2引数に整形したい書式文字列
    '第3引数以降に埋め込みたい文字列なり数値なり
    
    '今回は "Param1 = %d , Param2 = %s"という書式で数値(%d)と文字列(%s)を埋め込みます。
    
    Dim sOut As String
    Dim bOut() As Byte 'sOutをANSIに変換した結果を保持させます。
    Dim sFormat As String
    Dim bFormat() As Byte 'sFormatをANSIに変換した結果を保持させます。
    Dim lParam1 As Long
    Dim sParam2 As String
    Dim bParam2() As Byte 'sParam2をANSIに変換した結果を保持させます。
    
    sOut = String(200, vbNullChar) 'Unicode200文字文の領域を確保し、\0で埋めます。(=400バイト)
    bOut = StrConv(sOut, vbFromUnicode)  'sOutをANSIに変換した結果を保持させます。
    
    sFormat = "Param1 = %d , Param2 = %s"
    bFormat = StrConv(sFormat, vbFromUnicode) 'sFormatをANSIに変換した結果を保持させます。
    
    lParam1 = 123456
    
    sParam2 = "abc"
    bParam2 = StrConv(sParam2, vbFromUnicode) 'sParam2をANSIに変換した結果を保持させます。
    
    '今回 wsprintfAには4つに引数を渡しますので、4つのVariant変数を用意します。
    '今回は、4つ別個に用意するのは手間なので、配列で用意します。
    Dim vParams(0 To 3) As Variant
        
    
    'wsprintfAでのLP(C)TSTRは 最終的には char* なので、StringをANSI文字列に変換してByte配列に
    '格納し、そのByte配列の先頭アドレスを渡す必要があります。
    vParams(0) = VarPtr(bOut(0))
    vParams(1) = VarPtr(bFormat(0))
    vParams(2) = lParam1
    vParams(3) = VarPtr(bParam2(0))
    
    Dim iVarTypes() As Integer
    Dim lVarPtrs() As Long
    
    ReDim iVarTypes(0 To UBound(vParams))
    ReDim lVarPtrs(0 To UBound(vParams))

    Dim i As Long
    For i = 0 To UBound(vParams)
        iVarTypes(i) = VarType(vParams(i))
        lVarPtrs(i) = VarPtr(vParams(i))
    Next
    
    'wsprintfA関数の呼び出し規約はcdeclなので、第3引数にCC_CDECLを指定します。
    
    '今回はVC++でのint型=VBでのLong型が戻り値なので
    'vbLong(=3)を戻り値の型として指定します。
        
    lDispCallFuncResult = DispCallFunc(0, lProcAddress, _
                                       tagCALLCONV.CC_CDECL, VbVarType.vbLong, _
                                        UBound(vParams) + 1, VarPtr(iVarTypes(0)), VarPtr(lVarPtrs(0)), VarPtr(vFuncResult))

    
    FreeLibrary lLibraryHandle
    
    Debug.Print "Test4"
    Debug.Print "lDispCallFuncResult = " & lDispCallFuncResult
    
    Debug.Print "vFuncResult = " & vFuncResult
    
    '第1引数にbOutのANSI文字列バッファのアドレスを指定したので
    'wsprintfA APIは、200文字のChrA(0)で埋められたbOutの先頭部分をnull終端文字列で上書きします。
    '従って、今度はbOutをANSIからUnicodeに変換してVBA側で扱えるようにする必要があります。
    'さらに、VB側で欲しい文字列は、最初に見つかったChrW(0)(=vbNullChar)より左側の文字列です。
    sOut = StrConv(bOut, vbUnicode)
    Debug.Print Left(sOut, InStr(1, sOut, vbNullChar) - 1)
End Sub

最後に

 この方法でdllを利用する大きな利点は、declare functionで不可能な、cdecl呼び出し規約の関数呼び出しが可能になることと、動的なdll名の決定が可能になることです。実際にこの技術が必要になるケースはあまり多くはないと思いますが、VBA開発者として、「VBAはCと比べて~ができない」とC言語経験者から言われるのが精神的苦痛であったりしませんか? そのときの反論の一手段としても、本稿をご活用いただければと思います。

 なお、VBでもParamArrayというキーワードを使うと、関数に可変個引数を設定することができます。これと上記のサンプルコードを組み合わせると、wsprintfの完全なラッパー関数を作ることができます。これは皆さん自身でコーディングしてみてください。

この記事は参考になりましたか?

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
特集記事連載記事一覧

もっと読む

この記事の著者

山城 章仁(ヤマシロ アキヒト)

新入社員の頃、ホストコンピュータでのCOBOLプログラムの開発を行う部署に配属され、そのときの先輩社員の方に「Excelのマクロはすごいぞ。あれを覚えたら、あれだけで食っていけるぞ」と言われたのをきっかけに、Excelのマクロに傾倒し、Excelのマクロでの不可能をなくすためにWindowsAPIに...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/6780 2012/10/05 14:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー