C++の応用 - C Sharp DLLの使用
概要
C++ EXEからC# DLLの関数を呼び出す方法は、幾つか方法が存在しており、各々にメリットとデメリットがある。
下記の表1に代表的な4種類の方法を示す。
表1. C++ EXEからC# DLLの関数を呼び出す方法
方法 | メリット | デメリット |
---|---|---|
C++/CLIを使う | 最も簡単 VisualStudioのIntelliSenseも使用可能 |
プロジェクトの設定で[CLIを使う]に変更する必要がある。 |
.NET DLLExportを使用して、 C# DLLのメソッドをエクスポートする |
[CLIを使う]に変更しなくてよいGetProcAddress 関数が使用できるため、よく知られた方法で関数を呼び出す事が出来る |
C# DLL側のソースコードが無い場合は利用できない。 |
C# DLLに対するC++/CLIのラッパーDLLを作成して、 C++ EXEから使う |
[CLIを使う]に変更しなくてよい COMを使用しない場合において、 元のプロジェクトの設定を変更したくない場合に使用可能 |
やり方がスマートではない。 |
Monoを使う | C++実行ファイルとC#ライブラリの2つを作成するだけでよい。 Linuxでも使用可能 |
Monoをインストールする必要がある。 C++の実行ファイルにおいて、Monoに関連するヘッダファイルをインクルードして、 C#ライブラリを呼び出す。 |
C# DLLをCOM参照可能にする | [CLIを使う]に変更しなくてよい | C++ EXEのコード量が増えて面倒である。 |
上表1において、以下の手順を記載する。
- C++/CLIを使う
- C# DLL側で関数をエクスポートする
- C++/CLIのラッパープロジェクトを作成する
- C# DLLをCOM参照可能にする
C++/CLIを使う方法
// SampleDLL.cs
namespace SampleDLL
{
public class Class1
{
public static int Sum(int a, int b)
{
return a + b;
}
}
}
Visual C++のプロジェクト設定を開いて、[共通言語ランタイム サポート (/clr)]に変更する。
// SampleEXE.cpp
#include <Windows.h>
#include <iostream>
#using "SampleDLL.dll"
using namespace SampleDLL;
int main()
{
std::cout << Class1::Sum(1, 2) << std::endl;
return 0;
}
C# DLL側で関数をエクスポートする方法
まず、プロジェクトを作成してソースコードを記述する。
// SampleDLL.cs
namespace SampleDLL
{
public class Class1
{
[DllExport]
public static int Sum(int a, int b)
{
return a + b;
}
}
}
// SampleEXE.cpp
#include <Windows.h>
#include <iostream>
typedef int (*Sum)(int a, int b);
int main()
{
auto hModule = LoadLibrary(L"DllExportTest.dll");
auto sum = reinterpret_cast<Sum>(GetProcAddress(hModule, "Sum"));
std::cout << sum(1, 2) << std::endl;
return 0;
}
次に、DllExport.batをダウンロードして、DllExport.batをC# DLLのslnファイルと同じ階層に配置する。
続いて、コマンドプロンプトを開いて以下のコマンドを実行して、.NET DLLExportを起動する。
DllExport.bat -action Configure
.NET DLLExportダイアログにて、[Installed]チェックボックスにチェックを入力して、[Apply]ボタンを押下する。
最後に、C# DLLのプロジェクトをリビルドすると、作成した関数がエクスポートされる。
C++/CLIのラッパープロジェクトを使用する方法
C#ライブラリの作成
まず、C#ライブラリを作成する。
// CSharpDLL.cs
namespace CSharpDLL
{
public static class CSharpDLLClass
{
public static void ShowValue(ref int value)
{
DialogResult result = MessageBox.Show("C# Message Box", "C# Message Box", MessageBoxButtons.OKCancel);
if (result == DialogResult.OK)
{
value = 1;
}
else
{
value = 2;
}
return;
}
}
}
C++/CLIライブラリの作成
次に、C++/CLIライブラリを作成する。
ソリューションエクスプローラからC++/CLIプロジェクトを右クリックして[参照の追加]を選択、上記で作成したC# DLLファイルを追加する。
また、Visual C++のプロジェクト設定を開いて、[構成プロパティ] - [全般] - [共通言語ランタイム サポート (/clr)]に変更する。
同様に、[構成プロパティ] - [C/C++] - [プリプロセッサ] - [プリプロセッサの定義]項目に、DLL
プリプロセッサを追加する。
// CppCLIDLL.cpp
#include "stdafx.h"
#include "CppCLIDLL.h"
using namespace System;
using namespace System::Reflection;
using namespace CSharpDLL;
namespace CppCLIDll
{
public ref class CppCLIClass
{
public:void ShowCSharpMessageBox(int *value)
{
CSharpDLLClass::ShowValue(*value);
return;
}
};
}
void ShowMessageBox(int *value)
{
CppCLIDll::CppCLIClass clsCLI;
clsCLI.ShowCSharpMessageBox(value);
}
// CppCLIDLL.h
#pragma once
#ifdef DLL
__declspec(dllexport) void ShowMessageBox(int *value);
#else
__declspec(dllimport) void ShowMessageBox(int *value);
#endif
C++実行バイナリの作成
C++実行バイナリのプロジェクトを作成する。
暗黙的リンクを行う場合
メニューバーから[Visual C++のプロジェクト設定]を選択して、[構成プロパティ] - [C/C++] - [全般] - [追加のインクルードディレクトリ]項目に、
CppCLIDLL.hが存在するディレクトリを追加する。
同様に、[構成プロパティ] - [リンカー] - [全般] - [追加のライブラリディレクトリ]項目に、CppCLIDLL.libが存在するディレクトリを追加する。
さらに、[構成プロパティ] - [リンカー] - [入力] - [追加の依存ファイル]項目に、CppCLIDLL.libを追加する。
// CppEXE.cpp
#include "stdafx.h"
#include <windows.h>
#include "CppEXE.h"
#include "CppCLIDLL.h"
int _tmain()
{
int result = 0;
ShowMessageBox(&result);
if (result == 1)
{
printf("Ok Was Pressed \n");
printf("%d\n", result);
}
else if (result == 2)
{
printf("Cancel Was Pressed \n");
printf("%d\n", result);
}
else
{
printf("Unknown result \n");
}
system("pause");
return 0;
}
明示的リンクを行う場合
- まず、
LoadLibrary
関数を使用して、C++/CLIライブラリを読み込む。 - 次に、
GetProcAddress
関数を使用して、C++/CLIライブラリ内の関数オブジェクトのアドレスを取得する。 - C++/CLIライブラリの関数を呼び出す。
// CppEXE.cpp
#include "stdafx.h"
#include <windows.h>
#include "CppEXE.h"
using fnShowMessageBox void(*)(int*);
int _tmain()
{
int result = 0;
// C++/CLIライブラリを呼び出す
auto hModule = ::LoadLibrary(L"CppCLIDLL.dll");
if (NULL == hModule) {
return -1;
}
// C++/CLIライブラリの関数を読み込む
auto ShowMessageBox = reinterpret_cast<fnShowMessageBox>(::GetProcAddress(hModule, "ShowMessageBox"));
// C++/CLIライブラリの関数を実行する
ShowMessageBox(&result);
if (result == 1) {
std::cout << "Ok Was Pressed" << std::endl;
std::cout << result << std::endl;
}
else if (result == 2) {
std::cout << "Cancel Was Pressed" << std::endl;
std::cout << result << std::endl;
}
else {
std::cout << "Unknown result" << std::endl;
}
system("pause");
return 0;
}
Monoを使用する場合
Monoのインストール
- RHEL
- インストール - Mono(RHEL)を参照して、Monoをインストールする。
- インストール - Mono(RHEL)を参照して、Monoをインストールする。
- SUSE
- インストール - Mono(SUSE)を参照して、Monoをインストールする。
- インストール - Mono(SUSE)を参照して、Monoをインストールする。
- Windows
- Monoの公式Webサイトにアクセスして、[Download Mono 64-bit (no GTK#)]を選択して、Monoをダウンロードする。
- Monoをインストールする。
- 必要ならば、スタートメニューのMonoプログラムグループ下に[Monoコマンドプロンプトを開く]ショートカットを作成する。
- このショートカットは、Mono関連のパス情報がすでに設定されたコマンドシェルを起動するものである。
C#ライブラリの作成
以下の例では、SampleLibraryという名前のライブラリを作成している。
using System;
namespace SampleLibrary
{
public class SampleClass
{
public void SampleMethod(int intValue, string stringValue)
{
Console.WriteLine($"Received int: {intValue}, string: {stringValue}");
}
}
}
C++実行ファイルの作成
C#ライブラリのメソッドを呼び出すmono_runtime_invoke
関数に指定する引数を、以下に示す。
- 第1引数 :
void**
型- C#のメソッドがインスタンスメソッドである場合、メソッドを呼び出すインスタンスのポインタを指定する。
- Staticメソッドの場合は、
nullptr
を指定する。
- 第2引数 :
void**
型- メソッドに渡す引数を指定する。
- 引数がある場合、各引数の値へのポインタが配列として渡される。
- 引数が無い場合は、
nullptr
を指定する。
- exc :
MonoObject**
型- エラーが発生した場合に、エラー情報を格納する
MonoObject
型のポインタを指定する。 - エラーを取得しない場合は、
nullptr
を指定する。
- エラーが発生した場合に、エラー情報を格納する
#include <iostream>
#include <mono/jit/jit.h>
#include <mono/metadata/assembly.h>
#include <mono/metadata/debug-helpers.h>
int main()
{
// ドメインの初期化
MonoDomain* domain = mono_jit_init("SampleApp");
// C#ライブラリのアセンブリの読み込み
MonoAssembly* assembly = mono_domain_assembly_open(domain, "SampleLibrary.dll");
// C#ライブラリのクラスとメソッドの取得
MonoImage *image = mono_assembly_get_image(assembly);
const char *className = "SampleLibrary.SampleClass"; // "<名前空間名>.<クラス名>"を入力する
const char *methodName = "SampleMethod"; // メソッド名を入力する
MonoClass *klass = mono_class_from_name(image, "", className);
MonoMethod *method = mono_class_get_method_from_name(klass, methodName, 0);
// インスタンスの作成
MonoObject *instance = mono_object_new(domain, klass);
mono_runtime_object_init(instance);
// C#ライブラリのメソッドに引数がある場合 (以下の例では、第1引数 : int型、第2引数 : string型)
int intValue = 42;
const char *stringValue = "Hello from C++";
// 引数を格納する配列
void *params[] = {
&intValue, // int型へのポインタ
mono_string_new(domain, stringValue) // string型へのポインタ
};
// C#ライブラリのメソッドに引数がない場合
void *params[] = { nullptr };
// エラー情報が格納される変数
MonoObject *exc = nullptr;
// C#ライブラリのメソッドの呼び出し
// C#ライブラリのStaticメソッド(引数あり)を呼び出す場合 --> 例: mono_runtime_invoke(method, nullptr, params, nullptr);
// C#ライブラリのStaticメソッド(引数なし)を呼び出す場合 --> 例: mono_runtime_invoke(method, nullptr, nullptr, nullptr);
// C#ライブラリのStaticメソッド(引数あり)、かつ、エラー情報不要で呼び出す場合 --> 例: mono_runtime_invoke(method, nullptr, params, nullptr);
mono_runtime_invoke(method, instance, params, &exc);
// エラーの確認
if (exc != nullptr) {
// エラー情報の取得
MonoClass *excClass = mono_object_get_class(exc);
MonoMethod *toStringMethod = mono_class_get_method_from_name(excClass, "ToString", 0);
MonoString *errorMessage = (MonoString*)mono_runtime_invoke(toStringMethod, exc, nullptr, nullptr);
// エラーメッセージの出力
const char *pstrErrMessage = mono_string_to_utf8(errorMessage);
std::cout << "Error: " << pstrErrMessage << std::endl;
mono_free(pstrErrMessage);
}
// ドメインの解放
mono_jit_cleanup(domain);
return 0;
}
引数
ポインタを渡す場合
C#ライブラリ側でデータ型のポインタを受け取り、その変数の値を変更しても、その変更がC++実行ファイル側に反映されることは期待できない。
これは、C#とC++が異なるメモリ管理とランタイム環境を持っており、それぞれ独自のメモリ管理を行うためである。
C#では、ガベージコレクションが行われ、メモリの確保や解放はCLR (Common Language Runtime) によって管理されている。
一方、C++では開発者が手動でメモリの確保と解放を行う。
したがって、C#ライブラリ内でデータ型のポインタを受け取り、その変数の値を変更したとしても、それはC#ランタイムの管理するメモリ内で行われることになる。
C++実行ファイル側では、C#ランタイムのメモリに直接アクセスできないため、その変更が反映されることはない。
異なるランタイム環境でのメモリ管理の違いからくる制約を考慮して、C++とC#間でデータの受け渡しを行う場合は、適切な手法やデータ構造を選択する必要がある。
例えば、C#ライブラリ側で変更可能な値を戻り値として返し、それをC++で受け取る等の方法がある。
戻り値がある場合
戻り値がint型の場合
C#ライブラリが戻り値を返す場合は、mono_runtime_invoke
関数でC#のメソッドを呼び出して、戻り値をMonoObject*
型で受け取る。
その後、mono_field_get_value
関数を使用して、MonoObject*
型のオブジェクトからint型の戻り値を取得する。
※注意
mono_runtime_invoke
関数の戻り値はMonoObject*
型であるため、実際のint型のデータは内部的にはm_value
と呼ばれる領域に格納されていることである。
これにより、int型の戻り値を取り出すことができる。
// C#ライブラリがint型の戻り値を返す場合
MonoObject *pResult = mono_runtime_invoke(method, instance, params, &exc);
// エラーの確認
if (exc != nullptr) {
// エラー情報の取得
// ...略
}
else
{
// int型に変換
int returnValue;
mono_field_get_value(pResult, mono_class_get_field_from_name(mono_object_get_class(result), "m_value", "System.Int32"), &returnValue);
// 結果を出力
std::cout << "Method result: " << returnValue << std::endl;
}
戻り値がstring型の場合
以下の例では、mono_runtime_invoke
関数でC#のメソッドを呼び出して、戻り値をMonoObject*
型で受け取る。
次に、reinterpret_cast
を使用してMonoObject*
型をMonoString*
に変換する。
最後に、mono_string_to_utf8
関数を使用してstring
型の戻り値を取得している。
// C#ライブラリがstring型の戻り値を返す場合
MonoObject *pResult = mono_runtime_invoke(method, instance, params, &exc);
// エラーの確認
if (exc != nullptr) {
// エラー情報の取得
// ...略
}
else
{
// string型に変換
MonoString *pResultString = reinterpret_cast<MonoString*>(pResult);
const char *stringValue = mono_string_to_utf8(pResultString);
// 結果を出力
std::cout << "Method result: " << stringValue << std::endl;
// メモリの解放
mono_free(stringValue);
}
戻り値がタプル型の場合
タプル型において、C#からC++に直接渡すことは難しいため、複数の戻り値を配列や構造体で受け取る必要がある。
もし可能であれば、代わりに複数の引数を使用して値を取得することを検討することもできる。
以下の例では、タプル型 (int, string) の戻り値を配列として受け取り、各要素を取り出している。
ただし、戻り値の型や順序に依存するため、メソッドが変更されると対応が必要となることに注意する。
// C#ライブラリがタプル型の戻り値を返す場合
MonoObject *pResult = mono_runtime_invoke(method, instance, params, &exc);
// エラーの確認
if (exc != nullptr) {
// エラー情報の取得
// ...略
}
else
{
// タプル型から個々の値を取得
MonoArray *pResultArray = reinterpret_cast<MonoArray*>(pResult);
TupleResult tupleResult;
tupleResult.intValue = *reinterpret_cast<int*>(mono_array_addr(pResultArray, int, 0));
tupleResult.stringValue = mono_string_to_utf8(reinterpret_cast<MonoString*>(mono_array_addr(pResultArray, MonoString*, 1)));
// 結果を出力
std::cout << "Method result: " << tupleResult.intValue << ", " << tupleResult.stringValue << std::endl;
// メモリの解放
mono_free(tupleResult.stringValue);
}
C# DLLをCOM参照可能にしてC++ EXEから使用する方法
まず、C# DLLプロジェクトを作成して、アセンブリ情報を設定する。
- C# DLLプロジェクトのプロパティを開く。
- プロパティ画面左にある[アプリケーション]タブを選択して、[アセンブリ情報]ボタンを押下する。
- [アセンブリをCOM参照可能にする]チェックボックスにチェックを入力して、[OK]ボタンを押下する。
次に、ビルドの設定を行う。
- C# DLLプロジェクトのプロパティを開く。
- プロパティ画面左にある[ビルド]タブを選択して、[COM相互運用機能の登録]チェックボックスにチェックを入力する。
- C# DLLプロジェクトのプロパティを保存する。
※注意1
[COM相互運用機能の登録]は、regasm
コマンドによるCOMのレジストリ登録を、ビルド時に自動で行う機能と思われる。
そのため、開発時(デバッグ時)は有効にした方が便利であるが、インストール時はCOMのレジストリ登録が自動で行われないため注意すること。
※注意2
Windowsにログインしているアカウントの権限によっては、ビルド時にレジストリへの登録に失敗するエラーが発生することがある。
その時は、Visual Studioを管理者権限で実行すれば登録できる。
C# DLLでは、以下のような内容のソースコードを記述する。
この時、C++ EXE側から呼ぶクラスには、以下の属性を付加する。
- ComVisible
- ClassInterface
- Guid (Visual Studioの[ツール]メニューバー - [GUIDの作成]を選択する)
// CSharpCOMDLL.csファイル
using System;
using System.Runtime.InteropServices;
namespace CSharpCOMDLL
{
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
[Guid("85555B74-E2E0-4493-9869-3CE95F13CB99")] // Visual Studioの[ツール]メニューバー - [GUIDの作成]を選択する
public class CSharpCOMDLLClass
{
public Int32 Add(Int32 iParam1, Int32 iParam2)
{
int iRet = iParam1 + iParam2;
return (Int32)iRet;
}
public Int32 AddStr([MarshalAs(UnmanagedType.BStr)]string str) // 文字列を指定する場合はマーシャリングする
{
Console.WriteLine(str);
return (Int32)0;
}
}
}
C++ EXEプロジェクトを作成して、以下のような内容のソースコードを記述する。
// main.cppファイル
#include <iostream>
#include <Windows.h>
IDispatch *pIDisp = NULL;
IUnknown *pIUnk = NULL;
long Init(void);
long Finalize(void);
long AddInt(long p_Number1, long p_Number2);
long AddStr();
int main()
{
// COMの初期化処理
Init();
// C# DLLのメソッドを呼ぶ
int l_Result = Add(300, 500);
//後処理
Finalize();
printf("Calc Result : %d", l_Result);
return 0;
}
// 初期化関数
long Init(void)
{
// COMの初期化
::CoInitialize(NULL);
// ProcIDからCLSIDを取得(ネームスペース名.クラス名)
CLSID clsid;
HRESULT h_result = CLSIDFromProgID(L"CSharpCOMDLL.CSharpCOMDLLClass", &clsid); // 第1引数は、呼び出すC# DLLの<名前空間名>.<クラス名>にすること
if (FAILED(h_result))
{
return -1;
}
// インスタンスの生成
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;
}
// COMの終了処理
long Finalize()
{
// インスタンスの開放
pIDisp->Release();
// インターフェイスの開放
pIUnk->Release();
// COMの開放
::CoUninitialize();
return 0;
}
// C# DLLのメソッドを呼ぶ
long AddInt(long p_Number1, long p_Number2)
{
// メソッド名からID(DISPID)を取得(関数名の設定)
DISPID dispid = 0;
OLECHAR *Func_Name[] = { SysAllocString (L"Add") }; // C# DLLの呼び出すメソッド名を指定する
HRESULT h_result = pIDisp->GetIDsOfNames(IID_NULL, Func_Name, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
if (FAILED(h_result))
{
return -1;
}
// メソッドに渡すパラメータを作成(DISPPARAMS、 VariantInit等)
DISPPARAMS params = {0};
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);
// C# DLLのメソッドを呼ぶ(pIDisp->Invoke)
pIDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, ¶ms, &vRet, NULL, NULL);
delete[] pVarg;
return vRet.lVal;
}
long AddStr()
{
// メソッド名からID(DISPID)を取得(関数名の設定)
DISPID dispid = 0;
OLECHAR *Func_Name[] = { SysAllocString (L"AddStr") };
HRESULT h_result = pIDisp->GetIDsOfNames(IID_NULL, Func_Name, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
if (FAILED(h_result))
{
return -1;
}
// メソッドに渡すパラメータを作成(DISPPARAMS、 VariantInit等)
DISPPARAMS params = {0};
params.cNamedArgs = 0;
params.rgdispidNamedArgs = NULL;
params.cArgs = 1; // 呼び出すメソッドの引数の数
// 引数の指定 (順番が逆になることに注意すること)
VARIANT var;
DISPPARAMS dispParams;
var.vt = VT_BSTR; // 引数に渡すデータ型をBSTRにする
var.bstrVal = SysAllocString(L"あいうえお"); // 引数に渡す文字列
dispParams.cArgs = 1;
dispParams.rgvarg = &var;
dispParams.cNamedArgs = 0;
dispParams.rgdispidNamedArgs = NULL;
VARIANT vRet;
VariantInit(&vRet);
// C# DLLのメソッドを呼ぶ(pIDisp->Invoke)
printf("[OK] Invoke start\r\n");
h_result = pIDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dispParams, &vRet, NULL, NULL);
if (FAILED(h_result))
{
printf("[NG] Invoke failed\r\n");
return -2;
}
return vRet.lVal;
}
管理者権限でPowerShellまたはコマンドプロンプトを実行する。
次に、regasm
コマンドを使用して、C# DLL(COM)を登録する。
C# DLLのプラットフォーム(x86/x64)により、x86/x64向けのregasm
と合致させる必要があることに注意する。
また、regasm.exeをC++ EXE側のプロジェクトディレクトリにコピーして実行しても構わない。
# プロジェクトがx86の場合 C:\Windows\Microsoft.NET\Framework\<.NETのバージョン>\regasm /codebase <C# DLLのファイル名>.dll
# プロジェクトがx64の場合 C:\Windows\Microsoft.NET\Framework64\<.NETのバージョン>\regasm /codebase <C# DLLのファイル名>.dll
C++ EXEを実行する場合、以下の内容のバッチファイルを作成して、管理者権限で実行する。
常にregasm
コマンドを実行する場合、C# DLLプロジェクトの[COM相互運用機能の登録]の設定は不要である。
ただし、デバッグ時においては有効にした方が便利である。
rem exerun.batファイル
@echo off
cd %~dp0
regasm /codebase <C# DLLのファイル名>.dll
start /wait <C++ EXEのファイル名>.exe
echo exeからの戻り値は %ERRORLEVEL% です
pause
下表に、C#のデータ型、C++のデータ型、VARTYPEの関係を示す。
C++から引数を指定する場合、および、C# DLLからの戻り値を取得するために、VARIANT型を使用する必要がある。
そのため、C#、C++、VARIANT型の関係を理解する必要がある。
C++ | C# | VARTYPE | 使用するメンバ |
---|---|---|---|
SHORT (short) | short (System.Int16) | VT_I2 | iVal |
INT (int) LONG (long) |
int (System.Int32) | VT_I4 | lVal |
BOOL (long) | bool (System.Boolean) | VT_BOOL | boolVal |
LPCSTR (const char *) LPCWSTR (const wchar_t *) |
string (System.String) | VT_BSTR | bstrVal |
FLOAT (float) | float (System.Single) | VT_R4 | fltVal |
DOUBLE (double) | double (System.Double) | VT_R8 | dblVal |
※注意3
- C# DLLがx64の場合、C++ EXEもx64でビルドする必要がある。
同様に、C++ EXE -> C# COM DLL(ラッパーDLL) -> C# DLLとする場合も、ラッパーDLLはx64である必要がある。 - C# DLLがx86の場合、C++ EXEもx86でビルドする必要がある。
同様に、C++ EXE -> C# COM DLL(ラッパーDLL) -> C# DLLとする場合も、ラッパーDLLはx86である必要がある。
デバッグ
C++プロジェクトからC#プロジェクトに対するデバッグを有効にする手順を記載する。
- [ソリューションエクスプローラー]に表示されているC++プロジェクトを右クリックして、[プロパティ]を選択する。
- [<プロジェクト名> プロパティページ]画面にて、[構成プロパティ] - [デバッグ]を選択する。
- [デバッガーの種類]項目を、[混合]または[自動]に設定して、[OK]ボタンを押下する。