2021年7月アーカイブ

VisualBasicとDLLに実装されたアンマネージド関数との間で、構造体データの受け渡しを試して見ようと、GetSystemInfo関数を呼び出すだけのサンプルを作ってみました(GetNativeSystemInfo関数も同様)。

マイクロソフトのドキュメントによれば、SYSTEM_INFOの構文は、

typedef struct _SYSTEM_INFO {
    union {
        DWORD dwOemId;
        struct {
            WORD wProcessorArchitecture;
            WORD wReserved;
        } DUMMYSTRUCTNAME;
    } DUMMYUNIONNAME;
    DWORD     dwPageSize;
    LPVOID    lpMinimumApplicationAddress;
    LPVOID    lpMaximumApplicationAddress;
    DWORD_PTR dwActiveProcessorMask;
    DWORD     dwNumberOfProcessors;
    DWORD     dwProcessorType;
    DWORD     dwAllocationGranularity;
    WORD      wProcessorLevel;
    WORD      wProcessorRevision;
} SYSTEM_INFO, *LPSYSTEM_INFO;

なので、構造体メンバーの変数型を UShort と UInteger に置き換えれば、上手くゆく筈?


★それにしても、凡そ20年前の WindowsXP と ペンティアム4、現役バリバリで良く頑張っているなぁ。


<注意点>

・Win32 ではC/C++のポインタサイズは4バイト、ポインタ精度変数のサイズも4バイト。つまり、Integer 型です。

・DWORD_PTR は Win32/Win64 でサイズが変化する事に留意。LPVOID は IntPtr の方が好ましい?

・Visual Basic のデフォルトの文字セットは、UNICODE です。


お試し環境
  WindowsXP 32bit Edition
  Visual Basic 2008


/*-------------------------------- お試し結果 ------------------------------*/

・GetSystemInfo 関数の取得データ

取得SYSTEM_INFOデータ


・GetNativeSystemInfo 関数の取得データ

取得SYSTEM_INFO2データ

/*----------------------------------------------------------------------------*/


/*-------------------------------- お試しソース ----------------------------*/

Imports System.Runtime.InteropServices

Public Class Form1

    Const PROCESSOR_ARCHITECTURE_INTEL As Integer = 0 ' x86
    Const PROCESSOR_ARCHITECTURE_ARM As Integer = 5 ' ARM
    Const PROCESSOR_ARCHITECTURE_IA64 As Integer = 6 ' Intel Itanium-based
    Const PROCESSOR_ARCHITECTURE_AMD64 As Integer = 9 ' x64(AMD or Intel)
    Const PROCESSOR_ARCHITECTURE_ARM64 As Integer = 12 ' ARM64
    Const PROCESSOR_ARCHITECTURE_UNKNOWN As Integer = &HFFFF ' Unknown architecture

    Const PROCESSOR_INTEL_386 As Integer = 386
    Const PROCESSOR_INTEL_486 As Integer = 486
    Const PROCESSOR_INTEL_PENTIUM As Integer = 586
    Const PROCESSOR_INTEL_IA64 As Integer = 2200
    Const PROCESSOR_AMD_X8664 As Integer = 8664
    ' Const PROCESSOR_ARM As Integer = Reserved

    Structure SYSTEM_INFO
        Dim wProcessorArchitecture As UShort
        Dim wReserved As UShort
        Dim dwPageSize As UInteger
        Dim lpMinimumApplicationAddress As UInteger ' LPVOID
        Dim lpMaximumApplicationAddress As UInteger ' LPVOID
        Dim dwActiveProcessorMask As UInteger ' DWORD_PTR
        Dim dwNumberOfProcessors As UInteger
        Dim dwProcessorType As UInteger
        Dim dwAllocationGranularity As UInteger
        Dim wProcessorLevel As UShort
        Dim wProcessorRevision As UShort
    End Structure

    Structure SYSTEM_INFO2
        Dim wProcessorArchitecture As UShort
        Dim wReserved As UShort
        Dim dwPageSize As UInteger
        Dim lpMinimumApplicationAddress As IntPtr
        Dim lpMaximumApplicationAddress As IntPtr
        Dim dwActiveProcessorMask As UInteger ' DWOD_PTR
        Dim dwNumberOfProcessors As UInteger
        Dim dwProcessorType As UInteger
        Dim dwAllocationGranularity As UInteger
        Dim wProcessorLevel As UShort
        Dim wProcessorRevision As UShort
    End Structure

    Declare Auto Sub GetSystemInfo Lib "kernel32.dll" (ByRef sysInfo As SYSTEM_INFO)
    Declare Auto Sub GetNativeSystemInfo Lib "kernel32.dll" (ByRef sysInfo As SYSTEM_INFO2)

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        Dim sInfo As SYSTEM_INFO
        Dim sInfo2 As SYSTEM_INFO2
        Dim msg1, msg2 As String

        GetSystemInfo(sInfo)
        GetNativeSystemInfo(sInfo2)

        msg1 = "wProcessorArchitecture = " & sInfo.wProcessorArchitecture & vbCrLf & _
               "wReserved = " & sInfo.wReserved & vbCrLf & _
               "dwPageSize = " & sInfo.dwPageSize & vbCrLf & _
               "lpMinimumApplicationAddress = " & sInfo.lpMinimumApplicationAddress.ToString("x") & vbCrLf & _
               "lpMaximumApplicationAddress = " & sInfo.lpMaximumApplicationAddress.ToString("x") & vbCrLf & _
               "dwActiveProcessorMask = " & sInfo.dwActiveProcessorMask & vbCrLf & _
               "dwNumberOfProcessors = " & sInfo.dwNumberOfProcessors & vbCrLf & _
               "dwProcessorType = " & sInfo.dwProcessorType & vbCrLf & _
               "dwAllocationGranularity = " & sInfo.dwAllocationGranularity & vbCrLf & _
               "wProcessorLevel = " & sInfo.wProcessorLevel & vbCrLf & _
               "wProcessorRevision = " & sInfo.wProcessorRevision

        msg2 = "wProcessorArchitecture = " & sInfo2.wProcessorArchitecture & vbCrLf & _
               "wReserved = " & sInfo2.wReserved & vbCrLf & _
               "dwPageSize = " & sInfo2.dwPageSize & vbCrLf & _
               "lpMinimumApplicationAddress = " & sInfo2.lpMinimumApplicationAddress.ToString("x") & vbCrLf & _
               "lpMaximumApplicationAddress = " & sInfo2.lpMaximumApplicationAddress.ToString("x") & vbCrLf & _
               "dwActiveProcessorMask = " & sInfo2.dwActiveProcessorMask & vbCrLf & _
               "dwNumberOfProcessors = " & sInfo2.dwNumberOfProcessors & vbCrLf & _
               "dwProcessorType = " & sInfo2.dwProcessorType & vbCrLf & _
               "dwAllocationGranularity = " & sInfo2.dwAllocationGranularity & vbCrLf & _
               "wProcessorLevel = " & sInfo2.wProcessorLevel & vbCrLf & _
               "wProcessorRevision = " & sInfo2.wProcessorRevision

        MessageBox.Show(msg1, "sInfo members data")
        MessageBox.Show(msg2, "sInfo2 members data")

        Select Case sInfo.wProcessorArchitecture
            Case PROCESSOR_ARCHITECTURE_INTEL
                MessageBox.Show("32ビット です", "OSのバージョンは", MessageBoxButtons.OK, MessageBoxIcon.Information)
            Case PROCESSOR_ARCHITECTURE_ARM
            Case PROCESSOR_ARCHITECTURE_IA64
            Case PROCESSOR_ARCHITECTURE_AMD64
                MessageBox.Show("64ビット です", "OSのバージョンは", MessageBoxButtons.OK, MessageBoxIcon.Information)
            Case PROCESSOR_ARCHITECTURE_ARM64
            Case PROCESSOR_ARCHITECTURE_UNKNOWN
        End Select

        Select Case sInfo.dwProcessorType
            Case PROCESSOR_INTEL_386
            Case PROCESSOR_INTEL_486
            Case PROCESSOR_INTEL_PENTIUM
                MessageBox.Show("Pentium4 です", "CPUのタイプは", MessageBoxButtons.OK, MessageBoxIcon.Information)
            Case PROCESSOR_INTEL_IA64
            Case PROCESSOR_AMD_X8664
                MessageBox.Show("AMD Ryzen9 です", "CPUのタイプは", MessageBoxButtons.OK, MessageBoxIcon.Information)
        End Select
    End Sub
End Class

/*----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
/*============================================================================*/

CPUがx64だと言っても、まだまだx86の呼出し規則が優勢と言うかWOW64の方が全盛の内に、x86呼出し規則を浚ってみました。

C・C++間や他言語からのDLL内関数呼出しを行う場合、大事なのはCリンケージと __stdcall です。

特に指定しない限り、C++コンパイラはC++リンケージであるC++のタイプセーフな名前付け規約(名前装飾)と、C++の呼出し規則を使用します。

Cコンパイラは既定で、Cの呼出し規則とCリンケージを使います。

★C・C++間でDLL内の関数を利用するには、Cリンケージである extern "C" を指定して関数を宣言し、コンパイラがC++の関数名を装飾するのを禁止する必要があります。

★他言語からDLL内の関数を利用するには、__stdcall 呼出し規則を使用する必要があります。


<注意点>

・C++が装飾名を必須とするのは、オーバーロードされた関数、クラスや名前空間のメンバー、コンストラクターやデストラクター、などなどを一意に特定する必要がある事に起因します。

・__stdcall で修飾さる名前には、関数名の前にアンダースコア '_' が、後にアットマーク '@' が、更にその後に引数リストのバイト数が付けられます。例えば、int __stdcall func(int a, double b) 関数は、_func@12 と名前修飾されます。


【Visual C/C++ コンパイラの呼出し規則】

Visual C/C++ コンパイラの呼出し規則一覧
規約 *1コンパイラ
オプション
スタック
一掃
引数渡しCリンケージ
修飾形式 *2
補足
__cdecl /Gd
x86 のみ
呼出し元 右から左の順で
スタックに積まれる
_fname C/C++標準
__clrcall なし N/A 左から右の順で
CLR式スタックに収容
N/A .NET専用
__stdcall /Gz
x86 のみ
呼出し先 右から左の順で
スタックに積まれる
_fname@num Win32API呼出し
他言語アプリからの
関数呼び出し
__fastcall /Gr
x86 のみ
呼出し先 レジスタで渡され
残りは右から左の順で
スタックに積まれる
@fname@num 通常、高速呼出し
__thiscall なし 呼出し先 右から左の順でスタック
に積まれ this ポインタは
ECX レジスタに格納
N/A C++メンバー関数
__vectorcall /Gv
x86, x64
呼出し先 レジスタで渡され
残りは右から左の順で
スタックに積まれる
fname@@num __fastcall より多くの
レジスタで渡すか
既定の x64 呼出し *3
を使う

 *1:Visual C/C++ コンパイラでサポートされている呼び出し規約
 *2:fname は関数名、num は引数リストのバイト数を表す
 *3:x64ABIの既定では4レジスタ高速呼出しの呼び出し規則が使用される

前回に続いて、DLLに実装されたアンマネージド関数をマネージドコードから呼び出すことが出来る「プラットフォーム呼出し(PInvoke)」を使って、VBからWin32APIを呼び出してみます。

取り敢えず、DllImport 属性を使用して MessageBox を呼び出すだけのサンプルを作ってみました。

DllImport 属性でDLL内の名前を指定する事によって、引数と戻り値の相互運用マーシャリングも行われます。

ExactSpelling 項目は CharSet 項目の設定内容に影響を与えます。ExactSpelling を省略すると ExactSpelling:=False として扱われるよです。

ExactSpelling:=False の場合、CharSet の Auto、Ansi、Unicode 設定は柔軟に作用しますが、ExactSpelling:=True の場合は厳格に適用されます。

CharSet と EntryPoint と ExactSpelling 項目間の設定内容に不一致などがあると、メッセージボックス内で文字化けが起きたり、例外が発生したりします。


<注意点>

・DllImport はクラスのインスタンスを必要とするメソッドは使用できません。呼出しで共有(静的)メソッドを参照している場合にのみ、Windows API を呼び出せます。

・Visual Basic のデフォルトの文字セットは、UNICODE です。


お試し環境
  WindowsXP 32bit Edition、Windows7 64bit Edition
  Visual Basic 2008


/*-------------------------------- お試し結果 ------------------------------*/

・<DllImport("user32.dll", ... CharSet:=CharSet.Auto, EntryPoint:="MessageBox", ExactSpelling:=False, ...)> _ の場合

auto&false メッセージボックス表示


・<DllImport("user32.dll", ... CharSet:=CharSet.Ansi, EntryPoint:="MessageBox", ExactSpelling:=False, ...)> _ の場合

ansi&false メッセージボックス表示


・<DllImport("user32.dll", ... CharSet:=CharSet.Unicode, EntryPoint:="MessageBox", ExactSpelling:=False, ...)> _ の場合

unicode&false メッセージボックス表示


・<DllImport("user32.dll", ... CharSet:=CharSet.Auto, EntryPoint:="MessageBox", ExactSpelling:=True, ...)> _ の場合

auto&true-1 例外エラー表示


・<DllImport("user32.dll", ... CharSet:=CharSet.Ansi, EntryPoint:="MessageBox", ExactSpelling:=True, ...)> _ の場合

ansi&true-1 例外エラー表示


・<DllImport("user32.dll", ... CharSet:=CharSet.Unicode, EntryPoint:="MessageBox", ExactSpelling:=True, ...)> _ の場合

unicode&true-1 例外エラー表示


・<DllImport("user32.dll", ... CharSet:=CharSet.Auto, EntryPoint:="MessageBoxA", ExactSpelling:=False, ...)> _
・<DllImport("user32.dll", ... CharSet:=CharSet.Auto, EntryPoint:="MessageBoxA", ExactSpelling:=True, ...)> _ の場合

auto&false, true メッセージボックス文字化け表示


・<DllImport("user32.dll", ... CharSet:=CharSet.Ansi, EntryPoint:="MessageBoxW", ExactSpelling:=False, ...)> _
・<DllImport("user32.dll", ... CharSet:=CharSet.Ansi, EntryPoint:="MessageBoxW", ExactSpelling:=True, ...)> _ の場合

ansi&false, true メッセージボックス文字化け表示


・<DllImport("user32.dll", ... CharSet:=CharSet.Unicode, EntryPoint:="MessageBoxA", ExactSpelling:=False, ...)> _
・<DllImport("user32.dll", ... CharSet:=CharSet.Unicode, EntryPoint:="MessageBoxA", ExactSpelling:=True, ...)> _ の場合

unicode&false, true メッセージボックス文字化け表示


・<DllImport("user32.dll", ... CharSet:=CharSet.Auto, EntryPoint:="MessageBoxW", ExactSpelling:=True, ...)> _ の場合

auto&true-2 メッセージボックス表示


・<DllImport("user32.dll", ... CharSet:=CharSet.Ansi, EntryPoint:="MessageBoxA", ExactSpelling:=True, ...)> _ の場合

ansi&true-2 メッセージボックス表示


・<DllImport("user32.dll", ... CharSet:=CharSet.Unicode, EntryPoint:="MessageBoxW", ExactSpelling:=True, ...)> _ の場合

unicode&true-2 メッセージボックス表示


/*----------------------------------------------------------------------------*/


/*-------------------------------- お試しソース ----------------------------*/

Imports System.Runtime.InteropServices

Public Class Form1

    Const MB_ICONQUESTION As Integer = &H20
    Const MB_YESNO As Integer = &H4
    Const ID_YES As Integer = 6
    Const ID_NO As Integer = 7

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        ' Stores the return value.
        Dim RetVal As Integer

        Try
            RetVal = MBox5(0, "DllImport-exactspelling & unmanaged Windows API Test", "MBox5 charset auto", MB_ICONQUESTION Or MB_YESNO)

            ' Check the return value.
            If RetVal = ID_YES Then
                MsgBox("choice Yes", , "MBox5")
            Else
                MsgBox("choice No", , "MBox5")
            End If

        Catch ex As EntryPointNotFoundException
            MessageBox.Show(ex.Message, "MBox5の例外エラーを捕捉しました")
        Catch ex As Exception
            MessageBox.Show(ex.Message, "その他の例外エラーを捕捉しました")
        End Try

        Try
            RetVal = MBox6(0, "DllImport-exactspelling & unmanaged Windows API Test", "MBox6 charset ansi", MB_ICONQUESTION Or MB_YESNO)

            ' Check the return value.
            If RetVal = ID_YES Then
                MsgBox("choice Yes", , "MBox6")
            Else
                MsgBox("choice No", , "MBox6")
            End If

        Catch ex As EntryPointNotFoundException
            MessageBox.Show(ErrorToString(), "MBox6の例外エラーを捕捉しました")
        Catch ex As Exception
            MessageBox.Show(ErrorToString(), "その他の例外エラーを捕捉しました")
        End Try

        Try
            RetVal = MBox7(0, "DllImport-exactspelling & unmanaged Windows API Test", "MBox7 charset unicode", MB_ICONQUESTION Or MB_YESNO)

            ' Check the return value.
            If RetVal = ID_YES Then
                MsgBox("choice Yes", , "MBox7")
            Else
                MsgBox("choice No", , "MBox7")
            End If

        Catch ex As EntryPointNotFoundException
            MessageBox.Show(ex.Message, "MBox7の例外エラーを捕捉しました")
        Catch ex As Exception
            MessageBox.Show(ex.Message, "その他の例外エラーを捕捉しました")
        End Try

    End Sub

    <DllImport("user32.dll", CallingConvention:=CallingConvention.StdCall, CharSet:=CharSet.Auto, EntryPoint:="MessageBoxW", ExactSpelling:=True, SetLastError:=True)> _
    Public Shared Function MBox5(ByVal hWnd As Integer, ByVal text As String, ByVal caption As String, ByVal type As Integer) As Integer
    End Function

    <DllImport("user32.dll", CallingConvention:=CallingConvention.StdCall, CharSet:=CharSet.Ansi, EntryPoint:="MessageBoxA", ExactSpelling:=True, SetLastError:=True)> _
    Public Shared Function MBox6(ByVal hWnd As Integer, ByVal text As String, ByVal caption As String, ByVal type As Integer) As Integer
    End Function

    <DllImport("user32.dll", CallingConvention:=CallingConvention.StdCall, CharSet:=CharSet.Unicode, EntryPoint:="MessageBoxW", ExactSpelling:=True, SetLastError:=True)> _
    Public Shared Function MBox7(ByVal hWnd As Integer, ByVal text As String, ByVal caption As String, ByVal type As Integer) As Integer
    End Function

End Class

/*----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
/*============================================================================*/

Visual Basic.NET が登場してから早20年。.NETが提供する豊富な機能を利用する事で、Windows API を使用しなくても機能を実装することが出来るようになった、と登場時に言われてから久しいので今更ですが、VBからWin32APIを呼び出してみる事に。

Windows API は、WindowsOSの一部機能であるDLL(ダイナミック・リンク・ライブラリ)ですが、アンマネージド関数であるため、慎重さが必要です。

マイクロソフトのドキュメントによれば、

Windows API を使用する利点は、既に記述され、使用されるのを待っている便利な関数が多数含まれているため、開発時間を節約できる事です。欠点として、Windows API は処理が容易でなく、問題が発生した時に困難な状況に陥る事があります。

Windows API にマネージド・コードは使用されておらず、組み込みのタイプ・ライブラリはありません。また、使用するデータ型は Visual Studio で使用するものとは異なります。Windows API および .NET Framework との相互運用性は、プラットフォーム呼び出し(PInvoke)を使用して実現されます。Visual Basic で PInvoke を使用するには、Declare ステートメントを使用するか、DllImport 属性を「空のプロシージャ」に適用します。

Windows API 呼び出しは、過去においては Visual Basic プログラミングの重要な部分でしたが、Visual Basic .NET ではほとんど必要ありません。可能な限り、Windows API 呼び出しではなく .NET Framework のマネージド関数を使用してタスクを実行するようにして下さい。

の様です。

取り敢えず、Declare ステートメントを使用して MessageBox を呼び出すだけのサンプルを作ってみました。

文字セット修飾子が Auto の場合、関数名に自動的にWを補ってくれるようです。Ansi や Unicode の場合は、関数名にAまたはWが付加されている事を要求します。

Ansi 修飾子は全ての文字列を ANSI 値に、Unicode 修飾子は全ての文字列を UNICODE 値に、自動的にマーシャリングが行われます。

指定した文字型と関数のエントリポイントに不一致などがあると、メッセージボックス内で文字化けが起きたり、例外が発生したりします。


<注意点>

・Declare 宣言の文字セット修飾子の規定値(デフォルト)は、ANSI です。

・Declare 宣言時に Shared 修飾子は使用できませんが、暗黙的に Shared になります。

・Visual Basic のデフォルトの文字セットは、UNICODE です。


お試し環境
  WindowsXP 32bit Edition、Windows7 64bit Edition
  Visual Basic 2008


/*-------------------------------- お試し結果 ------------------------------*/

・Declare Auto Function MBox1 Lib "user32.dll" Alias "MessageBox" (arg1, ...) As Integer の場合

auto-1メッセージボックス表示


・Declare Ansi Function MBox2 Lib "user32.dll" Alias "MessageBox" (arg1, ...) As Integer の場合

ansi-1例外エラー表示


・Declare Unicode Function MBox3 Lib "user32.dll" Alias "MessageBox" (arg1, ...) As Integer の場合

unicode-1例外エラー表示


・Declare Auto Function MBox1 Lib "user32.dll" Alias "MessageBoxA" (arg1, ...) As Integer の場合

auto-2メッセージボックス文字化け表示


・Declare Ansi Function MBox2 Lib "user32.dll" Alias "MessageBoxW" (arg1, ...) As Integer の場合

ansi-2メッセージボックス文字化け表示


・Declare Unicode Function MBox3 Lib "user32.dll" Alias "MessageBoxA" (arg1, ...) As Integer の場合

unicode-2メッセージボックス文字化け表示


・Declare Auto Function MBox1 Lib "user32.dll" Alias "MessageBoxW" (arg1, ...) As Integer の場合

auto-3メッセージボックス表示


・Declare Ansi Function MBox2 Lib "user32.dll" Alias "MessageBoxA" (arg1, ...) As Integer の場合

ansi-3メッセージボックス表示


・Declare Unicode Function MBox3 Lib "user32.dll" Alias "MessageBoxW" (arg1, ...) As Integer の場合

unicode-3メッセージボックス表示


/*----------------------------------------------------------------------------*/


/*-------------------------------- お試しソース ----------------------------*/

Public Class Form1

    Const MB_ICONQUESTION As Integer = &H20
    Const MB_YESNO As Integer = &H4
    Const ID_YES As Integer = 6
    Const ID_NO As Integer = 7

    Declare Auto Function MBox1 Lib "user32.dll" Alias "MessageBox" (ByVal hWnd As Integer, ByVal text As String, ByVal caption As String, ByVal type As Integer) As Integer
    Declare Ansi Function MBox2 Lib "user32.dll" Alias "MessageBoxA" (ByVal hWnd As Integer, ByVal text As String, ByVal caption As String, ByVal type As Integer) As Integer
    Declare Unicode Function MBox3 Lib "user32.dll" Alias "MessageBoxW" (ByVal hWnd As Integer, ByVal text As String, ByVal caption As String, ByVal type As Integer) As Integer

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        ' Stores the return value.
        Dim RetVal As Integer

        RetVal = MBox1(0, "Declare-charsetmodifier & unmanaged Windows API Test", "MBox1 charset auto", MB_ICONQUESTION Or MB_YESNO)

        ' Check the return value.
        If RetVal = ID_YES Then
            MsgBox("choice Yes", , "MBox1")
        Else
            MsgBox("choice No", , "MBox1")
        End If

        Try
            RetVal = MBox2(0, "Declare-charsetmodifier & unmanaged Windows API Test", "MBox2 charset ansi", MB_ICONQUESTION Or MB_YESNO)

            ' Check the return value.
            If RetVal = ID_YES Then
                MsgBox("choice Yes", , "MBox2")
            Else
                MsgBox("choice No", , "MBox2")
            End If

        Catch ex As EntryPointNotFoundException
            MessageBox.Show(ex.Message, "MBox2の例外エラーを捕捉しました")
        Catch ex As Exception
            MessageBox.Show(ex.Message, "その他の例外エラーを捕捉しました")
        End Try

        Try
            RetVal = MBox3(0, "Declare-charsetmodifier & unmanaged Windows API Test", "MBox3 charset unicode", MB_ICONQUESTION Or MB_YESNO)

            ' Check the return value.
            If RetVal = ID_YES Then
                MsgBox("choice Yes", , "MBox3")
            Else
                MsgBox("choice No", , "MBox3")
            End If

        Catch ex As EntryPointNotFoundException
            MessageBox.Show(Err.Description(), "MBox3の例外エラーを捕捉しました")
        Catch ex As Exception
            MessageBox.Show(Err.Description(), "その他の例外エラーを捕捉しました")
        End Try

    End Sub
End Class

/*----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
/*============================================================================*/