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占用一个字节,就不用考虑这个问题了)
  5. 请注意函数调用约定。

如下代码所示:

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 (__stdcall *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* __stdcall NewScene();

#endif
  • 这里定义好了类的全部内容,但未实现方法,末尾处导出了一个方法(方法的实现见后面),用于创建实现类的对象。
extern "C" API_EXPORT TScene* __stdcall 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* __stdcall 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; __stdcall 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
如有不足,请多指教

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值