C++包成DLL給C#呼叫這點,網路上已經有很多相關文章了,可惜那些文章都只是傳遞基礎的int, float, double等,缺乏指標資料的傳遞方式,因此來做個紀錄方便自己的後續回顧以及幫助他人
以下以Visual Studio 2022 Community做示範
開啟VS,選擇建立新的專案
選擇C++、Windows、程式庫 > 動態連結程式庫(DLL)
選擇喜歡的位置儲存
將專案修改為Release版本(因為Debug性能很差),然後選擇目標平台(x86或x64)
對方案右鍵 > 屬性
C/C++ > 前置處理器 > 前置處理器定義 > 新增一個定義名(CPP_DLL_EXPORT),名稱可自定義
並且新增_CRT_SECURE_NO_WARNINGS這個定義,關掉VS的C4996警告
在方案總管 > 專案 > 標頭檔右鍵 > 加入 > 新增項目 > 標頭檔,建立一個名為DllTest.h的檔案
因為DllTest.h內有#pragma once,所以要把DllTest.h加入到pch.h中
打開DllTest.h,輸入以下程式碼
以下程式碼示範了int, float, string(const char pointer), struct資料的傳輸
#pragma once
#ifdef CPP_DLL_EXPORT
#define CPP_DLL_API __declspec(dllexport)
#else
#define CPP_DLL_API __declspec(dllimport)
#endif
namespace NS1
{
namespace NS2
{
#pragma pack(1)
typedef struct
{
int value1;
float value2;
} MyValue;
CPP_DLL_API int AddInt(int a, int b);
CPP_DLL_API float AddFloat(float a, float b);
CPP_DLL_API void PrintString(const char* str);
CPP_DLL_API const char* StringMerge(const char* str1, const char* str2);
CPP_DLL_API int* SetArray(int size);
CPP_DLL_API void SetStruct(MyValue* value);
}
}
namespace非必要,不過這個只是為了做教學示範,所以就把它也加進來了~
在方案總管 > 專案 > 資源檔右鍵 > 加入 > 新增項目 > 標頭檔,建立一個名為DllTest.cpp的檔案
打開DllTest.cpp,輸入以下程式碼
#include "pch.h"
#include <string.h> //strcat
#include <stdlib.h> //malloc
#include <iostream> //cout
int NS1::NS2::AddInt(int a, int b)
{
return a + b;
}
float NS1::NS2::AddFloat(float a, float b)
{
return a + b;
}
void NS1::NS2::PrintString(const char* str)
{
std::cout << str << std::endl;
}
const char* NS1::NS2::StringMerge(const char* str1, const char* str2)
{
char* res;
res = (char*)malloc(sizeof(char) * (strlen(str1) + strlen(str2)));
strcat(res, str1);
strcat(res, str2);
return const_cast<const char*>(res);
}
int* NS1::NS2::SetArray(int size)
{
int* arr;
arr = (int*)malloc(sizeof(int) * size);
for (int i = 0; i < size; i++) arr[i] = i + 1;
return arr;
}
void NS1::NS2::SetStruct(NS1::NS2::MyValue* value)
{
value->value1 = 10;
value->value2 = 20;
}
點擊建置 > 建置方案(或者快捷鍵Ctrl + Shift + B)
到專案的輸出資料夾(CppDLL/x64/Release),將CppDLL.dll複製到桌面上
接著打開Terminal(cmd或pwoershell皆可),使用cd切換到Visual Studio 2022 Community的工具資料夾內
並使用dumpbin.exe工具查詢封裝後的DLL函數名稱
cd 'C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.34.31933\bin\Hostx64\x64'
dumpbin.exe /EXPORTS C:\Users\yuyang\Desktop\CppDLL.dll
上圖中等號左邊的就是函數名稱,這些名稱請記住,稍後會在C#中用到
一樣開啟VS,選擇建立新的專案 > 選擇C#、Windows、主控台、.Net Framework
然後將專案切換為和DLL相同的平台(x86或x64),切換完畢後先偵錯 > 開始偵錯(或者快捷鍵F5),讓系統創建好資料夾路徑
將前面放到桌面的那個DLL放到系統創建好的資料夾中
接著開始編寫程式碼
程式中MethodName內的資訊請改為dumpbin得到的字串資料
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace CallDLL
{
internal class Program
{
private const string DLL_PATH = "CppDLL.dll";
//使用dumpbin.exe取得的函數名
private struct MethodName
{
public const string AddInt = "?AddInt@NS2@NS1@@YAHHH@Z";
public const string AddFlost = "?AddFloat@NS2@NS1@@YAMMM@Z";
public const string PrintString = "?PrintString@NS2@NS1@@YAXPEBD@Z";
public const string SetArray = "?SetArray@NS2@NS1@@YAPEAHH@Z";
public const string SetStruct = "?SetStruct@NS2@NS1@@YAXPEAUMyValue@12@@Z";
public const string StringMerge = "?StringMerge@NS2@NS1@@YAPEBDPEBD0@Z";
}
[StructLayout(LayoutKind.Sequential)]
struct MyValue
{
public int value1;
public float value2;
}
static void Main(string[] args)
{
Console.WriteLine(AddInt(10, 20));
Console.WriteLine(AddFloat(11.1f, 22.2f));
PrintString("Hello~ PrintString");
unsafe
{
IntPtr ptrStr = StringMerge("Hello~ ", "StringMerge");
string result = Marshal.PtrToStringAnsi(ptrStr);
Console.WriteLine(result);
Marshal.FreeCoTaskMem(ptrStr);
}
IntPtr ptrArray = SetArray(5);
int[] array = new int[5];
Marshal.Copy(ptrArray, array, 0, array.Length);
foreach(var value in array) Console.WriteLine(value);
MyValue mv;
SetStruct(out mv);
Console.WriteLine(mv.value1);
Console.WriteLine(mv.value2);
Console.ReadKey();//pause terminal
}
//C#的函數名稱可以和C++那邊的函數名稱不同,但傳入和傳出的參數數量與類型必須相同
//函數前面必須使用static extern (與此程式是否在static void Main內被呼叫無關)
[DllImport(DLL_PATH, EntryPoint=MethodName.AddInt)]
private static extern int AddInt(int a, int b);
[DllImport(DLL_PATH, EntryPoint=MethodName.AddFlost)]
private static extern float AddFloat(float a, float b);
[DllImport(DLL_PATH, EntryPoint=MethodName.PrintString)]
private static extern void PrintString(string s);
[DllImport(DLL_PATH, EntryPoint=MethodName.StringMerge)]
unsafe private static extern IntPtr StringMerge(string str1, string str2);
[DllImport(DLL_PATH, EntryPoint = MethodName.SetArray)]
private static extern IntPtr SetArray(int size);
[DllImport(DLL_PATH, EntryPoint = MethodName.SetStruct)]
private static extern void SetStruct(out MyValue value);
}
}
搞定~