C#程序如何調(diào)用C++?dll詳細教程
在使用C#開發(fā)客戶端時,有時需要調(diào)用C++ dll,本篇博客來介紹C#程序如何調(diào)用C++ dll。
一、創(chuàng)建C++ dll項目
首先使用VS2022創(chuàng)建C++ dll項目,具體步驟如下:
(1)選擇Windows桌面向?qū)?,點擊下一步, 取項目名,例如我的dll項目名是libMath
(2)選擇動態(tài)項目,勾選導(dǎo)出符號
(3)編寫動態(tài)代碼,代碼如下:
libMath.h
// 下列 ifdef 塊是創(chuàng)建使從 DLL 導(dǎo)出更簡單的 // 宏的標(biāo)準(zhǔn)方法。此 DLL 中的所有文件都是用命令行上定義的 LIBMATH_EXPORTS // 符號編譯的。在使用此 DLL 的 // 任何項目上不應(yīng)定義此符號。這樣,源文件中包含此文件的任何其他項目都會將 // LIBMATH_API 函數(shù)視為是從 DLL 導(dǎo)入的,而此 DLL 則將用此宏定義的 // 符號視為是被導(dǎo)出的。 #ifdef LIBMATH_EXPORTS #define LIBMATH_API __declspec(dllexport) #else #define LIBMATH_API __declspec(dllimport) #endif // 此類是從 dll 導(dǎo)出的 class LIBMATH_API ClibMath { public: ClibMath(); int Add(int a, int b); int Sub(int a, int b); }; // 由于需要給C#調(diào)用,為了方便導(dǎo)出類,添加了函數(shù)進行導(dǎo)出,并且需要加extern "C" extern "C" { LIBMATH_API ClibMath* CreateMyClass(); LIBMATH_API void DeleteMyClass(ClibMath* obj); LIBMATH_API int CallAdd(ClibMath* obj, int num1, int num2); LIBMATH_API int CallSub(ClibMath* obj, int num1, int num2); }
注意: 如果想導(dǎo)出C++類在C#中使用,由于語言語法差異,C++類在C#中無法使用,因為C++類通常包含成員函數(shù)、構(gòu)造函數(shù)、析構(gòu)函數(shù)等,而C#與C++在處理這些方面存在差異。一種可行的方法是在C++類中添加一些導(dǎo)出函數(shù),這樣它們可以通過C#調(diào)用。這些函數(shù)可以執(zhí)行類的實例化、調(diào)用成員函數(shù)等操作。確保使用 extern “C” 以避免名稱修飾, 因為C++函數(shù)在編譯時,會在原有的函數(shù)名前后添加一些符號,例如add函數(shù)在編譯后可能變成了@xxasd_sfdf_add_xxx之類的,但是使用extern "C"
后就是按照C語言的方式導(dǎo)出,函數(shù)名不變,例如我添加的一些類導(dǎo)出方法:
// 由于需要給C#調(diào)用,為了方便導(dǎo)出類,添加了函數(shù)進行導(dǎo)出,并且需要加extern "C" extern "C" { LIBMATH_API ClibMath* CreateMyClass(); LIBMATH_API void DeleteMyClass(ClibMath* obj); LIBMATH_API int CallAdd(ClibMath* obj, int num1, int num2); LIBMATH_API int CallSub(ClibMath* obj, int num1, int num2); }
libMath.cpp
// libMath.cpp : 定義 DLL 的導(dǎo)出函數(shù)。 // #include "framework.h" #include "libMath.h" // 這是已導(dǎo)出類的構(gòu)造函數(shù)。 ClibMath::ClibMath() { return; } int ClibMath::Add(int a, int b) { return a + b; } int ClibMath::Sub(int a, int b) { return a - b; } LIBMATH_API ClibMath* CreateMyClass() { return new ClibMath(); } LIBMATH_API void DeleteMyClass(ClibMath* obj) { delete obj; } LIBMATH_API int CallAdd(ClibMath* obj, int num1, int num2) { return obj->Add(num1, num2); } LIBMATH_API int CallSub(ClibMath* obj, int num1, int num2) { return obj->Sub(num1, num2); }
二、C#程序員調(diào)用C++ dll
生成dll后可以用命令拷貝到C#項目的exe目錄,或者手動拷貝,然后在C#代碼中使用import
導(dǎo)入即可,如下圖:
代碼如下:
/* C# 調(diào)用 C++ dll 將libMath.dll放到CSharpCallCppDLL/bin/Debug目錄下 */ using System.Runtime.InteropServices; namespace CSharpCallCppDLL { internal class Program { private static IntPtr myClassInstance; // 定義C++類的實例,用于后面的調(diào)用 [DllImport("libMath.dll", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr CreateMyClass(); [DllImport("libMath.dll", CallingConvention = CallingConvention.Cdecl)] private static extern void DeleteMyClass(IntPtr obj); [DllImport("libMath.dll", CallingConvention = CallingConvention.Cdecl)] private static extern int CallAdd(IntPtr obj, int num1, int num2); [DllImport("libMath.dll", CallingConvention = CallingConvention.Cdecl)] private static extern int CallSub(IntPtr obj, int num1, int num2); static void Main(string[] args) { Console.WriteLine("測試C#調(diào)用C++"); myClassInstance = CreateMyClass(); int nRet = CallAdd(myClassInstance, 1, 2); Console.WriteLine($"1 + 2 = {nRet}"); // 清理C++內(nèi)存 DeleteMyClass(myClassInstance); } } }
注意:由于C++的編譯特點,C++項目有Debug、Release、x86、x64之分,特別需要注意dll的放置位置,放錯了可能就鏈接失敗了,以及C++在x64于x86下的指針4字節(jié)與8字節(jié)的區(qū)分,這些都會導(dǎo)致在C#調(diào)用C++ dll代碼失敗或者crash。此外,確保你的應(yīng)用程序具有訪問和加載DLL所需的適當(dāng)權(quán)限。
運行結(jié)果如下:
在C#中可以創(chuàng)建一個對應(yīng)C++類的C#包裝類,使用 DllImport 屬性聲明導(dǎo)出函數(shù),例如下面的代碼:
public class MyClassWrapper { private IntPtr myClassInstance; [DllImport("YourCppLibrary.dll", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr CreateMyClass(); [DllImport("YourCppLibrary.dll", CallingConvention = CallingConvention.Cdecl)] private static extern void DeleteMyClass(IntPtr obj); [DllImport("YourCppLibrary.dll", CallingConvention = CallingConvention.Cdecl)] private static extern void CallMyMethod(IntPtr obj); public MyClassWrapper() { myClassInstance = CreateMyClass(); } ~MyClassWrapper() { DeleteMyClass(myClassInstance); } public void MyMethod() { CallMyMethod(myClassInstance); } }
使用C#包裝類:
在C#中,可以實例化MyClassWrapper類,并調(diào)用其中的方法,這將轉(zhuǎn)發(fā)調(diào)用到C++類的實例。
MyClassWrapper myInstance = new MyClassWrapper(); myInstance.MyMethod();
這是一個簡單的例子,實際情況可能更為復(fù)雜,特別是在處理類的繼承、虛函數(shù)等方面。確保在進行實際應(yīng)用時進行充分的測試,以確?;ゲ僮餍哉_\作。
三、C++與C#數(shù)據(jù)類型對應(yīng)
C#在調(diào)用C++ DLL時,需要通過P/Invoke技術(shù)來完成。P/Invoke是.NET Framework用于調(diào)用非托管代碼庫的一種方式。在這個過程中,我們需要處理兩種語言之間的數(shù)據(jù)類型轉(zhuǎn)換,因為它們的數(shù)據(jù)類型不完全一致。
基本數(shù)據(jù)類型對應(yīng)表
以下是C++和C#之間的一些常見數(shù)據(jù)類型的對應(yīng)表(請注意,這并不是一個完全的列表,只是一些常見類型的示例):
C++ | C# |
---|---|
bool | bool |
char / BYTE | byte |
short | short |
int | int |
long | int |
float | float |
double | double |
char* (C-style string) | string |
wchar_t* (Unicode string) | string |
在C#中,我們使用DllImport
特性來聲明對C++ DLL的函數(shù)調(diào)用。例如,如果我們有一個C++函數(shù)如下:
extern "C" __declspec(dllexport) int Add(int a, int b);
在C#中,我們可以這樣聲明和使用它:
[DllImport("MyLibrary.dll")] public static extern int Add(int a, int b); public void Main() { int result = Add(2, 3); }
對于更復(fù)雜的數(shù)據(jù)類型,如結(jié)構(gòu)體和類,我們需要在C#中創(chuàng)建對應(yīng)的類或結(jié)構(gòu)體來匹配。例如,如果我們有一個C++結(jié)構(gòu)體:
struct Person { char* name; int age; };
我們可以在C#中創(chuàng)建一個類來匹配它:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public class Person { public string name; public int age; }
注意我們使用了StructLayout
特性并設(shè)置CharSet
為CharSet.Ansi
,因為C++中的char*
類型對應(yīng)于ANSI字符串。
在處理C++ DLL中的函數(shù)時,也需要注意函數(shù)調(diào)用的約定(cdecl
,stdcall
等)。默認(rèn)情況下,C#假定被調(diào)用的函數(shù)使用stdcall
調(diào)用約定。如果函數(shù)使用不同的調(diào)用約定,你需要在DllImport
特性中指定它,例如:
[DllImport("MyLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
在處理更復(fù)雜的情況,如回調(diào)函數(shù),復(fù)雜的數(shù)據(jù)結(jié)構(gòu),和C++類時,可能需要更多的處理。處理這些情況通常需要對兩種語言都有深入的理解,并且可能需要手動管理內(nèi)存。
C++指針類型與C#類型
C++ 指針在C#中的相應(yīng)類型取決于指針指向的數(shù)據(jù)類型和你如何打算使用它。這里有幾種常見的情況:
指向簡單類型的指針:如果指針指向一個簡單的數(shù)值類型(如
int*
、float*
等),你可以在C#中使用IntPtr
類型來表示它。你可以使用Marshal
類中的方法(如Marshal.ReadInt32
、Marshal.WriteInt32
等)來讀取或?qū)懭胫羔樦赶虻臄?shù)據(jù)。指向字符串的指針:如果指針指向一個字符串(如
char*
或wchar_t*
),你可以在C#中使用string
類型來表示它。在DllImport
特性中,你可以使用MarshalAs
特性來指定字符串的編碼方式。指向結(jié)構(gòu)體的指針:如果指針指向一個結(jié)構(gòu)體,你可以在C#中創(chuàng)建一個對應(yīng)的類或結(jié)構(gòu)體,并使用
ref
關(guān)鍵字或者IntPtr
類型來表示指針。使用ref
關(guān)鍵字時,.NET運行時會自動處理內(nèi)存管理。使用IntPtr
時,你需要手動管理內(nèi)存。
例如,假設(shè)你有一個C++函數(shù)如下:
extern "C" __declspec(dllexport) void ModifyPerson(Person* person);
在C#中,你可以這樣使用它:
[DllImport("MyLibrary.dll")] public static extern void ModifyPerson(ref Person person); // 或者 [DllImport("MyLibrary.dll")] public static extern void ModifyPerson(IntPtr personPtr);
- 指向數(shù)組的指針:如果指針指向一個數(shù)組,你可以在C#中使用數(shù)組類型來表示它。你也可以使用
MarshalAs
特性來指定數(shù)組的大小。
對于更復(fù)雜的情況,例如指向函數(shù)的指針(函數(shù)指針),你可能需要使用Marshal.GetDelegateForFunctionPointer
方法將其轉(zhuǎn)換為C#委托。
需要注意的是,當(dāng)你使用IntPtr
來管理指針時,你需要自己負責(zé)內(nèi)存的分配和釋放。你可以使用Marshal.AllocHGlobal
和Marshal.FreeHGlobal
方法來分配和釋放非托管內(nèi)存。
總結(jié)
到此這篇關(guān)于C#程序如何調(diào)用C++ dll的文章就介紹到這了,更多相關(guān)C#調(diào)用C++ dll內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
WPF ComboBox獲取當(dāng)前選擇值的實例詳解
這篇文章主要介紹了WPF ComboBox獲取當(dāng)前選擇值的實例詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-01-01C#調(diào)用和實現(xiàn)WebService,純手工打造!
C#調(diào)用和實現(xiàn)WebService,純手工打造! 需要的朋友可以參考一下2013-02-02C#動態(tài)執(zhí)行字符串(動態(tài)創(chuàng)建代碼)的實例代碼
在編寫C#程序的時候,有時我們需要動態(tài)生成一些代碼并執(zhí)行。然而C#不像JavaScript有一個Eval函數(shù),可以動態(tài)的執(zhí)行代碼。所有這些功能都要我們自己去完成2013-03-03混合語言編程—C#使用原生的Directx和OpenGL繪圖的方法
本文要說的是混合C#和C/C++語言編程,在C#的Winform和WPF下使用原生的Direct和OpenGL進行繪圖2013-09-09