C++與Delphi中對象的互訪_第1頁
C++與Delphi中對象的互訪_第2頁
C++與Delphi中對象的互訪_第3頁
C++與Delphi中對象的互訪_第4頁
C++與Delphi中對象的互訪_第5頁
已閱讀5頁,還剩7頁未讀 繼續免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

1、C+與Delphi中對象的互訪2015年9月18日QQ:393660149QQ群:484247712實現目標:1) 測試在C/C+中調用Delphi對象的方法。2) 測試在delphi中調用C+對象的方法。我們知道兩種語言的編譯器產生的代碼有很多區別,這就造成跨語言的代碼利用變得困難。但是對于一個程序員來說,只要所用的語言工具容許內存指針操作,那么總是有辦法實現代碼的互相調用。C/C+/Delphi都支持指針操作,因此可以遍歷所有用戶空間內存。而且Delphi編譯器和C/C+編譯器有很多選項可以讓函數調用按照一定的規則產生代碼。因此實現互訪是有可能的。前提:1) 至少明白對象、類、繼承等基本概

2、念。2) 至少明白DLL庫是什么。3) 至少明白棧、堆、函數調用是什么。4) 對編譯、鏈接、加載有基本概念。基本概念:1) 對象這里簡單介紹下“對象”的概念,對象其實就是一個內存數據塊,與C語言的結構體(struct)沒有本質區別。當我們create(或new)一個對象時,其實就是最終會向內存管理單元申請(malloc)一個能存放在類中聲明的變量的大小的內存塊。然后調用初始化函數(構造函數)來初始化申請到這個內存塊。在delphi的System單元中class function TObject.NewInstance: TObject;begin Result := InitInstance(

3、_GetMem(InstanceSize);end;可以看到對象的create,就是先獲取一塊內存,然后初始化其中的變量。class function TObject.InitInstance(Instance: Pointer): TObject;$IFDEF PUREPASCALvar IntfTable: PInterfaceTable; ClassPtr: TClass; I: Integer;begin FillChar(Instance, InstanceSize, 0); / 全部清0 PInteger(Instance) := Integer(Self); /將類對象賦值給對象

4、的第一個指針。對象通常在堆內存分配,而不在棧上分配,這是由編譯器的策略決定的,因為對象通常在函數內部創建之后還有可能被函數外部使用,如果保留在棧上,那么棧就不能釋放,這是不符合棧的使用要求的。偶爾也有在棧上分配對象的,例如C+語言的聲明: CSomeClass cobject; 這樣的聲明,會讓編譯器直接在棧上創建一個對象,這時cobject對象與struct類型沒有本質區別,因此cobject.someMethod類似結構體的“.”語法結構是正確的。此時需要注意的是,對象僅僅是在函數體內使用才這樣聲明。 下面測試下直接將record(C/C+為struct)變量轉換為對象。 TMyClass

5、Record=record pclass:Pointer; v1:integer; v2:integer; end; 聲明一個與類一樣的記錄結構,這類似C/C+的結構體,然后將第一個指針賦值為類對象地址,這樣這個記錄就是該類的一個對象。這也是可以在delphi中實現棧對象的方式。 Var robj :TMyclassRecord;在棧中創建了一個對象robj, robj. pclass:= TMyClass; 則TMyClass(robj). someMethod方法調用就是(TMyClass.create). someMethod; robj. pclass:= TMyOtherClass;

6、則TMyOtherClass (robj). someMethod方法調用就是(TMyOtherClass.create). someMethod; 這里有意思的就是將一個record類型變量直接變成了一個類的對象來使用,而且你可以轉換成任意的類,只要該record的結構與該類的對象結構相同。如果要在堆中創建對象, CSomeClass cobject = new CSomeClass ();此時,cobject是一個對象指針,其地址是在棧上分配的,這個4字節(32位)的指針的值就是在堆中分配的地址。new會調用CSomeClass 的構造函數,而在此之前還有很多其他代碼,這些代碼負責申請對象

7、內存。此時必須采用cobject -> someMethod這樣的指針引用操作方式,而不是點語法。Delphi編譯器不支持在棧中直接創建對象,因此Delphi中對象變量都是指針處理。類的方法函數與普通函數沒有本質區別,只是這里有一點編譯器為你做了的事情,就是this/self這個指針會默認傳遞給類方法函數。cobject.someMethod(param1);編譯后實際的函數會變成someMethod(cobject,param1);這樣,我們就將對象與類的方法關聯起來了,對于不同的對象調用同樣的方法函數,實際上該函數操作的就是不同的內存數據(對象)。而在方法內直接訪問類的成員屬性或者加

8、上this/self也是一樣,在編譯時會變成cobject+偏移量(偏移量是由編譯器根據變量在對象中的排列來確定的)。這里有一個字節對齊問題,每個編譯器對于對象的字節對齊處理都不同的,這沒有嚴格規定,因此在不同語言之間共享對象時,就必須檢查字節對齊。2) Delphi對象的內存布局:Delphi編譯器只支持Object Pascal語言,這里我們就暫且用Delphi名詞代替Object Pascal。 Delphi比C+具有了更多的動態特性,但是相對于C+,省去了很多繁瑣的技術,如多重繼承、模板等。Delphi相對來說更加簡潔些,功能當然就少些,學起來也更清爽些。個人認為ObjectPasca

9、l簡潔的代碼是所有語言里最為漂亮的。Delphi的對象第一個指針指向類對象,然后緊接著是對象的私有變量和公有變量(屬性)。這里說的“類對象”是什么?就是一塊內存,這塊內存是由編譯器維護的,程序員通常不知道這塊內存的存在,這些內存中存放的就是對于類的一些說明信息,這些信息是編譯器在編譯期收集的,實現語言的RTTI就要靠這些信息。如Delphi的類對象中就保存了對象的大小,屬性的類型、方法表,虛擬函數表,父類指針。在delphi的System單元中定義了類對象的數據結構 vmtSelfPtr = -76; / 保存了指向自己的指針,就是類self的地址。 vmtIntfTable = -72; v

10、mtAutoTable = -68; vmtInitTable = -64; vmtTypeInfo = -60; /屬性類型信息,對象釋放時,根據類型自動釋放屬性指向的對象。 vmtFieldTable = -56; /屬性類型信息,對象釋放時,根據類型自動釋放屬性指向的對象。 vmtMethodTable = -52; /方法表,只有published的方法才被記錄 vmtDynamicTable = -48; /動態方法表,運行期才查詢此表地址來獲得函數調用 vmtClassName = -44; vmtInstanceSize = -40; /實例尺寸=所有變量大小+4字節(第一個指針

11、指向類) vmtParent = -36; /用來查詢父類信息 vmtSafeCallException = -32 deprecated; / don't use these constants. vmtAfterConstruction = -28 deprecated; / use VMTOFFSET in asm code instead vmtBeforeDestruction = -24 deprecated; vmtDispatch = -20 deprecated; vmtDefaultHandler = -16 deprecated; vmtNewInstance =

12、 -12 deprecated; vmtFreeInstance = -8 deprecated; vmtDestroy = -4 deprecated; vmtQueryInterface = 0 deprecated; /這些是基于COM的聲明, vmtAddRef = 4 deprecated; vmtRelease = 8 deprecated; vmtCreateObject = 12 deprecated;如果不是創建COM對象,0偏移地址處就是第一個虛擬方法。當Delphi要引用COM對象時,那么就可以將COM對象指針的0偏移處轉換為QueryInterface方法。當我們創建一

13、個對象時,對象的第一個指針就指向類對象0偏移處,例如我們可以獲得對象的地址然后通過這個地址中的第一個指針獲得類對象地址,然后將這個地址-40,就可以獲得類的名稱。等下我們講解在C+中怎樣通過在DLL中輸出的Delphi對象來獲得其類名。 注意Delphi的對象self與類的self是不同的,類的self指向了類對象的vmtSelfPtr。 3) Delphi中類函數的類型(1) 普通函數:在編譯時就會變成實際的地址,因為這些函數地址是確定的,因此除非你自己編寫函數將這些函數的地址保存到一個表,否則在運行時,你就沒法再通過代碼來查詢到這些函數地址。這樣的函數,在C+中是沒法訪問的(除非你在調試時

14、記錄這個函數加載到內存的位置)。(2) published 函數這些函數會被記錄到vmtMethodTable指向的方法表中,那么我們只要獲得這個對象指針,根據對象指針找到類對象,然后找到方法表,通過遍歷方法表就可以獲得指定函數名稱的方法。方法表中記錄了方法的地址和名稱以及參數類型。對于一個已經編譯好的VCL庫,delphi設計器一樣采用了方法表來獲得類支持的方法函數,因為delphi只需要動態管理published的方法,對于public方法是在編譯期確定的,不公布給其他動態調用者。方法描述符:前2個字節為描述符長度,然后是方法地址4字節,緊接著是方法名字符串。class function

15、TObject.MethodAddress(const Name: ShortString): Pointer;MethodAddress是用匯編寫的,為了提高速度。(3) virtual函數虛擬函數,在C+中也有這個概念,是實現多態的機制。虛函數的地址并不是在運行期才確定。虛擬方法表的地址是在編譯期就確定的,只是對于虛方法表的調用編譯器處理時會轉個彎,并不直接調用函數地址,而是調用虛擬方法表的地址。而多態其實就是因為對象指針的變化導致引用到的虛擬方法表也不同。SuperClass Obj = new SomeClass1();Obj.virtualMethod(); /調用的是SomeCla

16、ss1虛擬方法表中的方法。Obj = new SomeClass2();Obj.virtualMethod(); /調用的是SomeClass2虛擬方法表中的方法。同樣的語句,但是調用的是不同的方法,這里其實有一個隱含參數this/self。通過Obj本身找到了類對象,然后通過類對象找到了虛擬方法。因為Obj本身指向的對象變了,其找到的方法自然也就不同。虛擬方法在編譯時,編譯器會將其注冊到以上類對象0偏移地址開始處。因此通過對象指針找到類對象時,第一個指針就是第一個虛擬方法。Obj.virtualMethod();編譯后類似 MOV EAX,Obj /EAX為對象地址 MOV EAX ,EAX

17、 /EAX為類對象地址 Call EAX+4 /類對象地址開始的第二指針也就是第二個虛擬函數地址。(4) dynamic動態函數生命為dynamic的函數,在編譯時,會進入vmtDynamicTable指向的動態函數表,當子類繼承時,并不會復制表中的內容到子類的類對象。因此子類在調用聲明為dynamic的函數時,編譯器并不檢查是否有該函數的實現,編譯器會產生一個查詢方法然后再調用的代碼,因此對于Obj.dynamicMethod方法的調用,編譯后會是一段比較長的代碼,而不是簡單的Call指令。當然這段代碼是一個可共享的函數,是由編譯器支持庫提供的。動態方法可以在0地址上操作:SomeClass

18、1(0).dynamicMethod,運行時并不出錯,因為運行時,查詢方法函數在0地址對象上是找不到該方法的。正因為動態方法表并不復制到子類,因為當在子類vmtDynamicTable找不到該方法時,還要查詢父類的vmtDynamicTable,其速度比直接的方法調用慢很多。(5) abstract 抽象函數抽象函數只有聲明,沒有實現。類似java里面的interface,不能直接調用抽象方法,子類實現該方法后,就可以調用該方法,引入抽象方法的目的是為了實現對一類子類對象的操作。抽象方法必須同時聲明為virtual或dynamic,這樣做的原因很簡單,因為對于abstract函數的調用,在編譯

19、時是不能確定地址的,既然不能確定地址,就必須采用一個中間表來緩沖地址,編譯時調用的就是這個緩沖表中對應的地址,這個緩沖表就是虛擬方法表或動態方法表。同虛擬方法表中的地址一樣,地址是在編譯時就確定的(因為運行期是不能修改代碼段的,所謂的動態綁定其實也是通過訪問不同的表來查詢到不同的函數地址),那么對于父類的抽象方法,編譯器是怎樣處理的呢,其虛擬方法表(或動態方法表)中對應的地址是AbstractError函數的地址,當我們試圖調用父類的抽象方法時,編譯器并不會報錯,但是運行時就會彈出AbstractError窗口。而子類實現該抽象方法后,該地址就會替換成實現函數的地址。調用Delphi中對象方法

20、幾種可能:1) 通過對象指針找到類對象的虛函數表,通過虛函數表來訪問函數,這里必須清楚其虛函數表的順序。2) 通過對象指針找到類對象的函數表,通過查詢函數表來找到目標函數的地址。3) 在delphi的dll文件中創建輸出函數,函數返回一個函數指針表,將需要輸出的函數注冊到這個函數指針表中。第一種方式:訪問Delphi對象的虛擬方法1) 建立一個delphi的DLL項目。創建一個類 MyTestClass=class(TObject) ivar1:Integer; ivar2:integer; published /這里聲明為publish方便我們進行第二種通過查找函數表來獲得函數地址。 fun

21、ction add(v1,v2:integer):integer;virtual; stdcall; function add2:integer;virtual;stdcall; function add3:integer;virtual;stdcall; end;實現三個測試函數: add為v1+v2;add2為ivar1+ivar2, add3返回固定值33;2) 在dll中輸出對象function createObject(v1, v2:Integer):MyTestClass;stdcall;exports createObject;創建一個MyTestClass對象并返回對象指針。3

22、) 編譯成dll文件MyDelphiDll.dll,此時dll輸出函數為createObject一個函數4) 在delphi中調用該dll中創建的對象的方法(一)。function createObject(v1,v2:Integer):Pointer;stdcall; external 'MyDelphiDll.dll' var addFunctionPointer: function(aobj:Pointer;v1,v2:Integer):Integer;stdcall; add2FunctionPointer: function(aobj:TMyClass):Intege

23、r;stdcall;pobj:Pointer;pobj := createObject(); / 獲得創建對象的指針。addFunctionPointer := Pointer(Pointer (pobj); /對象指針取其地址中的值就是類對象add2FunctionPointer := Pointer(Pointer(integer(pobj )+4); /類對象的第二個指針是第二個虛函數addFunctionPointer (pobj, 1,2); add2FunctionPointer ();這里可以看到,函數指針原型聲明中第一個參數是對象本身,可以聲明為Pointer或其他類型指針,這

24、里形參并不重要,因為只要我們傳入的是有效的對象,那么調用該函數就會是成功的。對于程序員來說,必須非常清楚對于對象方法的調用,第一個參數必須是對象。這里還有一點需要說明:stdcall這個定義,在后面會詳細介紹,如果DLL程序中沒有聲明stdcall,那么這里也函數指針聲明也應該去掉,以保證編譯器在兩處的函數調用的參數傳遞的一致性,否則調用就變成了雞跟鴨講。5) 通過類擬合方式調用dll的輸出對象方法(二)在要使用dll的delphi項目中聲明一個與dll中聲明的一樣的類;TMyClass=class(TObject) function addMethod(v1,v2:integer):inte

25、ger;virtual;abstract; stdcall; function addMethod2:integer;virtual; abstract;stdcall; function addMethod3:integer;virtual; abstract;stdcall; end;這里三個虛函數方法對應dll中MyTestClass三個方法,只要參數一樣就可以,全部聲明為abstract,因為我們并不想再實現一次這些方法,而是會直接調用dll中的對象方法。 function createObject(v1,v2:Integer):TMyClass;stdcall; external &

26、#39;MyDelphiDll.dll' varpobj: TMyClass;pobj := createObject(); / 獲得創建對象的指針,這里直接將返回指針定義為TMyClass。pobj. addMethod(1,2); /調用的是DLL中對象的MyTestClass.add(1,2);這里需要注意的是方法名并不重要,重要的是方法聲明的順序,因為只要保證DLL中MyTestClass和本地TMyClass編譯后產生的虛方法表是一致的,那么方法調用的時候,pobj. addMethod在編譯后就是對pobj指向的對象的類對象保存的虛方法表中地址的訪問。因為pobj指向dll

27、產生的MyTestClass對象,因此實際調用到的也是該類的虛方法表。即使TMyClass類已經實現了addMethod方法,此處的訪問依然不會執行TMyClass. addMethod。其實這里TMyClass的定義只是告訴編譯器可以將pobj看做TMyClass的對象來產生代碼。因此這里其實是巧妙地利用了兩個類的虛擬方法的重合來實現方法調用。6) 注意,這里介紹的方法僅僅是能訪問dll輸出對象的虛擬方法,對于對象的普通方法函數還是不能訪問的。7) C+調用Delphi中的對象(一)c+訪問delphi創建的dll中的對象,與在delphi中訪問自己創建的dll是基本相同的。創建一個C+項目

28、,動態加載MyDelphiDll.dll文件,獲得createObject函數地址GetSomeObjFunction getSomeObj = (GetSomeObjFunction)GetProcAddress(hDll, "createMyObject"); 然后調用函數創建對象; int * pobj = getSomeObj(1,2); 這里我們不關心其返回對象的類型,我們只需要指針。 定義一個函數指針typedef int(_stdcall *Add)(int * obj, int v1, int v2);函數原型要與dll中輸出的虛擬函數相同,注意這里的int

29、 * obj是對象指針。然后我們將獲得的對象指針的指向的類對象地址轉換成add虛函數引用。(Add )(*(int*)*pobj)(obj,1,2)/這里的語法有點亂,*pobj是取指針的內容,也就是對象地址,(int*)將地址中的內容轉成int指針,也就是取對象的前4個字節,匯編指令會變成 dword ptr *pobj,然后取這個int指針中的內容(*(int*)*pobj)),也就是類對象的第一個指針指向的內容,也就是第一個虛擬函數的地址。將地址轉換成函數指針,然后調用函數。這里我們是通過操作指針來獲得虛函數表中函數的地址的。8) 通過對象擬合來訪問虛函數(二)在C+中聲明一個類似Del

30、phi中的類,class SomeClasspublic:virtual int _stdcall add(int v1, int v2)return 1;virtual int _stdcall addMethod2()return 2;virtual int _stdcall addMethod3()return 3;然后聲明函數指針中返回類型為SomeClass *,typedef SomeClass * PSomeClass;typedef PSomeClass(WINAPI * GetSomeObj)(int v1, int v2);創建對象GetSomeObj createObje

31、ctMethod = (GetSomeObj)GetProcAddress(hDll, "createObject");PSomeClass pobj = GetSomeObj(1,2); 注意上面直接將返回的對象指針變成了SomeClass; 這時我們通過 pobj -> add(10,20); /返回30,而不是上面定義的1 pobj -> addMethod2(); / 返回3,不是上面定義的2,因為實際調用的是Delphi中實現的方法。 這里因為Delphi產生的類對象與C+產生的類對象(實際上就是虛方法表)有一些重合的地方,都是從對象的第一個地址可以找

32、到虛方法表的第一個虛函數地址。Delphi中有三個虛函數,那么我們在C+通過構建一個類似的方法表,只要順序相同就可以調用,兩種編譯器產生的對虛函數的調用方法是相同的(都是通過對類對象基地址的偏移的調用),因此才能這樣操作。這也是Delphi特意這樣安排的虛函數表以與C+兼容,因為在Borland C+中大部分實際上是使用delphi的VCL庫。這里其實還有一個問題需要說明:找到函數地址并不一定就保證能正確調用函數,還有一個函數參數傳遞的問題,因為每個編譯器在處理函數參數傳遞時都有自己的特點。1) Delphi中的函數調用參數傳遞:默認情況下,Delphi采用寄存器傳遞參數,EAX、EDX、EC

33、X等,當參數個數比較多時,則將參數壓棧處理。其實編譯器處理參數傳遞通常是固定的,否則就可能造成不同版本之間的函數調用無法兼容。在Delphi中,第一個參數是EAX,第二個參數是EDX,第三個參數是ECX,多余的參數則通過棧傳遞。在TMyClass中聲明一個方法函數function TMyClass.paramTest(v1,v2,v3:integer):integer; register;/默認為register,通常省略。begin result := v1+v2;end;可以看到EAX 被賦予了esi,esi是一個臨時保存對象地址的寄存器,由上一條Create對象語句賦值。選擇eax做第一

34、個參數也是有原因的,對于CPU一些需要直接操作eax的指令,就不需要復制數據。EDX保存了第一個參數v1,因為edx通常作為臨時存放數據,ecx通常選做計數器,ebx則通常被選擇做被加數,因此優先選edx寄存器保存第二參數。其實編譯器并不一定要這樣處理參數傳遞,參數傳遞有N多種組合,就視編譯器設計者的策略而定,而細節也決定了一個編譯器產生的程序的速度。Delphi有一個非常優秀的編譯器,其產生的程序速度是相當優化的。register方式的處理參數是從左到右,如果還有第4個參數v4,那么是在push v3之后的。如果我們將函數聲明為stdcall參數傳遞方式呢function paramTest

35、(v1,v2,v3:integer):integer;stdcall;可以看到,參數是全部通過push方式壓入棧中,而且也是從右到左。參數的傳遞部分的代碼必須與函數內部的接收參數的代碼相同。這些代碼都是編譯器創建的,如果你直接編寫一個assembly(直接用匯編編寫)的方法,那么你就要注意這些參數的保存位置。2) 棧的清除是誰負責的問題上面的代碼我們看到,棧被壓入了4個參數,那么自然ESP會發生變化,但是調用完成后,并沒有對應的POP指令,因為Delphi的pascal調用和stdcall調用都是被調用函數負責清理棧的,如果我們將調用方式聲明為cdecl類型,則在調用完成后,編譯器會加上修改e

36、sp的代碼。這里是$10,也就是10進制的16字節,剛好4個參數的大小,對應4個PUSH語句。function paramTest(v1,v2,v3:integer):integer;cdecl;對應的函數體代碼如下:注意沒有修改ESP的指令,僅僅對棧幀寄存器ebp進行了保存和恢復。 如果聲明為pascal調用,對象指針是最后一個入棧的參數Pascal調用的參數傳遞是從左向右,通過棧傳遞,方法體內自己負責清理棧,ret $10指令返回同時讓ESP+16;這里看到ebp+$14指向了參數v1;可見pascal調用與stdcall調用只是參數傳遞順序的不同;Delphi的參數傳遞方式:參數傳遞方式

37、參數處理順序誰負責清棧用途,語言之間的兼容register從左到右函數默認,只有Delphi編譯器用pascal從左到右函數c/c+的_pascal聲明函數stdcall從右到左函數Windows中的API采用的方式cdecl從右到左調用者C/C+語言默認采用的方式Safecall從右到左函數會在調用者和函數都產生大量的安全檢查代碼。自動的COM異常處理。3) 讓C+中函數調用方式與Delphi協調一致,通常做法就是聲明為stdcall,因為這是windowsAPI的默認方式。C/C+中,為了向后兼容,弄出了很多帶下劃線的聲明,一個下劃線的是兼容以前16位版本程序#if defined(DOS

38、WIN32) | defined(_MAC)#define cdecl _cdecl#else#define cdeclMicrosoft VS中win32 C+程序函數調用協議:參數傳遞方式參數處理順序清棧用途,語言之間的兼容函數名約定默認模式從右到左函數ecx保存對象地址,其余參數壓棧,eax為方法地址C+自己用途同_cdecl_fastcall從左到右函數ECX第一個參數,edx第二個參數,其余壓棧,eax為方法地址 c+自己用途C: FunctioNnameNumberC+:?functionnameYI*Zpascal_stdcall與delphi的pascal完全不同_stdcal

39、l從右到左函數Windows中的API采用的方式C:_FunctionNameNumberC+:?FNYG*Zcdecl (32win)默認模式_cdecl從右到左調用者C的默認方式_FunctionNameC+:?FNYA*Z?函數名+YI+返回類型+參數類型+Z因此delphi與C+保證調用的一致性可以通過 _stdcall方式或_cdecl方式VS中聲明一個類的方法函數virtual int _cdecl add(int v1, int v2) return v1 + v2;通過棧從右到左壓入參數,edx保存了對象地址,eax則保存了第一個虛擬方法的地址。可見到_cdecl調用是調用者負

40、責清棧的(見上面的指令add esp, 0ch),在每個方法調用之后,C+編譯器還加入了檢查esp是否正確的代碼,這樣做的原因就是為了保證被調用函數與調用者具有同樣的調用協議。例如delphi中采用stdcall聲明,而此處采用_cdecl聲明,當采用上述擬合方式調用delphi的對象方法后,則esi與esp寄存器會不相等,說明函數調用完成后棧指針沒有復原。此時C+編譯器加入的_RTC_CheckEsp會拋出異常。采用stdcall調用協議,則調用者只需要將參數壓棧,而被調用函數則負責清棧。virtual int _stdcall add(int v1, int v2) return v1 +

41、 v2;以上已經分析了C+通過函數地址調用delphi對象的方法函數。也討論了調用時必須遵從的調用協議。下面我們討論另外一種更加方便的方法實現C+對delphi對象的函數訪問。上面的方法是通過虛擬方法表來訪問delphi對象的方法的,這樣的方式只能訪問聲明為virtual的函數,對于普通方法函數就沒法訪問。下面我們通過查詢Delphi類對象的方法表(vmtMethodTable)來訪問Delphi對象的方法,注意只有聲明為published的方法才能被查詢到。我們可以在C+中自己編寫方法來遍歷vmtMethodTable,但是Delphi的systemd單元其實已經有編寫了這樣一個方法,cla

42、ss function TObject.MethodAddress(const Name: ShortString): Pointer;那么我們只要將這個方法輸出到dll,那么我們在C+中就可以直接通過這個輸出的DLL函數查詢類的方法了。function queryMethodAddress(mname:String):Pointer;stdcall;begin result := MyTestClass.MethodAddress(mname);end;exports createObject, queryMethodAddress;在C+中要聲明該函數指針原型,typedef int*(W

43、INAPI *QueryMethodAddress)(char *);獲得DLL中的輸出函數QueryMethodAddress queryMethodAddress = (QueryMethodAddress)GetProcAddress(hDll, "queryMethodAddress");要定義返回的函數指針原型,例如要查詢add方法地址,那么首先要定義函數指針typedef int(_stdcall *Add)(PMyTestClass obj, int v1, int v2);可以通過函數指針查詢方法地址。Add add = (Add)queryMethodAd

44、dress(“add”);那么就可以用add(obj,1,2)方式調用到對象的方法。其實上面的語句并不能獲得add方法,實際上這里還有個問題,就是delphi中String對象與C+中string是不同的,在C/C+中沒有String,我們在delphi中與windowsAPI函數交互時,經常要進行字符串轉換PChar/PAnsiChar,因為兩種字符串在內存的表示是不同的,我們知道,在操作一個字符串時必須要知道字符串長度,在c/c+中,字符串通常就是以0字符結尾,一旦處理函數遇到0字符,那么就認為字符串結束,因為我們可以通過這樣構建字符串:char string5 = t,e,s,t,0;其

45、實在很多內存拷貝函數中,總是會附帶一個buffer_length參數,因為數據通常也可能包含0字符。在delphi中string其實是可以包含0字符的,delphi的短字符串String最多只能255個字符,為什么呢?delphi構建字符時,沒有在末尾加上0,而是在開始的一個字節寫入了字符串長度,字符串指針傳遞給copy等處理函數時,這些函數首先是從第一個字節獲得字符串長度,然后再進行操作的。因為要將delphi格式的字符串轉換成c語言可以接受的格式,就要用PChar函數處理,其做的工作就是移動字符串并加上0。那我們是不是可以通過如下方式來處理呢:char methodName4 = 3,a,d,d;Add add = (Add)queryMethodAddress(methodName);在Delphi中MethodAddress方法對字符串的處理就是這樣的字符串格式的方法名,但是這里有一個不一樣的地方就是queryMethodAdd

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論