首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 呼叫 C++ DLL 完整實作:從建立專案到 P/Invoke 封送處理

     編輯:關於C#

環境與工具

  • Visual Studio 2010
  • Win32 DLL 專案 (C++)
  • C# Console 應用程式

1. 建立 C++ DLL 專案

  1. 新增專案 → Visual C++ → Win32 Project → 名稱:CSInvokeCDll
  2. 應用程式精靈:選擇 DLL,勾選 匯出符號

2. C++ 實作代碼 (CSInvokeCPP.cpp)


#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):明確匯出
  • 陣列參數用指標 + 長度
  • 結構體記憶體由 DLL 配置,必須提供釋放函式,避免跨堆疊釋放崩潰

3. C# 封送處理包裝類 (CPPAdapter.cs)


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); // 立即釋放非託管記憶體
        }
    }
}

4. 部署與測試

  1. 編譯 C++ DLL (Release x86/x64 需與 C# 專案平台一致)
  2. 複製 CSInvokeCDll.dll 至 C# 專案 bin/Debug (或設定建置後事件自動複製)
  3. C# 專案設定 Platform target:x86 (32-bit DLL) 或 x64 (64-bit DLL),勿選 Prefer 32-bit 若用 64-bit DLL

5. 測試代碼


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
BadImageFormatException32/64 位元不匹配C# 專案 Platform target 與 DLL 位元數一致
AccessViolationException結構體佈局錯誤、記憶體跨堆疊釋放、指標無效StructLayout.Sequential、正確 MarshalAs、提供/呼叫釋放函式
中文亂碼CharSet 不匹配C++ 用 char* (ANSI) → C# CharSet.Ansi;或雙方用 Unicode
陣列內容不正確封送方向錯誤輸入陣列 [In],輸出陣列 [Out],雙向 [In, Out]

進階主題

  • 回呼函式:delegate + Marshal.GetFunctionPointerForDelegate,注意委派實例存活期
  • C++ 類別封裝:建立 C 風格介面 (建立/銷毀/方法) 包裝 C++ 類別
  • COM 互通:若 DLL 為 COM 元件,用 tlbimp 或 TypeLib 產生 Interop 組件
  • .NET Core / 5+:NativeLibrary.Load、LibraryImport (Source Generator) 簡化 P/Invoke

掌握 C++ DLL 建立與 C# P/Invoke 封送處理,是跨語言整合舊有高效能/硬體相關 C++ 庫的關鍵技能。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved