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

■35696 / 親記事)  C言語系で使用される書式をVBで使いたい
  
□投稿者/ かぼちゃプリン 一般人(1回)-(2026/02/19(Thu) 11:07:00)
  • アイコン環境/言語:[VB.NET] 
    分類:[.NET] 

    VS2022
    .NET Framework 4.8

    VBで、C言語系で使用される"%03d"(整数を0埋め3桁にする)のような書式が使えるメソッドは.NET Frameworkに用意されていますか?
    または、VB用の書式指定子に変換してくれるようなメソッドはありますか?
    VBだったら、例えばint変数.ToString("000")のような書式になると思います。

    実は、他システムから呼び出されるプログラムをVBで作成しているのですが、
    引数に"%03d"の書式指定子が含まれていて、これをもとに処理結果を出力する必要があります。
マルチポストを報告
違反を報告
引用返信 削除キー/
■35697 / ResNo.1)  Re[1]: C言語系で使用される書式をVBで使いたい
□投稿者/ 魔界の仮面弁士 大御所(1593回)-(2026/02/20(Fri) 22:17:28)
  • アイコンNo35696に返信(かぼちゃプリンさんの記事)
    > VBで、C言語系で使用される"%03d"(整数を0埋め3桁にする)のような書式が使えるメソッドは.NET Frameworkに用意されていますか?
    > VBだったら、例えばint変数.ToString("000")のような書式になると思います。

    .NET Framework には無いので、正規表現で分解して自前解析でしょうかね。

    C++ ランタイムである msvcrt.dll の sprintf 関数や snprintf 関数とか、
    それらの Unicode 対応版とかを呼ぶってのが実装的には確実なんですが、
    .NET Framework ……というか IL ってのが、そもそも C 言語でいうところの
     int sprintf(char* buf, const char* fmt, ...);
    の可変長引数「...」の呼び出しをサポートしていないので、
    DllImport (Declare) するなら引数の数ごとにオーバーロードを用意するしかないという罠。

    C# の __arglist、.NET の RuntimeArgumentHandle も、managed な引数向けであって
    「...」に対する P/Invoke 呼び出しには対応していないんですよね。
    (これは、VB の ParamArray や C# の params とは別の仕組み)


    C++/CLI なら呼べるので、それでブリッジ DLL を作る手はあるけれど、公開関数を
     static String^ Format(String^ fmt, ... array<Object^>^ args)
    としたところで、Object[] を va_list へ変換する部分は自前実装しかないという罠。


    いずれにせよ、可変長引数の扱いが厄介なのですが、引数の数がさほど膨大になることもないでしょうし
    ひとまず 10 個まで渡せるようにしてみました。


    Imports System.Runtime.InteropServices
    Imports System.Text

    Public Class Form1

      ' 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
違反を報告
引用返信 削除キー/
■35699 / ResNo.2)  Re[2]: C言語系で使用される書式をVBで使いたい
□投稿者/ 魔界の仮面弁士 大御所(1595回)-(2026/02/20(Fri) 22:46:27)
  • アイコンNo35697に追伸(魔界の仮面弁士の記事)
    > .NET Framework には無いので、正規表現で分解して自前解析でしょうかね。

    その「自前解析」で再実装された C スタイル書式文字列対応実装ソースが
    下記にあります。ライブラリも NuGet できます。
    https://github.com/adamhewitt627/sprintf.NET/tree/master


    上記 README で Adam 氏も書かれていますが、swprintf を
    直接 P/Invoke するのって、そこそこハードルが高いんですよね…。
    ( No35697 はそれを無理矢理呼んでいるわけですが )
違反を報告
引用返信 削除キー/
■35700 / ResNo.3)  Re[3]: C言語系で使用される書式をVBで使いたい
□投稿者/ かぼちゃプリン 一般人(2回)-(2026/02/21(Sat) 13:44:10)
  • アイコン魔界の仮面弁士様、ありがとうございました。
    C言語系の書式を使用するとなると、結構大がかりなのですね。
    また、ライブラリのご紹介もありがとうございました。

    実際問題として、本当にこの方法で対応すべきかどうかは検討します。
解決み!
違反を報告
引用返信 削除キー/



スレッド内ページ移動 / << 0 >>

このスレッドに書きこむ

Mode/  Pass/


- Child Tree -