文字列授受（C#⇄C++）のための文字(列)知識

C#とC++との間で文字列をやり取りするのための、C++&C#における文字列知識と方法について

## TL;DR

　日本語などで非ASCII文字(?)を中心に用いる場合、C++17以前で`wchar_t`もしくは`char16_t`を用いるのがよいと思われます（C++23未公開時点）。実装に関しては [文字列の授受（C# → C++）](#文字列の授受c--c-1)節と[文字列の授受（C++ → C#）](#文字列の授受c--c-2)節を参照してください。

> `std::string`や`std::wstring`は長さが変化するため、文字列授受を行う際のC++側の出口には使えません。したがって、それらを使う場合であっても出口となる関数では`wchar_t`などに変換する必要があります。

　C#の文字列型`string`はデフォルトでUTF-16によりエンコードされているため、メモリ消費量も考えると両者をUTF-16で扱うと便利かもしれません。

マジで沼

<details><summary>【追記 : C++23について】</summary><div>

2023年8月ごろにこのメモを書いてから数か月がたち、投稿時点ではC++23が策定されました。[UTF-8のソースコードをサポート](https://cpprefjp.github.io/lang/cpp23/support_for_utf8_as_a_portable_source_file_encoding.html)するようになったり、UTF-8周りの変化もあったようです。

最近UTF-8を必要としてなかったのであんまり調べてはないのですが、多分今もUTF-8を使用する必要はないんじゃないかと思います。あと現時点（2024年3月現在）ではMSVCがC++23をほとんど[採用してなかったり](https://en.cppreference.com/w/cpp/compiler_support/23)しますし。ClangとGCC使ってる方はC++23でもいいのかも…？

</div></details>

## 目次

- [TL;DR](#tldr)
- [目次](#目次)
- [文字列を扱うための前提知識](#文字列を扱うための前提知識)
  - [文字セット\&文字コード](#文字セット文字コード1)
    - [Shift-JISとCP932](#shift-jisとcp932)
    - [Unicode](#unicode)
  - [ポインタ(\*)と配列(\[\])](#ポインタと配列)
  - [マルチバイト文字列とワイド文字列](#マルチバイト文字列とワイド文字列4)
- [C++/C#における文字(列)型](#ccにおける文字列型)
  - [C++における文字型](#cにおける文字型)
  - [C++における文字列](#cにおける文字列)
  - [C++とUTF-8/UTF-16](#cとutf-8utf-16)
  - [文字列とロケール](#文字列とロケール)
  - [C#の文字列](#cの文字列)
- [文字列の授受（C# ⇄ C++）](#文字列の授受c--c)
  - [文字列の授受（C# → C++）](#文字列の授受c--c-1)
  - [文字列の授受（C++ → C#）](#文字列の授受c--c-2)
    - [文字列の型を`wchar_t*`または`wstring`とする](#文字列の型をwchar_tまたはwstringとする)
    - [文字列の授受はポインタと`StringBuilder`で行う](#文字列の授受はポインタとstringbuilderで行う)
    - [`CharSet.Unicode`を明示する（"Buffer is too small"エラー）](#charsetunicodeを明示するbuffer-is-too-smallエラー)
- [参考文献](#参考文献)

## 文字列を扱うための前提知識

### 文字セット&文字コード[1]

　文字種の分類として、「**文字セット**(character set)」と「**文字コード**」があります。文字セットが同じ体系を持つ文字コードの集まりであるのに対して、文字コードは何らかの規格化された文字の表し方を意味します。すなわち、(文字コード)∈(文字セット)といえます。
　…だと思いますが、調べてもこの境界がわかりません。（字面からするならば文字の集合はどのレベルであっても文字セットといえるように思う。）

　文字を表現する際必要なサイズに着目したとき、文字セットとして次の分類が可能です。

|分類|バイト数|説明|
|-|-|-|
|シングルバイト|1|ASCII, SJISの一部
|ダブルバイト|2|SJIS
|マルチバイト|1or2 (MSDN)<br>2~ (UTF-8)|定義が異なることがあるが、MSに従えばASCIIもSJISもこれ
|ワイド|2|Unicode (UTF-16, 32)

　文字コードは上に記述したASCIIやShift-JIS、UTF-8などのことを言います（というよりも文字の符号化規則の集合のことを文字コードと呼ぶような気がしますが）。このうちいくつかについて見ることにします。

#### Shift-JISとCP932
　**CP932**はマイクロソフト(等?)独自のShift-JISのスーパーセットです[2]。すなわち、(Shift-JIS)⊂(CP932)です。例えば「①」はCP932のほうにしか含まれないため、多くの場合Shift-JISはCP932を示します。C++の場合、いずれも(?)`char`で記述可能です。

#### Unicode
　**Unicode**は最大21bitsのコードポイントを用いて文字の振り分けを決めている規格です。UTF-8,16,32の違いはそれぞれそのコードポイントをどのように符号化するかという点にあります[3]。

- UTF-8は1~4Bytesで文字を記録する
- UTF-16は基本的に2Bytesで文字を記録する。4Byteとなることもある
- UTF-32は4Bytesで文字を記録する

C++の場合、UTF-8は`char`(または`char8_t`：C++20~)で記述を行い、UTF-16,32は`wchar_t`(または`char16_t`/`char32_t`)で記述することになると思います。

したがって、一般にはUTF-8またはUTF-16を使うことになります。前者はメモリを節約したい場合やbyte単位で扱う場合に有効で、後者はコードポイントをベースに扱う場合有効といえます。一方、UTF-32を選択することはほぼないといってよいでしょう。これは、ほとんどの文字が2Bytesで表しうるなか、4Bytesのデータとして処理するという欠点がかなり大きい点などが原因です。

　ラテンベースの処理を行う場合にはUTF-16は非効率なのですが、日本語を多用する都合上UTF-16（WindowsにおけるC++&C#のデフォルト）でも問題ないだろうと感じます。

### ポインタ(*)と配列([])

　基本的に`char[]`と`char*`は同じように扱えますが、前者は配列を示し、後者はそれを指し示すポインターを示すという点に違いがあります。したがって、文字列のことを示したい場合は`char[]`などを用いるべきでしょう。

### マルチバイト文字列とワイド文字列[4]

　前述のように、文字の符号化方式には可変長のマルチバイト文字(or narrow strings)と固定長(ではない場合もある)のワイド文字の2種類があります。前者は`char`や`char8_t`などで、後者は`wchar_t`や`char16_t`などです[5]。

　`char[]`文字列では1byteで表せた文字も`wchar_t[]`文字列においては2bytesで表されるなどの違いがあるため、基本的にASCII領域の文字が多いほど後者のサイズが大きくなります。一方でワイド文字列では1文字が基本的に固定長ですので、日本語などを扱いたい場合はこちらのほうが操作はしやすいようです。

## C++/C#における文字(列)型

### C++における文字型

　C++における文字の型は以下の5つです。いずれも英数字、非英数字グリフ、非印刷文字(non-printing)を表すことができます。

- ナロー文字列：`char`, `char8_t`
- ワイド文字列：`wchar_t`, `char16_t`, `char32_t`

`char`はマルチバイト文字の個々のバイトを格納できます。`wchar_t`は実装定義（implementation-based: WindowsとLinuxなどで実装が異なることを示す？）のワイド文字型です。Microsoftコンパイラでは`char`は8ビット型であり、`wchar_t`は16ビット（UTF-16LEでエンコードされたUnicodeを格納する）となります。


### C++における文字列

C++における文字列には大きく分けて2種類あります[6]。

|||
|-|-|
|Cスタイルの文字列|`char[]`や`wchar_t[]`など、固定長かつ`null`で終わるもの|
|標準C++文字列|`std::string`や`std::wstring`など、可変長<br>`basic_string`クラスのオブジェクトにより操作されるシーケンス|

ここで、標準C++文字列は様々な操作を行いやすくした、通常の型と同様に文字列を扱えるコンテナです。

### C++とUTF-8/UTF-16

　C++20では、UTF-8を扱うのはほぼ無価値なように感じます。たしかにchar8_t型が追加はされてはいるのですが、文字列を処理する関数などがあまり存在しないためです。例えば`mbrtoc8`(`char` -> `char8_t`)関数はC++23以降(おそらく)に追加されることとなっているようです[7]。

　こういった基本的なシステムが存在しない以上、UTF-8を扱う場合はC++17標準を用いるのがなんだかんだよいのではないでしょうか。とはいえ、UTF-8を扱う場合も結構面倒くさそう＆他への影響も大きそう[8]ですので、現状無難な選択肢はUTF-16の使用だと思います。

### 文字列とロケール

　文字列処理関数はロケールの影響を受けるようです。このため、それらの関数を使用する前には`setlocale`関数を用いてロケールの設定をした方がいいかもしれません。

　ちなみに`wchar_t`は「`wchar_t`型のひとつのオブジェクトは、実装がサポートするロケールの文字セットの任意の一文字を表現できる」ものとして定義されたそうですが、WindowsにおいてはUTF-16を使うものとしています[9]。よくわかりませんが、Windows環境下においては文字列自体にはlocaleは影響してこないということでしょうか…？

### C#の文字列

　C#における代表的な文字列の型は`string`です。一応C#においても`char[]`を用いることは可能ですが、おそらく一般的でないでしょう。

　また、C#の`string`のエンコード方式はUTF-16です[10]。`string`型文字列を他のエンコード方式に変更したい場合（エンコード方式を変更したテキストデータを取得したい場合）は`Encoding`関数[11]を使うことになります。

## 文字列の授受（C# ⇄ C++）

### 文字列の授受（C# → C++）

　逆方向への文字列の授受と比較すると、こちらはわりと楽です。このうちC++では、メモリ開放の責を負わせないため`const`にしたほうがよいようです。また、C#側において`CharSet`の指定は必須ではないですが、エンコーディング方式の不一致が生じる可能性もあるため記述します。

```Cpp
// C++
#define DLLEXPORT extern "C" __declspec( dllexport )
DLLEXPORT bool TestStr1(const wchar_t* in_str);
```

```Csharp
// C#
[DllImport("EditorUtility.dll", CharSet = CharSet.Unicode)]
TestStr1(string textIn);

TestStr1("hello");
```

### 文字列の授受（C++ → C#）

　ようやく本題です。先に結論を述べると、次のように記述することで文字列の授受が可能でした。

```Cpp
// C++
DLLEXPORT void __stdcall TestStr2(wchar_t* out_str, int len)
    wstring source = L"日本語など含む文字列";
    wcscpy_s(out_str, len, source.c_str());
}
```

```Csharp
// C#
[DllImport("EditorUtility.dll", CharSet = CharSet.Unicode)]
static extern void TestStr2(StringBuilder outBuf, int len);

StringBuilder outBuf = new StringBuilder(255);
TestStr2(outBuf, outBuf.Capacity);
Debug.WriteLine(outBuf.ToString());
```

　ポイントは以下の３つです。このスレッド[1]が最も役に立ちました。

- C++では文字列の型を`wchar_t*`または`wstring`とする
- 文字列の授受はポインタ(C++)と`StringBuilder`(C#)で行う
- `DllImport`において`CharSet`を`CharSet.Unicode`と明示する

#### 文字列の型を`wchar_t*`または`wstring`とする

　unicode(ワイド文字)の文字列を扱う場合はこれらを用います。一応オプションで1バイトとマルチバイトを切り替えられる`TCHAR`も存在はしますが、特にこれにする利点が思い浮かばないため`wchar_t`などを採用しました。

#### 文字列の授受はポインタと`StringBuilder`で行う
　
　文字列の授受はポインタ(C++)と`StringBuilder`(C#)で行います。

　文字型(?)配列と文字列型（例えば`char*`と`string`）はおおよそ同じであり、長さが不足した場合に自動的にメモリを確保するか否かという点のみ異なる（と思われます）[5]。

#### `CharSet.Unicode`を明示する（"Buffer is too small"エラー）

　文字列のコピーにおいて "Buffer is too small" エラーが生じることがあります。`wcscpy_s`について、第二引数で渡す長さ(`size_t`)を`StringBuilder`のバッファサイズとすることで解決しました。

　`wcscpy_s`関数を使用した際のこのエラー自体は、デバッグモードの際ランダムに生じることがある[2]とのことです。

## 参考文献

[1] [charとUnicodeとワイド文字をごっちゃにしないために](http://marupeke296.com/CPP_charUnicodeWideChar.html)

[2] [Shift_JISとCP932とWindows-31Jの違いを整理した](https://monologu.com/sjis-cp932-windows31j/)

[3] [Unicodeとは？ その歴史と進化、開発者向け基礎知識](https://www.buildinsider.net/language/csharpunicode/01)

[4] [Multibyte and Wide Characters](https://learn.microsoft.com/en-us/cpp/c-language/multibyte-and-wide-characters?view=msvc-170)

[5] [Microsoft, char、wchar_t、char8_t、char16_t、char32_t](https://learn.microsoft.com/ja-jp/cpp/cpp/char-wchar-t-char16-t-char32-t)

[6] [Microsoft, basic_string Class](https://learn.microsoft.com/en-us/cpp/standard-library/basic-string-class)

[7] [mbrtoc8](https://en.cppreference.com/w/c/string/multibyte/mbrtoc8)

[8] [C++(Visual Studio)でUTF-8を扱うための試行錯誤のメモ](https://qiita.com/BlueSilverCat/items/1e5af7fdaeaf8fd2289d)

[9] [C++標準化委員会、ついに文字とは何かを理解する: char8_t](https://qiita.com/yumetodo/items/54e1a8230dbf513ea85b)

[10] [C# 11.0 new features: UTF-8 string literals](https://endjin.com/blog/2023/02/dotnet-csharp-11-utf8-string-literals#:~:text=The%20C%23%20string%20type%20is,as%20a%20UTF%2D16%20string.)

[11] [Microsoft, Encoding クラス](https://learn.microsoft.com/ja-jp/dotnet/api/system.text.encoding?view=net-7.0)

[12] [Return contents of a std::wstring from C++ into C#](https://stackoverflow.com/questions/7051097/return-contents-of-a-stdwstring-from-c-into-c-sharp)

[13] [VC++ wcscpy_s randomly assert on "Buffer is too small](https://stackoverflow.com/questions/58779909/vc-wcscpy-s-randomly-assert-on-buffer-is-too-small)