東坡下載:內(nèi)容最豐富最安全的下載站!

首頁(yè)編程開(kāi)發(fā)VC(VC++) → C++和C#之間互相調(diào)用經(jīng)驗(yàn)詳談

C++和C#之間互相調(diào)用經(jīng)驗(yàn)詳談

相關(guān)文章發(fā)表評(píng)論 來(lái)源:本站時(shí)間:2011/4/23 6:27:38字體大。A-A+

更多

作者:專(zhuān)題點(diǎn)擊:2007次評(píng)論:0次標(biāo)簽: c語(yǔ)言

先說(shuō)說(shuō)程序大概組織邏輯。主程序有一套公用接口(其實(shí)就是純虛類(lèi)),在加載DLL時(shí)候?qū)⒋私涌趥鞯紻LL中,這樣子模塊在需要的時(shí)候就可以調(diào)用父的邏輯了,至于父調(diào)子,那就更簡(jiǎn)單了,主程序有一個(gè)純虛類(lèi),子模塊都繼承此接口,并進(jìn)行重寫(xiě),主程序按照一定的順序分別調(diào)用,這樣父與子的邏輯交互就完成了,這些對(duì)都是C++程序來(lái)說(shuō),這當(dāng)然沒(méi)問(wèn)題,F(xiàn)在問(wèn)題是,要嵌入.NET的類(lèi)庫(kù),由此引發(fā)一系列問(wèn)題。。。。。
軟件是以C++為父,DLL作為子的項(xiàng)目。
開(kāi)發(fā)環(huán)境:WIN7 64BIT+VS2010+MFC+ATL+COM。
.NET環(huán)境下先以C#為例,其他的大部分一樣下,不排除做一些簡(jiǎn)單或者復(fù)雜的修改。
下面正式開(kāi)始把。
1. 動(dòng)態(tài)加載 即父調(diào)子。
COM確實(shí)是好東西(他的褒與貶我們無(wú)作評(píng)論),她的語(yǔ)言無(wú)關(guān)性,不僅是我們實(shí)現(xiàn)動(dòng)態(tài)加載的關(guān)鍵,更是實(shí)現(xiàn)加載其他.NET類(lèi)庫(kù)的核心。如VB.NET。有了她,才是這一切皆有可能。
由于.NET下的類(lèi)庫(kù)(DLL),和傳統(tǒng)的WIN DLL 不太一樣,畢竟托管的東西。她一些函數(shù)對(duì)外是不可見(jiàn)的,但對(duì)COM可見(jiàn)。因?yàn)槲覀兙鸵訡OM方式定義一套接口,并把此接口當(dāng)成普通C++的純虛接口,來(lái)完成父到子的調(diào)用。
這一點(diǎn)不論在理論上、代碼上都比較簡(jiǎn)單,而且網(wǎng)上大多也是這樣子,所以我們直接上代碼。
如下為COM接口定義。
[ComVisible(true),
Guid("B86D71F4-FE07-4B60-8246-F5AE283ED2A3"),
InterfaceType(ComInterfaceType.InterfaceIsDual)
]
public interface IHMI
{
[PreserveSig, DispId(1)]
void OnCreate(int a);
[PreserveSig, DispId(2)]
void SetRect(int left, int top, int width, int height);
//其他接類(lèi)似
}
[ ComVisible(true),
ClassInterface(ClassInterfaceType.AutoDual),
ProgId("xxxxxxx.xxxxxxx") //ProgId 主程序根據(jù)此,運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建。
]
C#在使用時(shí)要繼承并實(shí)現(xiàn)接口邏輯,如下類(lèi)似。
public class CustomCOMClient : IHMI
{
public CustomCOMClient()
{
}
[DispId(1)]
public void OnCreate(int a)
{
//邏輯
}
[DispId(2)]
public void SetRect(int left, int top, int width, int height)
{
//邏輯
}
}
當(dāng)然了,在建項(xiàng)目時(shí),項(xiàng)目類(lèi)型要為類(lèi)庫(kù)。至此類(lèi)庫(kù)部分已經(jīng)完畢。接下來(lái)再看看主程序如何加載,以及如何調(diào)用把。
其中在動(dòng)態(tài)創(chuàng)建時(shí),ProgId是關(guān)鍵。這一部分對(duì)搞過(guò)COM,在加上ATL的人來(lái)說(shuō),可能太簡(jiǎn)單了,‘可能’這個(gè)詞也許用的不太恰當(dāng),因?yàn)樗皇?amp;lsquo;可能’,她確實(shí)簡(jiǎn)單。不信看代碼。
::CoInitialize(NULL);
const OLECHAR lpszProgID[]=OLESTR("xxxxxxx.xxxxxxx"); //ProgID
CComPtr m_NetCustomer;
HRESULT hr = m_NetCustomer.CoCreateInstance(lpszProgID);
if(SUCCEEDED(hr))
{
const LPCOLESTR szMember=OLESTR("OnCreate");
VARIANT v;
v.vt = VT_I4; v.lVal = 1024;
hr = m_NetCustomer.Invoke1(szMember,&v);
if(SUCCEEDED(hr))
{
}
}
::CoUninitialize();
怎么樣?沒(méi)有撒謊把,幾行代碼就把創(chuàng)建、調(diào)用搞定了。
郁悶,從C++拷出來(lái)代碼沒(méi)有格式,還的手工加。。。。
2. 回調(diào) 即子調(diào)父。
主程序肯定按照自己的邏輯順序依次調(diào)用子模塊的接口,如先創(chuàng)建、子的相關(guān)邏輯、最后銷(xiāo)毀。如果說(shuō)在實(shí)際運(yùn)用中,子模塊完全不會(huì)在調(diào)用父的相關(guān)功能,那么此時(shí)框架已經(jīng)完全實(shí)現(xiàn)了,我們之前做的工作就是。難道不是嗎?,但應(yīng)用程序往往也有父與子相互調(diào)用,下面就來(lái)看看,子如何回調(diào)父的功能把
前面也說(shuō)過(guò),子調(diào)父往往是這樣,從父身上分離出部分代碼,重新封裝一個(gè)dll,由子靜態(tài)綁定,這步最簡(jiǎn)單、最方便。不過(guò)這顯然不是正道,讓人覺(jué)得別扭。
同時(shí)維護(hù)兩份相同功能代碼? 也許你會(huì)說(shuō),主程序從此也可以調(diào)用DLL啊,那不就一致了,你要真這樣說(shuō),我的回答是,“我只是在說(shuō)明問(wèn)題,不涉及到架構(gòu)問(wèn)題”
還有每個(gè)子模塊都靜態(tài)綁定這個(gè)DLL?
還有你在分離這個(gè)DLL時(shí),如果依賴主程序太多,你怎么辦?
還有你能保證分離后的穩(wěn)定性嗎?回帶來(lái)其他的問(wèn)題嗎?
還有你僅僅是為了滿足功能,才這樣做的?
你覺(jué)得這樣看著順眼嗎?
等等。反正我覺(jué)得是古怪之急。
接下來(lái)就要需找其他替代方案了。
先考慮下在C++中這一部分是如何實(shí)現(xiàn)的把。 父?jìng)鹘o子一個(gè)虛接口(虛類(lèi)),子在適當(dāng)?shù)臅r(shí)候調(diào)用。僅此而已。讓我們把調(diào)用函數(shù)想的深入一點(diǎn)。直接看匯編代碼把。
看代碼之前,還要先簡(jiǎn)單說(shuō)一下函數(shù)調(diào)用相關(guān)信息。在匯編層調(diào)用一個(gè)函數(shù)無(wú)非也就是JMP、CALL 之類(lèi)的指令,若函數(shù)還有參數(shù)就是一些PUSH指令。好了知道這些就足夠了,下面看看在VC中的偽代碼。
__asm
{//類(lèi)虛函數(shù)的匯編模擬調(diào)用,函數(shù)無(wú)參數(shù)、無(wú)返回值。
mov eax,xxxxx //存放函數(shù)地址
mov ecx,xxxxx //this指針
call eax //調(diào)用
}
這樣調(diào)用就完成了,其實(shí)真正的調(diào)用也如此,只不過(guò)指令多幾條而已。因?yàn)樗玫侥承┬畔ⅰ?br/>好了,如果說(shuō).NET支持內(nèi)斂匯編,那我們完全可以自己模擬虛函數(shù)調(diào)用,不用在封裝什么DLL,這所有的一切都可以搞定,但可惜的時(shí),常規(guī)下內(nèi)斂匯編是不支持的。不錯(cuò),我說(shuō)的是常規(guī),那非常規(guī)呢?答案是肯定的。
關(guān)于內(nèi)斂匯編網(wǎng)上也是一大片,底層思想是,在內(nèi)存開(kāi)辟一段空間,并放入相應(yīng)指令,到時(shí)侯執(zhí)行這一部分邏輯即可,這樣就可以完成內(nèi)斂匯編了。
其中網(wǎng)上有一個(gè)封裝好的DLL(AsmClassLibrary.dll),提供接口編寫(xiě)匯編代碼,用Reflector 查看了發(fā)現(xiàn)其最后執(zhí)行采用遠(yuǎn)程線程注入方式,對(duì)于嵌入一兩個(gè)模塊的,可以這樣做,但如果模塊很多的話,畢竟注入涉及到安全的問(wèn)題,這一點(diǎn)不太好,當(dāng)然這也太另類(lèi)了,我可不想應(yīng)用程序到處以這種方式來(lái)執(zhí)行。
所以我們采用Marshal.GetDelegateForFunctionPointer方式。
因?yàn)閺牡讓由现v,是不分什么語(yǔ)言編寫(xiě),只認(rèn)機(jī)器指令的,因此只要我們模擬的合理、正確,這一點(diǎn)是沒(méi)有問(wèn)題的。
好了,現(xiàn)在我們目標(biāo)很明確,用內(nèi)斂方式在C#模擬虛函數(shù)的調(diào)用。
在給出代碼之前,也先說(shuō)下思路。
根據(jù)之前所講以及常規(guī)知識(shí),以下幾點(diǎn)是必須的。
A 類(lèi)對(duì)象指針,因?yàn)槲覀円獙⒋酥到oECX。
B 成員函數(shù)地址,當(dāng)然了,我們要CALL嘛。
C 參數(shù),這值是在C#中使用的。
這就是主要內(nèi)容,實(shí)現(xiàn)他們方式有很多種,以下是我的方案。
因?yàn)榻涌跁?huì)很多,因此我將this指針、函數(shù)地址都放到數(shù)組中,然后在傳遞給C#中,其實(shí)按道理說(shuō),只傳遞一個(gè)this指針就夠了,其他部分應(yīng)該在C#中實(shí)現(xiàn),但操作指針C++中比較簡(jiǎn)便,所以這部分代碼就在C++中做了。
得到this指針 太簡(jiǎn)單啦,根據(jù)虛表布局得到其地址也很簡(jiǎn)單。如下。
接口定義如下。
class CInterface
{
public:
virtual void test1( LPSTR p)
virtual void test2();
virtual void test3( int a);
};
得到this指針及成員函數(shù)地址。
CInterface *pInterface = new CInterface;
DWORD base_proc = (*((DWORD *)(pInterface))); //虛表指針
DWORD f1 = *(( DWORD *)base_proc); //第1個(gè)
DWORD f2 = *(( DWORD *)(base_proc + 4)); //第2個(gè)
DWORD f3 = *(( DWORD *)(base_proc + 8)); //第三個(gè)
到時(shí)將值賦值到SAFEARRAY 安全數(shù)組中,在傳遞到C#中。
看看在C#中時(shí)如何使用的把。當(dāng)然這一部分的內(nèi)斂、委托、開(kāi)辟內(nèi)存、托管到非托管轉(zhuǎn)換時(shí)少不了的,老規(guī)矩,看代碼把。
先定義委托和內(nèi)斂。
//委托 參數(shù)分別為 this指針 成員函數(shù)地址 參數(shù)
delegate void testcall(int pthis, int pfun, int param);
byte[] codetest = {
// 0xCC,
0x8B, 0x5C, 0x24, 0x0C, //mov ebx,[esp+0Ch] 第三個(gè)參數(shù) @@
0x8B, 0x44, 0x24, 0x08, //mov eax,[esp+08h] 函數(shù)地址
0x8B, 0x4C, 0x24, 0x04, //mov ecx,[esp+04h] this 指針
0x53, //push ebx 參數(shù)入棧 @@
0xFF, 0xD0, //call eax
0xC3 // ret
};
書(shū)寫(xiě)內(nèi)斂匯編當(dāng)然可以考研我們的功底啦,看看你知道不知道底層是如何實(shí)現(xiàn)的、如何入棧、出棧、傳值、傳指針、傳引用、堆棧平衡等。還有一點(diǎn),書(shū)寫(xiě)匯編雖容易,但是機(jī)器指令我們并不都知道,山人自有妙計(jì),匯編代碼貼到VC中,ALT+8看反匯編,在拷貝回來(lái)即可。
以上代碼中,完成接口第三個(gè)函數(shù)調(diào)用,帶有一個(gè)整形參數(shù),并且傳值。
注釋掉@@部分完成接口第二個(gè)函數(shù)調(diào)用,無(wú)參數(shù)。
為了簡(jiǎn)便都寫(xiě)在一個(gè)里面,實(shí)際運(yùn)用中,你可以按照不同格式分開(kāi)。
接下來(lái)看看如何調(diào)用,,主要代碼如下。
VirtualAlloc。。。。。。之前肯定得先開(kāi)辟內(nèi)存啊
Marshal.Copy(codetest, 0, handle, codetest.Length);
testcall Customer = Marshal.GetDelegateForFunctionPointer(handle, typeof(testcall)) as testcall;
int bb = 22;
Customer (fun[0], fun[4], bb);
不錯(cuò),這就是子模塊調(diào)用父相關(guān)邏輯的主要實(shí)現(xiàn)。
3. 后話
這就是相互調(diào)用的所有部分嗎?這次答案是否定,實(shí)際上遠(yuǎn)遠(yuǎn)不至于此,我們此次實(shí)現(xiàn)的,只是最最基本的部分,尤其在參數(shù)上,我們用的最簡(jiǎn)單的類(lèi)型 int,實(shí)際使用中,對(duì)于兩者之間都存在的基本類(lèi)型,還好說(shuō)一點(diǎn),當(dāng)涉及到字符串、數(shù)組、結(jié)構(gòu)體等這些類(lèi)型時(shí),真的會(huì)讓你很麻煩的,尤其是字符串,兩邊還不一樣。。。。。
其中對(duì)參數(shù)類(lèi)型來(lái)說(shuō),我們用的是傳值方式,直接將值push,對(duì)于引用或者指針要把其地址push,就可以實(shí)現(xiàn)了,當(dāng)然還是針對(duì)最基本的類(lèi)型來(lái)說(shuō)的。
對(duì)于字符串參數(shù)的,我用全局函數(shù)實(shí)現(xiàn)了一個(gè)接口(具體的可以看代碼),這樣其中大部分轉(zhuǎn)換操作,對(duì)我們就透明了,為何不自己搞?我有時(shí)間在補(bǔ)充進(jìn)去把,這些就留給你們了,同樣你們搞出來(lái)之后要告訴我啊,這里給大家一個(gè)建議,處理字符串時(shí),在C#中最好使用char數(shù)組,但在書(shū)寫(xiě)內(nèi)斂匯編時(shí)要注意,數(shù)組前面可有數(shù)組的大小,要偏移過(guò)去。
。。。。
。。。。
等把這一切都搞定之后,動(dòng)態(tài)創(chuàng)建、嵌入VB的、C#的、WPF的以及她3D部分、硬件加速部分。。。。。。。。。
不錯(cuò),如此看來(lái),現(xiàn)在才剛剛開(kāi)始。。。。。。。。。。。。。
希望能給大家起到一個(gè)拋磚引玉作用。
最后附一個(gè)類(lèi)型轉(zhuǎn)換的帖,供使用參考,類(lèi)型轉(zhuǎn)換我就不啰嗦了。
http://topic.csdn.net/u/20090225/15/a6bc50ad-9721-4749-b189-dc4a4bc045a1.html
再附效果圖一張,圖中部分為嵌入C#的類(lèi)型。

為了嵌入到父窗口上,使用了API SetParent 并且我有建了一個(gè)項(xiàng)目,就是封裝一些常用功能,具體看代碼把。

  • c語(yǔ)言學(xué)習(xí)app大全
  • c語(yǔ)言入門(mén)教程大全
  • C語(yǔ)言編程軟件下載
c語(yǔ)言學(xué)習(xí)app大全
(15)c語(yǔ)言學(xué)習(xí)app大全

C語(yǔ)言是一款編程的語(yǔ)言,很多軟件都是使用c語(yǔ)言編輯的,學(xué)好了c語(yǔ)言,以后找高新工作非常好找,在手機(jī)中安裝c語(yǔ)音學(xué)習(xí)軟件可以隨時(shí)利用空閑時(shí)間進(jìn)行學(xué)習(xí),cc語(yǔ)言學(xué)習(xí)app大全包含了豐富的學(xué)習(xí)軟件,從入門(mén)到精通的知識(shí),基礎(chǔ)語(yǔ)法,視頻教程,二級(jí)考試題目都有涵蓋。

...更多>>
c語(yǔ)言入門(mén)教程大全
(14)c語(yǔ)言入門(mén)教程大全

c語(yǔ)言在編程中是很多的程序員都是需要學(xué)習(xí)的,那么現(xiàn)在就可以試試本站為你收集到最新的c語(yǔ)言入門(mén)教程大全,這里面包含了各種最新的c語(yǔ)言入門(mén)視頻教程,還有很多的c語(yǔ)言學(xué)習(xí)軟件,有在學(xué)習(xí)c語(yǔ)言的用戶都是可以來(lái)免費(fèi)的下載的!

...更多>>
C語(yǔ)言編程軟件下載
(6)C語(yǔ)言編程軟件下載

C語(yǔ)言是計(jì)算機(jī)語(yǔ)言的基礎(chǔ)語(yǔ)言,學(xué)會(huì)了C語(yǔ)言,再去學(xué)習(xí)其它的語(yǔ)言就要簡(jiǎn)單的多了!那么隨著想學(xué)習(xí)C語(yǔ)言的人越來(lái)越多了,如何學(xué)習(xí)C語(yǔ)言,學(xué)習(xí)C語(yǔ)言要用到那些軟件都是新手比較關(guān)心的問(wèn)題了!今天本站給大家準(zhǔn)備了一些C語(yǔ)言編程相關(guān)的軟件供大家來(lái)參考,覺(jué)得自己有需要,或者是想找些C語(yǔ)言編程軟件的朋友都可以來(lái)本站看看有沒(méi)有你需要的軟件!

...更多>>

相關(guān)評(píng)論

閱讀本文后您有什么感想? 已有 人給出評(píng)價(jià)!

  • 2791 喜歡喜歡
  • 2101 頂
  • 800 難過(guò)難過(guò)
  • 1219 囧
  • 4049 圍觀圍觀
  • 5602 無(wú)聊無(wú)聊
熱門(mén)評(píng)論
最新評(píng)論
發(fā)表評(píng)論 查看所有評(píng)論(0)
昵稱:
表情: 高興 可 汗 我不要 害羞 好 下下下 送花 屎 親親
字?jǐn)?shù): 0/500 (您的評(píng)論需要經(jīng)過(guò)審核才能顯示)