LoginSignup
4
1

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-06-03

やりたいこと

日本語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

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

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

image.png

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

image.png

システムロケールの設定を行うと、C#exeからC++dll側に、うまく日本語が渡ってくるようになる。
image.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/

4
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1