見出し画像

C#からC++を実行する-その2

1. はじめに

 C++でGUIを使用するのは面倒である。例えば、Visual C++にはMFCというライブラリがあり、GUIを作成することができる。しかし、記述方法が非常に特殊で、筆者にとってはC#の方が扱いやすい。

 そこで、C++をDLL化し、C#のGUI上から呼び出せないか検討した。そのためには、C#とC++間のデータの受け渡し方法を確立する必要がある。その第一段階として、以前、C#フォームアプリケーションからC++DLLを実行する方法をまとめている(過去記事は以下)。

 上記では、C++をDLL化した関数を、C#から値渡しで呼び出している。DLLを呼び出すことには成功したがまだ問題点がある。値渡しの場合、関数を抜けるとデータが消失してしまうため、例えば、DLL側で代入したデータをC#側で使用することができない。

 そこで、今回は、C#からDLLに対して構造体を渡し、データをセットする方法をまとめようと思う。

2. プロジェクトの作成

 過去記事を見直しながら、もう一度、プロジェクトを作成する。

2.1 空のプロジェクトの作成

 まずは、C++のプロジェクトを作成する。Visual Studioから、空のプロジェクトを作成する。

2.2 cpp、hファイルを追加する

 *.cppファイルと*.hファイルを追加する。今回は、example.hとexample.cppを追加した。

2.2.1 ヘッダファイル(example.h)

 C++側のヘッダファイルを記述する。

//example.h
#pragma once
#ifndef EXAMPLE_HPP
#define EXAMPLE_HPP

#include <string.h>
//受け渡しをする構造体
typedef struct DATA {
    char str[8];
    int num;
}Data;

#endif

 上記は、C++のヘッダファイルの記述である。インクルードガードの中に構造体DATAを定義している。この構造体データがやり取りするデータである。

2.2.2 ソースファイル(example.cpp)

 C++側のソースファイルを記述する。

//example.cpp
#include "example.h"

extern "C"
{
    __declspec(dllexport) void __stdcall DataSet(DATA* data) {
        memcpy(data->str, "test\n", sizeof(5));
        data->num = 10;
    }
}

 上記では、エントリポイントを作成し、DataSet関数をC#からの呼び出してもらう。また、DataSet関数では以下の処理を行う。
・memcpyで、「test」という文字列を代入する
・変数numに10を代入する

 つまり、C#からDataSet関数を呼び出すと、渡された構造体にデータをセットする。なお、データの受け渡しをすればよいので、意味のないプログラムである。

2.3 C++のDLLの作成

 2.2で作成したプログラムをDLLファイルとしてビルドする。前回を参考に以下の設定に変更した。
(1) プロジェクトのプロパティで「構成の種類」→ダイナミックライブラリ(.dll)に変更
(2) 構成マネージャをx64に変更

 上記の設定に変更し、ビルドを実行すると、*.dllファイルが作成できる。

2.4 C#のプロジェクトを作成する

 C#のフォームアプリケーションのプロジェクトを作成する。作成したFormクラスに以下のプログラムを記述した。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Runtime.InteropServices;


namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {

        //受け渡しをする構造体
        public struct DATA
        {
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)]
            public String str;
            public int num;
        }
        [DllImport(@"C:\Users\username\source\repos\WindowsFormsApp1\WindowsFormsApp1\bin\Debug\Project1.dll", EntryPoint =
        "DataSet", CallingConvention = CallingConvention.StdCall)]
        //参照渡しをする場合は、refを入れる
        public static extern void DataSet(ref DATA data);

        public Form1()
        {
            InitializeComponent();
        }
        //ボタンイベントをトリガーにしてDLLを動作する
        private void button1_Click(object sender, EventArgs e)
        {
            //構造体を宣言
            DATA data = new DATA();

            //参照渡しで構造体をC++DLLに渡す
            DataSet(ref data);
        }
    }
}

 詳細を以下に示す。

2.4.1 構造体の定義

 C++とやり取りする構造体を定義する。そのために、DLLとの変数の受け渡し用の構造体(DATA)を作成した。C#とC++では言語仕様が異なる。よって、DLLに対してString型の構造体の受け渡しのやり方に不安がある。そこで、マーシャリングを行い、サイズを固定にして受け渡すこととした。記述方法は以下である。

//受け渡しをする構造体
public struct DATA
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)]
    public String str;
    public int num;
}

 「[MarshalAs……]」と書いてある箇所がマーシャリングの記述である。 マーシャリングとは、異なる環境間でデータを行うための変換のことである。今回は、C#をC++が理解できる形に変換する。つまり、C++における以下と対応しているはずである。

//string型でやり取りできるかわからなかったため、配列とする
char str[8];

2.4.2 ボタンイベントを作成 

 今回は、GUI上にボタンを作成し、イベントハンドラからDLLを呼び出す(図1)。

図1 ボタン

 図1は、フォーム上に配置したボタンである。C#ではこれをダブルクリックすることで、自動的にイベントハンドラが作成される。以下が追加したボタンイベントの記述である。

//ボタンイベントをトリガーにしてDLLを動作する
private void button1_Click(object sender, EventArgs e)
{
    //構造体を宣言
    DATA data = new DATA();

    //参照渡しで構造体をC++DLLに渡す
    DataSet(ref data);
}

 上記は以下の動作をする記述内容である。
(1) 構造体を宣言する
(2) DataSet関数を呼び出し、構造体を参照渡しで渡す

なお、C#で参照渡しをする場合は、refを付けるようだ。「DataSet(ref data);」と記述し、作成した構造体データをDLL側に受け渡している。

3. 実行結果

 2.で一通りの準備が整ったので、今回の目的を振り返る。

3.1 確認方法

 今回は、C#からDLLに対して構造体を渡し、データをセットすることである。確認をするために、作成したDataSet関数にブレークポイントをセットする。

図2 ブレークポイントの設定の様子

 図2は、C#側のDataSet関数にブレークポイントを設定した様子である。筆者は以下のように確認することを想定している。
(1) C#をデバッグ実行する
(2) C#側DataSet関数のブレークポイントで動作が止まる
(3) ステップインし、DLLのDataSetを呼び出す
(4) 一通り実行し、C#に戻る
(5) data変数をウォッチし、構造体にデータが存在することを確認する

3.2 結果

 実行すると、C#側の構造体のデータが図3ということを確認出来た。

図3 C#の構造体の中身

 確認した結果、C#の構造体の中身がきちんと設定されていることが分かった。

4. おわりに

 今回は、C#からC++で作成したDLLに対してデータを受け渡しをする方法をまとめた。やり方はほかにもいろいろあるだろうが、参考になれば幸いである。

いいなと思ったら応援しよう!