C/C++で実装したDLLをC#から呼び出す(3)
構造体の授受

どもです。

C#からVC++のDLLを呼び出す内容を、以下のエントリで書いています。

これらのエントリでは、C#からVC++のDLLに値を渡す場合の例を示しています。VC++からC#に対して値を返す、ということはしていません。そこで今回は、C#とVC++の間で構造体を介してデータの受け渡しをしてみたので、その内容について書いてみます。

1. 開発環境

毎度ですが、開発環境です。

開発/実行環境
項目 内容
OS Windows10 Pro(22H2)
CPU i7-8700
メモリ 16GB
IDE Visual Studio Community 2019
Version 17.7.6
.NET .NET 7.0
Windows SDK 10.0.22621.0
※設定上は「10.0(最新のインストールされているバージョン)」を指定。

2. やってみた内容

今回やってみた内奥は、以下のような内容になります。

  1. 整数型のメンバーを3つ持つ構造体を定義する。(C#/VC++ともに)
  2. C#側で、構造体の変数を宣言、領域を確保、テキトーな値をメンバーにセットする。
  3. C#から、構造体を引数に持つVC++の関数を呼び出す。
  4. VC++側で、構造体のメンバーの和を計算して、もう1つのメンバーに値をセットする。
  5. C#側で、セットされた和の値を表示する。

またこの時、VC++の関数はDLLとして実装し、C#側でこのDLLの関数を呼び出せるようにしています。

3. やってみよう

それでは、実際にやってみます。

3.1. 構造体

今回は、以下のような構造体を太陽、定義します。

public struct SAMPLE_STRUCT
{
	public short ParamA { get; set; }
	public short ParamB { get; set; }
	public int Result { get; set; }
};

これは、C#の場合の構造体定義になります。VC++の場合のコードは、少し異なります。構造体のメンバを示すことが目的であるため、詳細は省略します。

この構造体のメンバーのうち、ParamA、ParamBを入力とし、Resultに計算結果(今回は2つの値の「和」)を入力します。

3.2. C#の実装

C#側の実装、即ちVC++の関数の呼び出しは、以下のようになります。

SAMPLE_STRUCT sampleData = new SAMPLE_STRUCT();
sampleData.ParamA = 10;
sampleData.ParamB = 23;
sampleData.Result = -1;

long res = CppSideIF.CppSideSampleIF(ref sampleData);

Console.WriteLine($"   res = {res}");
Console.WriteLine($"Result = {sampleData.Result}");

ここで「CppSideSampleIF」が、その名前の通りVC++で実装したライブラリのI/Fになります。VC++で構造体の内容を変更、VC++からC#側に返せるようにするために、参照を渡しています。

3.3. VC++側の実装

VC++の実装です。VC++側で構造体の内容を変更するため、引数で渡された構造体の要素の和を、構造体の別のメンバーにセットします。

typedef struct _SAMPLE_STRUCT_DATA
{
	SHORT	paramA;
	SHORT	paramB;
	LONG	result;
} SAMPLE_STRUCT;

SHORT
WINAPI
CppSideSampleIF(SAMPLE_STRUCT* input)
{
	LONG result = input->paramA + input->paramB;

	input->result = result;

	return result;
}

この実装は、過去の記事とは少し異なり、DLLのエクスポートについての記述がありません。このDLLのエクスポートは、簡略化のために「.defファイル」に任せています。

処理の実装は、標準的なC++/VC++の実装です。特筆すべきことはありません。

3.4. C#側での宣言

最後に、C#からVC++で実装されたDLLの関数を呼び出すための実装です。これは、以下のように実装します。

namespace CsSideSampleProgram
{
	public static class CppSideIF
	{
		[DllImport("CppSideSampleDll.dll")]
		public static extern short CppSideSampleIF(ref SAMPLE_STRUCT input);
	}
}

この実装は、過去の記事で「定石」とした方法です。ただ今回は、VC++側の引数がポインタとなっているため、C#側での宣言は参照を渡すことを示す「ref」を付与しています。

3.5. 実行結果

これまで紹介したコードを実装、実行すると下図のようになります。



実装した通り、ParamAとParamBの和がResultにセットされていることが確認できます。

4. まとめ

今回は、C#とVC++の間で、構造体を介してデータの受け渡しを行う方法について書きました。C#側でのDLLの宣言においてrefを付与することで、VC++のDLLのポインタ引数に対してアドレスを渡すことができます。これによりVC++側では、C#から渡された構造体のメンバーの値の参照、および値の変更ができます。

今回紹介した内容は、基本的な内容のみです。より厳密には、マネージド領域、アンマネージド領域についても考慮する必要があります。そういった内容については、別の機会に書こうと思います。

今回の内容が誰かの助けになれば幸いです。ではっ!

Ex. 公開しています

今回のエントリの全コードを、GitHubにて公開しています。より詳細に内容を確認したい場合には、コチラを参照してください。