🫂

C# interop 系文字列メモ

に公開

Win32 API P/Invoke とか COM 相互運用とかで出てくる系の文字列の作り方をすぐ忘れるのでメモ。CsWin32 とアンセーフコード前提。

PWSTR (LPWSTR)

普通の Unicode 文字列。

P/Invoke だとたぶん Span オーバーロードがあるので通常は使う必要なし。どうしても使いたい場合は:

fixed (Char* buf = new Char[128])
{
  PWSTR pwstr = new(buf);
  nint hWnd = WindowNative.GetWindowHandle(App.MainWindow);
  // pwstr.Length は中身の文字列の長さを返すのでここでは使えない
  PInvoke.GetWindowText(new HWND(hWnd), pwstr, 128);
  String rev = pwstr.ToString();
}

PCWSTR (LPCWSTR)

const な PWSTR。

P/Invoke だとたぶん String オーバーロードがあるので通常は使う必要なし。どうしても使いたい場合は:

String csStr = "hoge";
fixed (Char* csStrPtr = csStr)
{
  PCWSTR pcwstr = new(csStrPtr);
  String rev = pcwstr.ToString();
}

BSTR

COM で使うやつ。

BSTR bstr = (BSTR)Marshal.StringToBSTR("hoge");
BSTR bstr2 = PInvoke.SysAllocString("hoge");
String rev = bstr.ToString();

Char* を引数にする BSTR コンストラクターもあるけど実質罠。

使用後は解放が必要。

Marshal.FreeBSTR(bstr);
PInvoke.SysFreeString(bstr2);

文字列のバイト表記

たぶんやり方は山ほどある。

分かりやすいのはこれ。コピーが発生するけど通常は気にするほどのものではなし。

Byte[] rawBytes1 = Encoding.Unicode.GetBytes("hoge");

PCWSTR 的なイメージで。うげって感じ。

fixed (Char* csStrPtr = "hoge")
{
  ReadOnlySpan<Byte> rawBytes2 = new(csStrPtr, "hoge".Length * sizeof(Char));
}

Span<Char> を Span<Byte> にキャスト。

ReadOnlySpan<Byte> rawBytes3 = MemoryMarshal.Cast<Char, Byte>("hoge".AsSpan());

文字列間のコピー

どの文字列も Span<Char> になる。

ReadOnlySpan<Char> srcSpan = "hoge".AsSpan();
ReadOnlySpan<Char> srcSpan = new(pwstr.Value, pwstr.Length);
ReadOnlySpan<Char> srcSpan = new(pcwstr.Value, pcwstr.Length);
ReadOnlySpan<Char> srcSpan = new(bstr.Value, bstr.Length);

Span 同士でコピー。

srcSpan.CopyTo(destSpan);

受け側の Span は readonly ではない普通の Span で、src を受け入れられるだけのバッファサイズで作る。

終端ヌル文字はコピーされない(というか作成した Span に入っていない)ので、受け側バッファが初期化されていない場合や、さらに別の文字列に変換したい場合は注意。

Shift-JIS (ANSI) 文字列

P/Invoke だと Unicode 版があるので通常は使う必要なし。どうしても使いたい場合は:

Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
Byte[] sjisBytes = Encoding.GetEncoding(932).GetBytes("あいうえお");

RegisterProvider() はアプリ内で一度だけ実行すれば大丈夫。

COM 用メモリ確保

文字列用バッファとして確保したいこともあるかも。AllocCoTaskMem() でアンマネージドメモリを確保。終端ヌル文字を入れる場合、必要なバッファサイズは (文字列の長さ + 1) * sizeof(Char) になる。Span でアクセスできる。

使用後は解放が必要。

nint bufPtr = Marshal.AllocCoTaskMem(needLength);
Span<Char> span = new((void*)bufPtr, needLength / sizeof(Char));
Marshal.FreeCoTaskMem(bufPtr);

通常は BSTR で足りると思うが、(先方が CoTaskMem で確保して)こちらが CoTaskMem で解放する必要のある API もある。

CoTaskMem でメモリ確保しつつ文字列をコピーしてくれる StringToCoTaskMemUni() もある。明確な記述がないが、逆の PtrToStringUni() はヌル文字までを変換することから、StringToCoTaskMemUni() はヌル文字も含めてコピーすると思われる(メモリの内容を見た限りではヌルありだった)。

nint bufPtr = Marshal.StringToCoTaskMemUni("hoge");
String? rev = Marshal.PtrToStringUni(bufPtr);

もちろん使用後は解放が必要。

CoTaskMem と並ぶ二大巨頭だった Marshal.AllocHGlobal() は .NET 6 以降は推奨されなくなった。

代わりに NativeMemory が登場(でも実装は同じという噂)。

Char* bufPtr = (Char*)NativeMemory.Alloc(needLength);

内容を初期化してくれるバージョンもあって便利。

Char* bufPtr = (Char*)NativeMemory.AllocZeroed(needLength);

いずれも解放が必要。

NativeMemory.Free(bufPtr);

確認環境

項目 環境
OS Windows 11 Pro 23H2
Visual Studio 2022 17.13.5
.NET 9.0

参考リンク

Discussion