C++のモジュールからC#のDLLを呼び出してみる

提供: とある社畜の頭脳整理
ナビゲーションに移動 検索に移動

こんなことほとんどする事ないんだろうけど…MetaTraderの開発をしているとしたくて仕方なくなることがあるんだよ(笑)

C♯のプロジェクト作成

DLLだけにクラスライブラリでプロジェクトを作成するんだけど…プロジェクトのプロパティを変更する必要があるんだよ。

アセンブリ情報の設定

  1. プロジェクトのプロパティを開く
  2. 「アプリケーション」のタブで「アセンブリ情報」のボタンをクリックする
  3. 「アセンブリをCOM参照可能にする」のチェックボックスにチェックを入れる
  4. 「OK」ボタンをクリックする

CppConsoleToCSharpDll-001.jpg

ビルドの設定

  1. プロジェクトのプロパティを開く
  2. 「ビルド」のタブで「COM相互運用機能の登録」にチェックを入れる
  3. 保存する

CppConsoleToCSharpDll-002.jpg

注意事項

OSにログインしているアカウントの権限によっては、ビルド時にレジストリへの登録に失敗するエラーが発生することがあるんだ。そんなときはVisualStudioを管理者権限で実行すれば登録できるよ。

ソース

C♯のDLL

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

//追加
using System.Runtime.InteropServices;

namespace CSharpDLL
{
    /// <summary>
    /// クラスインターフェース設定
    /// </summary>
    [ClassInterface(ClassInterfaceType.AutoDual)]

    /// <summary>
    /// 合計クラス
    /// </summary>
    public class CSharpDLL
    {
        /// <summary>
        /// 合計処理
        /// </summary>
        /// <param name="p_Number1">数値1</param>
        /// <param name="p_Number2">数値2</param>
        /// <returns></returns>
        public Int32 Sum(Int32 p_Number1, Int32 p_Number2)
        {
            return p_Number1 + p_Number2;
        }
    }
}

C++のソース

「初期処理」「メイン処理」「後処理」で分けたので、DLLのときも応用できると思うよ。

// CppConsoleToCSharpDLL.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"

//追加
#pragma comment(lib, "comsupp.lib")
#pragma comment(lib, "comsuppw.lib")
#include <Windows.h>
#include <comutil.h>
#include <objbase.h>

//グローバル変数
IDispatch *pIDisp = NULL;
IUnknown *pIUnk = NULL;

//プロトタイプ宣言
long _Init(void);
long _Finalize(void);
long _Sum(long p_Number1, long p_Number2);

int _tmain(int argc, _TCHAR* argv[])
{
	//変数宣言
	int l_Result = 0;

	//初期処理
	_Init();

	//合計処理
	l_Result = _Sum(300, 500);

	//後処理
	_Finalize();

	printf("計算結果:%d", l_Result);
}

//***************************************************************************//
//初期化関数
//***************************************************************************//
long _Init(void)
{
	CLSID clsid;

	//COM初期化
	::CoInitialize(0);

	//ProcIDからCLSIDを取得(ネームスペース名.クラス名)
	HRESULT h_result = CLSIDFromProgID(L"CSharpDLL.CSharpDLL", &clsid);
	if (FAILED(h_result))
	{
		return -1;
	}

	//Instanceの生成
	h_result = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, (void**)&pIUnk);
	if (FAILED(h_result))
	{
		return -2;
	}

	//インターフェースの取得(pIDispは共通変数)
	h_result = pIUnk->QueryInterface(IID_IDispatch, (void**)&pIDisp);
	if (FAILED(h_result))
	{
		return -3;
	}

	//正常終了
	return 0;
}

//***************************************************************************//
//後始末処理
//***************************************************************************//
long _Finalize(void)
{
	pIDisp->Release();
	pIUnk->Release();
	::CoUninitialize();
	return 0;
}

//***************************************************************************//
//合計処理呼出処理
//***************************************************************************//
long _Sum(long p_Number1, long p_Number2)
{
	//DISPIDの取得(関数名の設定)
	DISPID dispid = 0;
	OLECHAR *Func_Name[] = { L"Sum" };
	HRESULT h_result = pIDisp->GetIDsOfNames(IID_NULL, Func_Name, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
	if (FAILED(h_result))
	{
		return -1;
	}

	//パラメータ作成
	DISPPARAMS params;
	::memset(&params, 0, sizeof(DISPPARAMS));

	params.cNamedArgs = 0;
	params.rgdispidNamedArgs = NULL;
	params.cArgs = 2; //呼び出す関数の引数の数

	//引数設定(順番に注意…逆になる)
	VARIANTARG* pVarg = new VARIANTARG[params.cArgs];
	pVarg[0].vt = VT_I4;
	pVarg[0].lVal = p_Number2;
	pVarg[1].vt = VT_I4;
	pVarg[1].lVal = p_Number1;
	params.rgvarg = pVarg;

	VARIANT vRet;
	VariantInit(&vRet);

	//呼び出し
	pIDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &params, &vRet, NULL, NULL);

	delete[] pVarg;
	return vRet.lVal;
}

C♯の型とC++の型とVARTYPEの関係

戻り値を取得するのに「VARIANT型」を使用する必要があるんだ。そうなると、C♯とC++とVARIANT型の関係が分からないと、戻り値が取得できなくなっちゃうんだよ。(引数を渡すときも型の関係が分からないとだね…)ネットで調べても、なかなかまとまったものが出てこなかったので、ここにまとめておくよ。

C♯ C++ VARTYPE 使用するメンバ
short (System.Int16) SHORT (short) VT_I2 iVal
int (System.Int32) INT (int)
LONG (long)
VT_I4 lVal
bool (System.Boolean) BOOL (long) VT_BOOL boolVal
string (System.String) LPCSTR (const char *)
LPCWSTR (const wchar_t *)
VT_BSTR bstrVal
double (System.Double) DOUBLE (double) VT_R8 dblVal
float (System.Single) FLOAT (float) VT_R4 fltVal

参考サイト

動的な実行
@IT:.NET TIPS Win32 APIやDLL関数を呼び出すには? - C#
もっと具体的な参考にしたサイトがあったんだけど…閉鎖されたか知らないところに移動したみたい…。