CSInvokeCDll
#include "stdafx.h"
#include
// 簡單函式
extern "C" __declspec(dllexport) int Add(int x, int y) { return x + y; }
extern "C" __declspec(dllexport) int Sub(int x, int y) { return x - y; }
extern "C" __declspec(dllexport) int Mul(int x, int y) { return x * y; }
extern "C" __declspec(dllexport) int Div(int x, int y) { return y != 0 ? x / y : 0; }
// 陣列求和
extern "C" __declspec(dllexport) int Sum(int* arr, int len)
{
int sum = 0;
if (!arr) return 0;
for (int i = 0; i < len; i++) sum += arr[i];
return sum;
}
// 結構體建立 (呼叫端需釋放)
typedef struct { char name[32]; int age; } User;
extern "C" __declspec(dllexport) User* CreateUser(const char* name, int age)
{
User* u = (User*)malloc(sizeof(User));
if (!u) return NULL;
strncpy_s(u->name, 32, name, _TRUNCATE);
u->age = age;
return u;
}
// 釋放結構體 (必要!)
extern "C" __declspec(dllexport) void FreeUser(User* u)
{
if (u) free(u);
}
extern "C":防止 C++ 名稱修飾,確保匯出名稱可被 C# 找到__declspec(dllexport):明確匯出
using System;
using System.Runtime.InteropServices;
public class CPPAdapter
{
const string DllName = "CSInvokeCDll.dll";
// 簡單類型
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
public static extern int Add(int x, int y);
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
public static extern int Sub(int x, int y);
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
public static extern int Mul(int x, int y);
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
public static extern int Div(int x, int y);
// 陣列:自動封送為指標
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
public static extern int Sum([In] int[] arr, int len);
// 結構體:LayoutKind.Sequential 對應 C 結構體記憶體佈局
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct User
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string Name;
public int Age;
}
// 回傳指標,C# 端手動封送
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr CreateUser(string name, int age);
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
private static extern void FreeUser(IntPtr userPtr);
// 友善包裝
public static User CreateUserSafe(string name, int age)
{
var ptr = CreateUser(name, age);
if (ptr == IntPtr.Zero) throw new OutOfMemoryException();
try
{
return Marshal.PtrToStructure(ptr);
}
finally
{
FreeUser(ptr); // 立即釋放非託管記憶體
}
}
}
CSInvokeCDll.dll 至 C# 專案 bin/Debug (或設定建置後事件自動複製)
class Program
{
static void Main()
{
Console.WriteLine($"Add: {CPPAdapter.Add(10, 5)}"); // 15
Console.WriteLine($"Sub: {CPPAdapter.Sub(10, 5)}"); // 5
Console.WriteLine($"Mul: {CPPAdapter.Mul(10, 5)}"); // 50
Console.WriteLine($"Div: {CPPAdapter.Div(10, 5)}"); // 2
var arr = new[] { 1, 2, 3, 4, 5 };
Console.WriteLine($"Sum: {CPPAdapter.Sum(arr, arr.Length)}"); // 15
var user = CPPAdapter.CreateUserSafe("張三", 25);
Console.WriteLine($"User: {user.Name}, Age: {user.Age}");
}
}
| 問題 | 原因 | 解決 |
|---|---|---|
| EntryPointNotFoundException | 名稱修飾、大小寫、CallingConvention 不匹配 | 用 dumpbin /exports DLL 確認實際匯出名稱;統一 Cdecl |
| BadImageFormatException | 32/64 位元不匹配 | C# 專案 Platform target 與 DLL 位元數一致 |
| AccessViolationException | 結構體佈局錯誤、記憶體跨堆疊釋放、指標無效 | StructLayout.Sequential、正確 MarshalAs、提供/呼叫釋放函式 |
| 中文亂碼 | CharSet 不匹配 | C++ 用 char* (ANSI) → C# CharSet.Ansi;或雙方用 Unicode |
| 陣列內容不正確 | 封送方向錯誤 | 輸入陣列 [In],輸出陣列 [Out],雙向 [In, Out] |
delegate + Marshal.GetFunctionPointerForDelegate,注意委派實例存活期tlbimp 或 TypeLib 產生 Interop 組件NativeLibrary.Load、LibraryImport (Source Generator) 簡化 P/Invoke掌握 C++ DLL 建立與 C# P/Invoke 封送處理,是跨語言整合舊有高效能/硬體相關 C++ 庫的關鍵技能。