やりたいこと
日本語Windows10の自分のPCで動かすと、出力される文字列(日本語を含む)がうまく表示されるのに、exeをコピーして英語Windows10で動かすと、文字化けしてうまく表示されなくなった。解決方法を知りたい。
実験のために動かしたプログラム
元のプログラムとは異なるものだが、この現象の実験のために作ったプログラムが下記。
プログラムの構成としては下記の構成になっている。
- 実行ファイル(.exe)(C#)
- dllファイル(.dll)(C++)
#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);
}
#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);
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
が、????になってしまっている。)
色々調べた結果、Windowsの設定の「Unicode対応ではないプログラムの言語」から、**「システムロケールの設定」**を「日本語」にしてやると、改善されることが分かった。
Windows10では、下記のようにするとその設定を変更できる。
- コントロールパネルを開く(「カテゴリ」で開く)
- [時計と地域]を選択する
- [地域]を選択する
- [管理]タブを開く
- [システムロケールの変更]を押す
- [日本語(日本)]を選択する
システムロケールの設定を行うと、C#exeからC++dll側に、うまく日本語が渡ってくるようになる。
どんな言語の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に変更
public static class NativeMethod
{
[DllImport("DllTest.dll", CallingConvention = CallingConvention.Cdecl, CharSet =CharSet.Unicode)]
public extern static void Test_MyApi([MarshalAs(UnmanagedType.LPWStr)]string txt);
}
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/