もくじ
→https://tera1707.com/entry/2022/02/06/144447
やりたいこと
C++で作ったDLLを、C#で使いたい。
参考
呼び出し規約の種類(cdeclとか、stdcallとか)
https://konuma.org/blog/2006/01/02/post_1fd3/
extern "C" とは?
https://konuma.org/blog/2006/01/04/post_144e/
DLLを静的リンクで呼び出す
http://yamatyuu.net/computer/program/sdk/base/static_dll/index.html
C++側(呼ばれる側=DLL)
ヘッダーファイル
__declspec(dllexport) と __declspec(dllimport)
マクロを使用して、エクスポートする側(関数の実装を書いているcppにインクルードするとき)には「declspec(dllexport)」を書く。
インポートする側(dllを使う側のcppでインクルードするとき)には、「declspec(dllimport)」を書く。
→詳細は、下記を参照
https://konuma.org/blog/2006/01/02/post_1fd3/
extern "C"
通常、DLLを作成するときは、C形式のDLLとして作成する。
C形式:マングリングが行われない
C++形式:マングリングが行われる
→詳細は、下記を参照
https://konuma.org/blog/2006/01/04/post_144e/
#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
// エクスポート関数のプロトタイプ宣言
// 通常、__stdcallを適用する(__stdcall = WINAPI)。
VC_DLL_EXPORTS void __cdecl Test_MyApi();
VC_DLL_EXPORTS void __cdecl Test_MyApi2(const wchar_t* lpText, const wchar_t* lpCaption);
VC_DLL_EXPORTS void __cdecl Test_MyApi3(int count);
cppファイル
#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
#define VC_DLL_EXPORTS
#include "DllTest.h"
// エクスポート関数の実装
void __cdecl Test_MyApi()
{
const wchar_t* lpText = L"Test\0";
wprintf_s(lpText);
}
void __cdecl Test_MyApi2(const wchar_t* lpText, const wchar_t* lpCaption)
{
// MessageBoxを呼び出すだけ。
wprintf_s(L"%s", lpText);
wprintf_s(L"%s", L" ");
wprintf_s(L"%s", lpCaption);
}
void __cdecl Test_MyApi3(int count)
{
for (int i = 0; i < count; i++)
{
wprintf_s(L"%d,", i);
}
}
C#側(呼ぶ側)
using System.Runtime.InteropServices;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
NativeMethods.Test_MyApi(1);
NativeMethods.Test_MyApi2("テキスト", "キャプション");
NativeMethods.Test_MyApi3(3);
}
}
public static class NativeMethods
{
[DllImport("DllTest.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public extern static void Test_MyApi(int param);
[DllImport("DllTest.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public extern static void Test_MyApi2(string lpText, string lpCaption);
// 下記でも、うまく動く。(CharSetの代わりにMarshallAsでLPWSTRを指定する)
//[DllImport("DllTest.dll", CallingConvention = CallingConvention.Cdecl)]
//public extern static void Test_MyApi2([MarshalAs(UnmanagedType.LPWStr)]string lpText, [MarshalAs(UnmanagedType.LPWStr)]string lpCaption);
[DllImport("DllTest.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public extern static void Test_MyApi3(int count);
}
}
不明点
- C#側で、C++の関数に文字列を渡すとき、stringで渡してwchar_tで受けてるが、本当にこれでOK?(C#のstringは、C++の何の型にあたる??)
→albireo様にコメント頂きました。
C++の文字列変数は「メモリ上のアドレス」を指している。
C#の文字列はマネージドメモリ上を参照しているため、特定のメモリアドレスを持っていない。
C++側に文字列型を渡すときは、「マーシャリング」という変換処理を自動で行う。
「[MarshalAs(UnmanagedType.LPWStr)]」などで、マーシャリングの方法をコンパイラに指定する。
コード