' VB では可変引数を直接宣言できないため、引数を固定して宣言 ' arg0, arg1, arg2, …の数は、20個でも30個でも必要な分だけ定義する ' ここでは例として10個の引数をサポートする関数を宣言 <DllImport("msvcrt.dll", EntryPoint:="_snwprintf", CharSet:=CharSet.Unicode, CallingConvention:=CallingConvention.Cdecl)> Private Shared Function _snwprintf( ByVal buffer As StringBuilder, ByVal count As Integer, <MarshalAs(UnmanagedType.LPWStr)> ByVal format As String, ByVal arg0 As IntPtr, ByVal arg1 As IntPtr, ByVal arg2 As IntPtr, ByVal arg3 As IntPtr, ByVal arg4 As IntPtr, ByVal arg5 As IntPtr, ByVal arg6 As IntPtr, ByVal arg7 As IntPtr, ByVal arg8 As IntPtr, ByVal arg9 As IntPtr ) As Integer End Function
' VB 側の printf ラッパー(最大10引数) Friend Function CPrintf(format As String, ParamArray args As Object()) As String Dim capacity As Integer = 256 Dim buffer As New StringBuilder(capacity)
' 固定 10 引数に展開するための配列 Dim ptrs(9) As IntPtr Dim allocatedStrings As New List(Of IntPtr)()
Try For i = 0 To Math.Min(args.Length - 1, 9) If args(i) Is Nothing Then ptrs(i) = IntPtr.Zero ElseIf TypeOf args(i) Is String Then ' 文字列はアンマネージドの Unicode 文字列に変換してそのアドレスを取得 Dim p As IntPtr = Marshal.StringToHGlobalUni(DirectCast(args(i), String)) ptrs(i) = p allocatedStrings.Add(p) Else Dim val As Long = Convert.ToInt64(args(i)) ' 数値の場合の処理 If IntPtr.Size = 8 Then ' x64なら 8byte そのまま積んでOK ptrs(i) = New IntPtr(val) Else ' x86なら 32bitに切り詰める(制限事項:%lld 等は非対応) ' ここで 32bit 範囲外なら例外を出すか、下位 32bit のみを渡す ptrs(i) = New IntPtr(CInt(val And &HFFFFFFFFL)) ' double などの浮動小数点は非対応(%f 等は非対応)だが ' どうしても渡したいなら、該当引数の定義を As Double にしておく手もアリ End If End If Next
' 1回目の呼び出し(サイズ確認) ' 可変長引数はサポートされていないため、10個の引数を固定で渡す Dim required As Integer = _snwprintf( buffer, capacity, format, ptrs(0), ptrs(1), ptrs(2), ptrs(3), ptrs(4), ptrs(5), ptrs(6), ptrs(7), ptrs(8), ptrs(9) )
' バッファ不足時の再試行 ' msvcrt/_snwprintf の仕様により、エラー時は -1 を返す場合と必要サイズを返す場合があるため、両方のケースを考慮する If required < 0 OrElse required >= capacity Then capacity = If(required < 0, 1024, required + 1) buffer = New StringBuilder(capacity) _snwprintf( buffer, capacity, format, ptrs(0), ptrs(1), ptrs(2), ptrs(3), ptrs(4), ptrs(5), ptrs(6), ptrs(7), ptrs(8), ptrs(9) ) End If
Finally ' 確保したメモリを解放 For Each ptr In allocatedStrings Marshal.FreeHGlobal(ptr) Next End Try
Return buffer.ToString() End Function
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click Dim s As String = CPrintf("Hello %s %d %X %03d World. ", "魔界の仮面弁士", 123, 255, 78)
' "Hello 魔界の仮面弁士 123 FF 078 World. " MessageBox.Show(s) End Sub End Class