如何在C#中調用C++編寫的動態庫?三種方式詳解
場景和優點
在以下場景下,可能會使用C#調用C++編寫的dll:
- C++庫已經存在并且經過了充分測試和驗證,需要被C#項目重復使用時。
- C++編寫的庫中包含高性能計算、海量數據處理等需要使用底層語言實現的操作時,可以考慮將這些操作封裝為動態鏈接庫供C#調用。
- 在跨平臺開發時,C++可在多個平臺上運行,通過封裝為dll,可以讓C#項目也能夠在多個平臺上運行。
- 需要將不同的功能模塊拆分成獨立的組件,C++編寫的dll可以作為一個獨立的組件,供C#項目或其他語言的項目調用。
此外,使用C#調用C++編寫的dll還有以下優點:
- C#具有較高的開發效率和易用性,通過調用C++編寫的dll可以兼顧高性能和高開發效率。
- C#可以使用.NET Framework提供的強大工具和庫,如LINQ、異步編程等等,這些工具和庫可以提高開發效率,同時也可以利用C++的性能優勢。
- C#可以與其他語言,如Java、Python等配合使用,借助各種技術,如SOAP、WCF、gRPC等實現多語言之間的互操作。
- C++作為一種系統級編程語言,可以訪問系統底層資源,如內存、磁盤、網絡等,C#調用C++編寫的dll可以實現訪問這些底層資源的功能,從而提供更多的功能。
C#調用C++編寫的動態庫的方式
在C#中調用C++編寫的動態庫有以下幾種方式:
1、使用DllImport特性
使用DllImport特性可以直接引入動態鏈接庫中的C++函數,并在C#中進行調用。
下面是一個簡單的示例:
首先,我們在C++中編寫一個簡單的dll,里面包含一個計算兩數之和的函數addition:
c++Copy Code// file: mylib.cpp
#include "pch.h"
#include "mylib.h"
int addition(int a, int b) {
return a + b;
}
然后,我們在C++中將其封裝為一個dll,并導出addition函數:
// file: mylib.h
#ifdef MYLIB_EXPORTS
#define MYLIB_API __declspec(dllexport)
#else
#define MYLIB_API __declspec(dllimport)
#endif
extern "C" MYLIB_API int addition(int a, int b); // export the function
接著,在C#項目中使用DllImport特性導入這個dll,并調用其中的函數:
using System.Runtime.InteropServices;
class Program
{
[DllImport("MyLib.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int addition(int a, int b);
static void Main(string[] args)
{
int result = addition(1, 2);
Console.WriteLine("The sum is: " + result);
}
}
在上述示例中,我們使用DllImport特性聲明了一個addition方法,將其與C++中的addition函數進行綁定。在Main函數中,我們調用了這個方法,并輸出計算結果。
需要注意的是,在使用DllImport特性時,需要指定正確的dll名稱和函數調用規約,否則可能會出現運行時錯誤。
2、使用C++/CLI
另一種實現方式是使用C++/CLI(C++/Common Language Infrastructure)。
C++/CLI是一種結合了C++和CLR(Common Language Runtime)的語言,它可以編寫針對.NET Framework/CLR的代碼,同時也可以訪問C++的底層資源。因此,我們可以使用C++/CLI來封裝C++庫,并將其作為dll供C#調用。
下面是一個簡單的示例:
首先,在C++/CLI中編寫一個類LibraryWrapper,里面包含一個使用C++庫計算兩數之和的方法Addition:
// file: LibraryWrapper.h
#pragma once
namespace MyLibrary {
public ref class LibraryWrapper
{
private:
Library* lib; // the C++ object we want to wrap
public:
LibraryWrapper(); // constructor
~LibraryWrapper(); // destructor
int Addition(int a, int b); // method used to add two numbers
};
}
其中,Library是我們需要封裝的C++庫中的一個類。
然后,在實現文件LibraryWrapper.cpp中實現類的構造函數、析構函數和Addition方法:
// file: LibraryWrapper.cpp
#include "pch.h"
#include "LibraryWrapper.h"
#include "Library.h"
using namespace MyLibrary;
LibraryWrapper::LibraryWrapper()
{
lib = new Library(); // create a new Library object
}
LibraryWrapper::~LibraryWrapper() {
delete lib; // release the memory
}
int LibraryWrapper::Addition(int a, int b)
{
return lib->addition(a, b); // call the addition method in C++ library
}
這里我們實例化了一個C++庫中的對象,然后在Addition方法中調用了它的addition方法。
最后,在C++/CLI項目中發布dll,并在C#項目中引用。在C#項目中,我們可以創建一個LibraryWrapper對象,并調用其中的Addition方法:
using System;
using System.Runtime.InteropServices;
namespace CppCLILibraryTest
{
class Program
{
static void Main(string[] args)
{
MyLibrary.LibraryWrapper wrapper = new MyLibrary.LibraryWrapper();
int result = wrapper.Addition(1, 2);
Console.WriteLine("The sum is: " + result);
}
}
}
需要注意的是,當使用C++/CLI封裝C++庫時,我們需要確保兩者所使用的Runtime是相同的。比如,如果C++庫是使用靜態連接的方式與CRT(C Runtime)鏈接的,那么我們需要在C++/CLI項目的屬性中設置“/MT”選項,以保證代碼使用相同的CRT版本。
3、使用COM組件
另一種實現方式是使用COM組件。COM是微軟推出的一種二進制接口標準,它可以讓不同的應用程序之間以二進制碼互相通信。
下面是一個簡單的示例:
首先,在C++中編寫一個簡單的dll,里面包含一個計算兩數之和的函數addition:
// file: MyLibrary.h
#pragma once
#ifdef MYLIBRARY_EXPORTS
#define MYLIBRARY_API __declspec(dllexport)
#else
#define MYLIBRARY_API __declspec(dllimport)
#endif
namespace MyLibrary {
class MyMath {
public:
static int Addition(int a, int b);
};
}
然后,我們將這個dll封裝為一個COM組件。我們需要創建一個類,其中包含COM接口和類工廠:
// file: MathCOM.h
#pragma once
#include "MyLibrary.h"
class MathCOM : public IUnknown {
private:
ULONG m_cRef;
public:
MathCOM();
~MathCOM();
// IUnknown methods
STDMETHODIMP QueryInterface(REFIID riid, void** ppv);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);
// COM interface method
STDMETHODIMP Addition(int a, int b, int* result);
};
class MathClassFactory : public IClassFactory {
private:
ULONG m_cRef;
public:
MathClassFactory();
~MathClassFactory();
// IUnknown methods
STDMETHODIMP QueryInterface(REFIID riid, void** ppv);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);
// IClassFactory methods
STDMETHODIMP CreateInstance(IUnknown* pUnknownOuter, REFIID riid, void** ppvObject);
STDMETHODIMP LockServer(BOOL fLock);
};
在實現文件MathCOM.cpp中,我們需要為這些接口方法提供具體的實現:
// file: MathCOM.cpp
#include "stdafx.h"
#include "MathCOM.h"
MathCOM::MathCOM() {
m_cRef = 1;
}
MathCOM::~MathCOM() {}
STDMETHODIMP MathCOM::QueryInterface(REFIID riid, void** ppv) {
*ppv = NULL;
if (riid == IID_IUnknown || riid == IID_IDispatch)
*ppv = this;
if (*ppv != NULL) {
((LPUNKNOWN)*ppv)->AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
STDMETHODIMP_(ULONG) MathCOM::AddRef() {
return InterlockedIncrement((LONG*)&m_cRef);
}
STDMETHODIMP_(ULONG) MathCOM::Release() {
ULONG cRef = InterlockedDecrement((LONG*)&m_cRef);
if (cRef == 0) delete this;
return cRef;
}
STDMETHODIMP MathCOM::Addition(int a, int b, int* result) {
*result = MyLibrary::MyMath::Addition(a, b);
return S_OK;
}
MathClassFactory::MathClassFactory() {
m_cRef = 1;
}
MathClassFactory::~MathClassFactory() {}
STDMETHODIMP MathClassFactory::QueryInterface(REFIID riid, void** ppv) {
*ppv = NULL;
if (riid == IID_IUnknown || riid == IID_IClassFactory)
*ppv = this;
if (*ppv != NULL) {
((LPUNKNOWN)*ppv)->AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
STDMETHODIMP_(ULONG) MathClassFactory::AddRef() {
return InterlockedIncrement((LONG*)&m_cRef);
}
STDMETHODIMP_(ULONG) MathClassFactory::Release() {
ULONG cRef = InterlockedDecrement((LONG*)&m_cRef);
if (cRef == 0) delete this;
return cRef;
}
STDMETHODIMP MathClassFactory::CreateInstance(IUnknown* pUnknownOuter, REFIID riid, void** ppvObject) {
if (pUnknownOuter) return CLASS_E_NOAGGREGATION;
MathCOM* pMathCOM = new MathCOM();
if (!pMathCOM) return E_OUTOFMEMORY;
HRESULT hResult = pMathCOM->QueryInterface(riid, ppvObject);
pMathCOM->Release();
return hResult;
}
STDMETHODIMP MathClassFactory::LockServer(BOOL fLock) {
return S_OK;
}
在項目中使用C++編譯器生成COM組件dll之后,在C#項目中使用COM互操作性來調用這個COM組件,代碼如下:
using System.Runtime.InteropServices;
namespace COMTest
{
[ComImport, Guid("B9D43B8A-61F3-4668-AB30-C2BE194AD0AA")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IMathCOM {
[PreserveSig]
int Addition(int a, int b, out int result);
}
[ComImport, Guid("8CFD0B22-24A3-4490-9127-9DB3FD53E15F")]
class MathCOM { }
class Program
{
static void Main(string[] args)
{
IMathCOM mathCOM = (IMathCOM)new MathCOM();
int result = 0;
mathCOM.Addition(1, 2, out result);
Console.WriteLine("The sum is: " + result);
}
}
}
在這個示例中,我們聲明了一個用來調用COM組件的接口IMathCOM,然后實例化MathCOM類并把它轉換為IMathCOM類型,就可以調用其中的Addition方法了。
以上三種方式都可用于調用C++編寫的動態庫,選擇使用哪種方式應該根據具體的場景和需求來決定。