在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
特别注意
-
注意内存管理问题,哪一层申请的内存,最好在哪一层释放,涉及到传递内存块或者指针的时候,最好的方式是做一次内存拷贝操作。
- 传值时,注意类型要对应,XE8中的string和C++层的string完全不一样,应该是对应C串,即char*。
-
申明结构体需要注意双方的对齐方式
。在Delphi里,record前面如果加了packed,就不会被编译器强制对齐。 -
注意编码,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 (__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
如有不足,请多指教
版权声明:本文为I_can_原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。