Delphi調用C++的對象

在Delphi調用由C++導出的類的對象

原理

調用方式是C++中使用純虛函數,在Delphi中也就是純虛類,他們有着相同的佈局的虛方法表。每個Delphi的類都有一張VMT表,VMT中包含了一些基礎信息、一些獲得運行時信息的方法以及虛方法指針。因爲佈局相同,所以可以互相調用。
但是C++的類還是C++的類,Delphi類都繼承於TObject,而C++沒有這個概念。所以,獲得了C++的類,不能去嘗試調用TObject的方法。一般情況下都是將一些功能型模塊封裝出接口,導出給Delphi用。

方式

將C++的類封裝成DLL,並導出一個新建對象接口,Delphi層加載DLL後,調用該接口去創建C++類的對象,這樣在Delphi層就可以直接使用這個對象。

環境說明

Delphi XE8
Visual studio 2017

特別注意

  1. 注意內存管理問題,哪一層申請的內存,最好在哪一層釋放,涉及到傳遞內存塊或者指針的時候,最好的方式是做一次內存拷貝操作。
  2. 傳值時,注意類型要對應,XE8中的string和C++層的string完全不一樣,應該是對應C串,即char*。
  3. 申明結構體需要注意雙方的對齊方式。在Delphi裏,record前面如果加了packed,就不會被編譯器強制對齊。
  4. 注意編碼,XE8中的字符一個char佔用兩個字節,而C++中char佔用一個字節。比如:XE8傳字符串 ‘aaa’給C++,C++層接收到的內容是:#97#0#97#0#97#0,碰到第一個#0就截斷了,只能接到第一個字符了。遇到這個問題,解決辦法就是將它視爲一塊無類型內存塊。(Delphi2007中char佔用一個字節,就不用考慮這個問題了)

如下代碼所示:

struct TEntityNode
{
...
	void* key;	//指向一塊內存塊
	int lenKey;	//這塊內存塊的size
...
};

C++層

demo是在以前的代碼上修改的

  • 首先定義一個類,包含一些需要導出的純虛方法。
//Interface.h
#pragma once

#ifndef INTERFACE_H
#define INTERFACE_H

#include "stdafx.h"
#define API_EXPORT __declspec(dllexport)

#pragma pack(push, 1)
struct TEntityNode
{
	TEntityNode* xPrev;
	TEntityNode* xNext;
	TEntityNode* yPrev;
	TEntityNode* yNext;
	void* key;
	int lenKey;
	int x;
	int y;
};
#pragma pack(pop)

#pragma pack(push, 1)
struct TAddNode
{
	void* key;
	int lenKey;
	int x;
	int y;
};
#pragma pack(pop)

typedef void (*Fun_Print)(TEntityNode*);

struct TCallBackFuncRcd
{
	Fun_Print print;
};

class TScene {
public:
	virtual bool __stdcall Init() = 0;
	virtual void __stdcall Free() = 0;
	virtual void __stdcall registerCallBack(TCallBackFuncRcd rcd) = 0;
	virtual TEntityNode* __stdcall Add(TAddNode* node) = 0;
	virtual TEntityNode* __stdcall Get(void* key, int lenKey) = 0;
	virtual bool __stdcall Leave(void* key, int lenKey) = 0;
	virtual TEntityNode* __stdcall Move(void* key, int lenKey, int x, int y) = 0;
	virtual void __stdcall PrintAll(bool isPrintX) = 0;
	virtual void __stdcall PrintAOI(TEntityNode* node, int xArea, int yArea) = 0;
};

extern "C" API_EXPORT TScene *NewScene();

#endif
  • 這裏定義好了類的全部內容,但未實現方法,末尾處導出了一個方法(方法的實現見後面),用於創建實現類的對象。
extern "C" API_EXPORT TScene *NewScene();
  • 然後,再看實現類的實現部分。實現類中可以添加一些自己額外的成員,但僅限於C++類的內部調用,Delphi層調用不到。
//Handler.h

class TSceneHandler : public TScene {
public:
	virtual bool __stdcall Init();
	virtual void __stdcall Free();
	virtual void __stdcall registerCallBack(TCallBackFuncRcd rcd);
	virtual TEntityNode* __stdcall Add(TAddNode* node);
	virtual TEntityNode* __stdcall Get(void* key, int lenKey);
	virtual bool __stdcall Leave(void* key, int lenKey);
	virtual TEntityNode* __stdcall Move(void* key, int lenKey, int x, int y);
	virtual void __stdcall PrintAll(bool isPrintX);
	virtual void __stdcall PrintAOI(TEntityNode* node, int xArea, int yArea);
private:
	void _add(TEntityNode* node);
private:
	TEntityNode * head;
	TEntityNode * tail;
	Fun_Print fPrint;
};

  • 比較有意思的是,我們還可以將Delphi的方法作爲回調註冊到C++的類中來,做完一系列操作後,可以調用這個回調函數回調內容回去。
    方式也很簡單,只需要加載Dll的時候,將回調函數的指針傳給C++層即可。
typedef void (*Fun_Print)(TEntityNode*);

struct TCallBackFuncRcd
{
	Fun_Print print;
};

void TSceneHandler::registerCallBack(TCallBackFuncRcd rcd)
{
	fPrint = rcd.print;
}

這裏我註冊的是一個打印函數。

  • 下面貼出C++層Add方法的實現(全部代碼放在最後)。
//Handler.cpp
TEntityNode* TSceneHandler::Add(TAddNode * node)
{
	//這裏將內容拷貝過來,以防在Delphi層被釋放,進而引發內存訪問異常的問題
	TEntityNode* retNode = new TEntityNode();
	retNode->lenKey = node->lenKey;
	retNode->key = new char(node->lenKey);
	memcpy(retNode->key, node->key, node->lenKey);

	retNode->x = node->x;
	retNode->y = node->y;
	_add(retNode);

	return retNode;
}


  • 最後,實現類寫好以後。我們需要提供一個接口,用於創建實現類的對象,上面已經將這個接口導出了,我們看看這個接口的具體實現。
//Interface.cpp
#include "stdafx.h"
#include "Interface.h"
#include "handler.h"

API_EXPORT TScene* NewScene() {
	return new TSceneHandler();
}

直接返回新創建的對象

Delphi層

  • 首先,把純虛類寫好。類中的方法前後順序要與上面的一致,方法的參數類型要一致,沒有對應的類型,使用無類型指針、內存塊傳遞。

純虛類定義如下

unit dll;

interface

type
  PEntityNode = ^TEntityNode;
  TEntityNode = packed record
    xPrev   : PEntityNode;
    xNext   : PEntityNode;
    yPrev   : PEntityNode;
    yNext   : PEntityNode;
    Key     : Pointer;
    lenKey  : Integer;
    X       : Integer;
    Y       : Integer;
  end;

  PAddNode = ^TAddNode;
  TAddNode = packed record
    Key     : Pointer;
    lenKey  : Integer;
    X       : Integer;
    Y       : Integer;
  end;

  TCallBackFuncRcd = record
    print: Pointer;
  end;

type
  TScene = class
  public
    function Init: Boolean; virtual; stdcall; abstract;
    procedure Free; virtual; stdcall; abstract;
    procedure registerCallBack(rcd: TCallBackFuncRcd);virtual; stdcall; abstract;
    function Add(const node: PAddNode): PEntityNode; virtual; stdcall; abstract;
    function Get(const key: Pointer; const lenKey: Integer): PEntityNode; virtual; stdcall; abstract;
    function Leave(const key: Pointer; const lenKey: Integer): Boolean; virtual; stdcall; abstract;
    function Move(const key: Pointer; const lenKey: Integer; const x, y: Integer): PEntityNode; virtual; stdcall; abstract;
    procedure PrintAll(const isPrintX: Boolean);virtual; stdcall; abstract;
    procedure PrintAOI(const node: PEntityNode; xArea, yArea: Integer); virtual; stdcall; abstract;
  end;

implementation

end.

  • 然後從DLL中導入創建類的對象的接口
function NewScene(): TScene; external 'Scene.dll';

調用NewScene方法就可以獲得對象了

  • 初始化的時候,調用NewScene,並注入回調函數
procedure TForm2.print(pNode: PEntityNode);
var
  lvKey: string;
begin
  if not Assigned(pNode) then
  begin
    mmo1.Lines.Add('Print-->節點不存在');
    Exit;
  end;
	
  //這裏除以了2是因爲一個char佔用兩個字節
  //且,內存長度的計算是用的ByteLength()方法,而不是Length()方法
  SetString(lvKey, PChar(pNode.Key), pNode.lenKey div 2);
  mmo1.Lines.Add(Format('%s - (%d, %d)', [lvKey, pNode.x, pNode.y]));
end;

procedure TForm2.FormCreate(Sender: TObject);
var
  rcd: TCallBackFuncRcd;
begin
  FScene := NewScene;

  if not FScene.Init then
  begin
    mmo1.Lines.Add('初始化失敗');
    Exit;
  end;

  rcd.print := @GPrint;
  FScene.registerCallBack(rcd);
end;
  • 記得要釋放,釋放時候要調用DLL中的類的自己定義的釋放方法。
procedure TForm2.FormDestroy(Sender: TObject);
begin
  FScene.Free;
end;
  • 同樣的,這裏也貼出Delphi層的Add方法(全部的代碼放在最後


function TForm2.AddNode(const Key: string; const X, Y: Integer): PEntityNode;
var
  addNode: PAddNode;
begin
  New(addNode);
  addNode.Key := PChar(Key);
  //注意使用ByteLength(),Length()函數會少算一半長度
  addNode.lenKey := ByteLength(Key);
  addNode.X := X;
  addNode.Y := Y;
  Result := FScene.Add(addNode);
  //C++層做了拷貝操作
  Dispose(addNode);
end;

全部代碼,我放在了我的資源裏,歡迎下載
//download.csdn.net/download/I_can_/12007246
如有不足,請多指教

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章