[C++/C#]英語Windows10で、日本語Ansi文字列をC#→C++DLLに渡す

# やりたいこと
日本語Windows10の自分のPCで動かすと、出力される文字列(日本語を含む)がうまく表示されるのに、exeをコピーして英語Windows10で動かすと、文字化けしてうまく表示されなくなった。解決方法を知りたい。

# 実験のために動かしたプログラム
元のプログラムとは異なるものだが、この現象の実験のために作ったプログラムが下記。

プログラムの構成としては下記の構成になっている。

- 実行ファイル(.exe)(C#)
- dllファイル(.dll)(C++)

```DllTest.cpp
#include "stdafx.h"
#include <windows.h>
#include <stdio.h>

#define VC_DLL_EXPORTS
#include "DllTest.h"

// エクスポート関数の実装
// 通常、__stdcallを適用する(__stdcall = WINAPI)。
void __cdecl Test_MyApi(const char* txt)
{
	char buf[256] = { 0 };

	strcpy_s(buf, sizeof(buf), txt);

	printf(buf);
}
```

```DllTest.h
#pragma once
#include <Windows.h>
#include <string.h>

#ifdef VC_DLL_EXPORTS
#undef VC_DLL_EXPORTS
#define VC_DLL_EXPORTS extern "C" __declspec(dllexport)
#else
#define VC_DLL_EXPORTS extern "C" __declspec(dllimport)
#endif

VC_DLL_EXPORTS void __cdecl Test_MyApi(const char* str);
```

```Program.cs
using System.Runtime.InteropServices;
using System;
using System.Globalization;
using System.Threading;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            string txt = "あいうえお";

            NativeMethod.Test_MyApi(txt);

            Console.ReadLine();
        }
    }

    public static class NativeMethod
    {
        [DllImport("DllTest.dll", CallingConvention = CallingConvention.Cdecl, CharSet =CharSet.Ansi)]
        public extern static void Test_MyApi([MarshalAs(UnmanagedType.LPStr)]string txt);
    }
}

```

# 調査

**ポイントは、C#exeからC++dllに文字列を渡す際、「Ansi」形式で渡しているところだった。**

原因を追いかけていくと、exeからdllに文字列を渡したときに、化けていた。
デバッグ実行すると、受け取った文字列(下記では`txt`が、????になってしまっている。)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/d4157a65-d057-5de6-af94-ffb3b6224665.png)

色々調べた結果、Windowsの設定の「Unicode対応ではないプログラムの言語」から、**「システムロケールの設定」**を「日本語」にしてやると、改善されることが分かった。
Windows10では、下記のようにするとその設定を変更できる。

- コントロールパネルを開く(「カテゴリ」で開く)
- [時計と地域]を選択する
- [地域]を選択する
- [管理]タブを開く
- [システムロケールの変更]を押す

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/e29fc2a4-102c-2b4a-f93d-3a0a4d4486e5.png)

- [日本語(日本)]を選択する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/a970644e-adc9-8e34-cac9-e9e373e3403b.png)

システムロケールの設定を行うと、C#exeからC++dll側に、うまく日本語が渡ってくるようになる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/ed899ee7-668c-e617-d9db-ff84b8d8acc3.png)


# どんな言語のWindowsでも大丈夫なように、システムロケールをコードで日本語に設定できないか？
システムロケールの設定を変えてやれば解決できることは分かったが、どんな言語のWindowsでも大丈夫なように、システムロケールをコードで日本語に設定できないか？を試した。が、結果としては、下記のようなコードを入れてみたが、システムロケールを英語にしていると受けた文字列が「????」になってしまい、うまくいかなかった。

やったこと
- C#側で`Thread.CurrentThread.CurrentCulture = new CultureInfo("ja-JP");`
- C++側で`std::setlocale(LC_ALL, "ja_JP");`

とりあえず、一旦解決しているので置いておくが、時間があれば調べる。

# 別解
システムロケールが英語のままにしたい場合は、Unicodeを使うように、C#、C++のコードを直すことでも対応はできる。

- C++dll側で、引数で受け取る文字列の方をwchar_t*に変更し、中で使う処理もwchar_t*に合わせる。
- C#exe側で、呼び出すdllメソッドのImportをしている部分で、マーシャリングの設定をUnicodeのための設定にする。
    -  CharSetをAnsiからUnicodeに変更
    -  MarshalAsのタイプをLPSTRからLPWSTRに変更

```Program.cs
public static class NativeMethod
{
    [DllImport("DllTest.dll", CallingConvention = CallingConvention.Cdecl, CharSet =CharSet.Unicode)]
    public extern static void Test_MyApi([MarshalAs(UnmanagedType.LPWStr)]string txt);

}
```

```DllTest.cpp
void __cdecl Test_MyApi(const wchar_t* txt)
{
	wchar_t buf[256] = { 0 };

	wcscpy_s(buf, txt);

    // ★システムロケールが英語なので、bufには正しく日本語文字列が入るが、Console表示は化ける(????になる)
	wprintf(L"%s", buf);
}
```

これで、システムロケールが英語でも、正しく日本語文字列を渡すことができる。

# 考察(今回わかったこと(推測・感触含む))

今回のC++dll側のような`char*`で文字列をやりとりしているようなプログラムのことを、今のWindows的には「Unicode対応ではないプログラム」と位置付けている。

Unicode対応ではないプログラム(dll)に対して、引数で文字列を渡すようなときは、Windowsはシステムロケールの設定をもとに、その文字列がなにかを判定してやりとりさせている(っぽい)。

だから、NativeMethodのstaticクラスのところでメソッドをImportするときに、`CharSet =CharSet.Unicode`とか`[MarshalAs(UnmanagedType.LPWStr)]`と書いていても、「日本語である」ということが分かっていないため、うまく渡せない(っぽい)。

# 参考

マーシャリング(++C++)
https://ufcpp.net/blog/2016/12/tipsstringmarshal/