一、序言
在C#開發(fā)過程中,我們可能會遇到需要調(diào)用Windows API 或是第三方庫的場景,然而有時候這些庫是由C++編寫的,并不能直接應(yīng)用在C#的程序中,這為開發(fā)帶來許多阻力。本文介紹兩種使用C#調(diào)用C++動態(tài)庫的方式,以及在這個過程中可能遇到的問題,看完之后會對你的困境有所幫助。
二、非托管與托管
很多時候在項目中需要通過C++調(diào)用C#的dll,或者反過來調(diào)用。首先明白一個前提:C#是托管型代碼。C++是非托管型代碼。簡而言之:
- 托管型代碼的對象在托管堆上分配內(nèi)存,創(chuàng)建的對象由虛擬機(jī)托管。(C# )
- 非托管型代碼對象有實(shí)際的內(nèi)存地址,創(chuàng)建的對象必須自己來管理和釋放。(C++)
所以C#調(diào)用C++的動態(tài)庫可以歸類成兩種辦法:
- 非托管C++創(chuàng)建的dll庫,需要用靜態(tài)方法調(diào)用;
- 直接使用CLR,生成托管C++dll庫。
接下來詳細(xì)介紹具體的實(shí)現(xiàn)方式。
三、方法一:靜態(tài)調(diào)用
1.加載第三方動態(tài)庫
在使用C#時,在解決一些問題時會用到Windows API,這時你很容易看到如下的調(diào)用:
[DllImport("user32.dll")]
public static extern bool ReleaseCapture();
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (e.Button == MouseButtons.Left)
{
ReleaseCapture();
SendMessage(Handle, 0x00A1, 2, 0);
}
}
這就是通過DLLImport來靜態(tài)調(diào)用動態(tài)庫中的方法,比較便捷,適用于一些簡單的調(diào)用,但是缺點(diǎn)也很明顯,想要使用相應(yīng)的方法就得提前進(jìn)行靜態(tài)聲明,而且不利于參數(shù)傳遞,也就是說如果參數(shù)有在庫中聲明的結(jié)構(gòu)體,不能直接被靜態(tài)方式引用,你必須在庫中聲明一個公共類做為接口來暴露數(shù)據(jù)。而有時我們使用的DLL是第三方的工具,不方便自己開源修改。
2.自定義動態(tài)庫
有時候我們需要自己編寫動態(tài)庫來調(diào)用,就可以參考如下形式:
例子:新建C++項目,創(chuàng)建動態(tài)鏈接庫(DLL),然后添加頭文件textdll.h
#pragma once
#ifdef A_EXPORTS
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif
extern "C" DLL_API void MessageBoxShow(); //通過extern “C” 使MessageBoxShow方法為一個導(dǎo)出方法,外部可見
這一步是要在頭文件中聲明接口,之后需要實(shí)現(xiàn)相應(yīng)的函數(shù):
#include "stdafx.h"
#include "textdll.h"
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
void MessageBoxShow()
{
MessageBox(NULL, TEXT("Hello World"), TEXT("In a DLL"), MB_OK);
}
我們編譯之后,在目錄下會生成 一個DLL(.dll為后綴的文件),我們復(fù)制到C#的debug目錄下,使用C#程序靜態(tài)調(diào)用:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices; //必須要添加該引用
using System.Text;
using System.Threading.Tasks;
namespace dlltest
{
class Program
{
[DllImport("TESTDLL.dll")] //最關(guān)鍵的,導(dǎo)入該dll
public extern static void MessageBoxShow();
static void Main(string[] args)
{
MessageBoxShow();
}
}
}
但是這樣做在很多時候不太方便,如果想要知道庫中具體有哪些函數(shù),還需要去翻閱文檔或者源碼,調(diào)用前還需要逐個進(jìn)行靜態(tài)的聲明,所以接下來引用第二種方法。
四、方法二:托管C++動態(tài)庫
通過C++/CLI作為中間層,因為C++/CLI既可調(diào)用.NET的類庫,又可調(diào)用C++原生庫,
所以可以通過C# 調(diào)用 C++/CLI 再調(diào)用 C++ DLL這樣的三層調(diào)用方式完成。
1.動態(tài)庫
首先需要編寫一個DLL,可以是自己編寫的動態(tài)庫也可以是第三方的動態(tài)庫,但是很重要一點(diǎn)是,在函數(shù)中需要有如下聲明才能被后續(xù)引用:
_declspec(dllexport) int sum(int a, int b);//給c++調(diào)用
extern "C" __declspec(dllexport) int sum(int a, int b);//給c#調(diào)用
如果是自己編譯則在VS中選擇創(chuàng)建DLL項目,如何編寫動態(tài)庫這里就不贅述:
2.新建CLR項目
1.創(chuàng)建
打開VS,創(chuàng)建一個CLR的空項目:
從描述中就可以看到CLR本身就是打通.NET與C++代碼的橋梁。
2.配置
以實(shí)現(xiàn)HID相關(guān)的USB功能為例,需要使用到一個hid的動態(tài)庫,在創(chuàng)建完項目后打開項目屬性,先引用第三方的DLL:
這個庫目錄中需要包含同名的相關(guān)文件:
同時引用庫:
3.方法
然后新建一個類文件來編寫相關(guān)方法:Cli.h
#pragma once
#include "hidapi.h"
public ref class DFUCli
{
public:
hid_device* handle;
DFUCli();
int Openhid();
void test();
};
Cli.cpp
#include "DFUCli.h"
DFUCli::DFUCli()
{
}
int DFUCli::Openhid()
{
uint16_t pid = 0x0000;
uint16_t vid = 0x0000;
handle = hid_open(vid, pid, NULL);
if (handle == NULL) {
return -1;
}
return 0;
}
void DFUCli::test()
{
}
編寫完成之后進(jìn)行編譯,此處需要更改生成類型為動態(tài)庫:
這樣我們就得到一個CLR的動態(tài)庫文件。
3.C#引用DLL間接調(diào)用方法
新建一個C#工程,引用剛剛生成的DLL:
這時我們就能像使用普通的動態(tài)庫一樣調(diào)用相關(guān)方法,實(shí)例化對象之后,VS還會自動提示相應(yīng)的函數(shù),無須其他多余操作:
五、問題點(diǎn)匯總
當(dāng)然由于這種多級的調(diào)用,經(jīng)常會由于錯誤的操作或者配置導(dǎo)致一些未知的錯誤,下面整理了一些調(diào)試過程中遇到的問題,可能對你有所幫助:
1.fatal error LNK1561: 必須定義入口點(diǎn)
出現(xiàn)這個錯誤是由于我們創(chuàng)建的DLL是并沒有入口函數(shù),也就是main函數(shù),而編譯器按常規(guī)流程處理引發(fā)錯誤,有兩種解決方式。
(1)設(shè)置子系統(tǒng),解決方案上,右鍵->屬性->鏈接器->系統(tǒng)->子系統(tǒng),下拉框選擇:窗口 (/SUBSYSTEM:WINDOWS)
(2)設(shè)置入口點(diǎn),解決方案上,右鍵->屬性->鏈接器->高級->入口點(diǎn),設(shè)置成:WinMainCRTStartup
2.未能加載文件或程序集“XXX.dll”或它的某個依賴項的解決方法
這個問題有時候比較隱蔽,正常情況下應(yīng)用程序查找依賴的dll時,順序為先查找程序exe的輸出路徑,如果沒有找到,那么會去C:\Windows\System32或System64文件夾中查找。
這種情況下我們只需要添加相關(guān)的dll到輸出文件夾下就能解決錯誤,但是當(dāng)你添加了對應(yīng)的依賴項之后仍然報相關(guān)錯誤:
這時候你可能覺得無法理解從而陷入誤區(qū),然而事實(shí)上在這種多級的引用中,有可能你當(dāng)前直接引用了A.dll,然后A.dll是一個CLR動態(tài)庫引用了B.dll。如果A.dll找到了,但是A.dll依賴的B.dll沒有找到,也會報上述錯誤,但這時它有可能只會提示沒有找到A.dll。所以你需要把相關(guān)的依賴項全部都放在一個輸出文件夾中來避免這種情況,當(dāng)然也有可能你不清楚你所使用的動態(tài)庫是否引用了第三方DLL,此時我們可以用Visual Studio中的dumpbin.exe工具查找A.dll的依賴項。
3.所生成項目的處理器框架“MSIL”與引用“xxx”的處理器架構(gòu)“AMD64”不匹配
這是由于你的C#程序與動態(tài)庫的生成配置不同導(dǎo)致的,將兩者進(jìn)行統(tǒng)一即可解決:
4.System.EntryPointNotFoundException: '無法在 DLL“xxx.dll”中找到名為“xx”的入口點(diǎn)。
在系統(tǒng)內(nèi)的函數(shù)名稱不對,遇到這個問題可以用工具查看內(nèi)部名稱,發(fā)覺函數(shù)名稱并不是 “xx” 而是類似“?xx@@YAHHH@Z”這種不合法的形式。
原因為dll導(dǎo)出的格式不對,給c#調(diào)用時需要增加extern “C”,標(biāo)準(zhǔn)C格式。
_declspec(dllexport) int sum(int a, int b);//給c++調(diào)用
extern "C" __declspec(dllexport) int sum(int a, int b);//給c#調(diào)用
這一點(diǎn)在前文生成托管動態(tài)庫中提到過。
5.錯誤 LNK2019 無法解析的外部符號
這個如果是運(yùn)行時報錯,常見的原因是你引用了某個庫的函數(shù),然后也正確添加了它的頭文件路徑,vs在寫代碼階段可以找到這個函數(shù)的定義,但是,由于你沒有添加或者正確設(shè)置這個庫的lib或者dll路徑的話,那么vs就會在運(yùn)行時候報錯無法解析的外部符號。庫目錄(lib文件目錄)是在項目->屬性->配置屬性→VC++目錄→庫目錄里進(jìn)行添加,這一點(diǎn)在前文配置CLR項目中有詳細(xì)步驟。
第二種可能是類似第4點(diǎn)錯誤,你沒有正確的設(shè)置導(dǎo)出格式,需要增加相應(yīng)的定義。
同樣的第3點(diǎn)錯誤也可能導(dǎo)致這個問題。
除了前幾種可能,還有一點(diǎn)就是lib的覆蓋問題,引用動態(tài)庫,在項目生成中如果更改lib生成路徑,而正好引用該lib的項目又同時包含了原來的和重新生成的兩個lib路徑,編譯器就有可能鏈接的還是原來的lib,就會導(dǎo)致這個問題。
綜上所述,出現(xiàn)這個問題除了低級的拼寫錯誤外,無非就是沒有正確的引用相關(guān)的依賴文件,或是程序和他所引用的依賴同時引用了相關(guān)依賴但是版本不一致。
6.CS0570 '現(xiàn)用語言不支持xxx
這個比較常見是dll的問題,解決方案中有一個類庫項目,原來是引用的dll文件,后來又在本解決方案中建了一個一樣的類庫項目,有的項目直接引用的是這個類庫項目,有的又是引用的dll文件。
后來統(tǒng)一引用類庫項目就能編譯成功了。
還有一種可能則是函數(shù)的參數(shù)不兼容,假設(shè)你引用了一個MFC項目需要輸入一個CString類型的參數(shù),而C#中的String類型不能替代或是強(qiáng)制轉(zhuǎn)換也會出現(xiàn)問題。文章來源:http://www.zghlxwxcb.cn/news/detail-559095.html
六、總結(jié)
使用動態(tài)庫讓我們的程序有了更多可能,同時可以兼容不同的語言,但是在引用過程中容易出現(xiàn)各種問題,特別要遵循一定的規(guī)范,否則在復(fù)雜操作時會有意想不到的問題。但是使用得當(dāng)會是不可多得的助力!文章來源地址http://www.zghlxwxcb.cn/news/detail-559095.html
到了這里,關(guān)于C#調(diào)用C++動態(tài)庫的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!