Delphi采用接口实现DLL调用
Delphi使用模块化开发,可以采用DLL或者BPL,两者的区别是BPL只能被同版本的Delphi使用,DLL可以被不同版本和不同开发工具的开发的软件调用。
因此我们的软件大多使用Delphi作为界面以及部分DLL模块的开发工具。
DLL模块之间通过接口方式调用。
1.对象创建采用工厂模式,每个DLL负责某个对象或若干个对象的创建及释放,例如:
DLL工程为http客户端(prjHttp.DLL)模块,通过DLL导出的GetHttpClientFactory获取http客户端工厂接口,通过接口创建Http客户端和释放Http客户端,工程
包括3个文件:工程文件,实现单元,接口单元。
调用此DLL的程序仅需要包含接口单元。
DLL工程文件
library prjHttp;
uses System.SysUtils, System.Classes, utHTTPClient in 'utHTTPClient.pas';
{$R *.res}
exports
GetHttpClientFactory;
end.
utHttpClient示例
unit utHttpClient; interface uses utBaseObject, utHttpInterface, Classes, SysUtils; type
......... THTTPClientConnection = class(TIntObject, IHTTPClientConnection)
public
function Connect: Boolean;
function Info: IHTTPClientConnectionInfo;
function TcpConnection: ITcpConnection;
function DataConnection: IConnection;
function Param:IHttpClientConnectionParam;
public
constructor Create;
destructor Destroy; override;
end; THttpClientConnectionFactory = class(TIntObject, IHttpClientConnectionFactory)
protected
FObjectPool: THTTPClientConnectionPool;
public
constructor Create;
destructor Destroy; override;
procedure CreateHttpClient(out Conn: IHTTPClientConnection);
procedure DestroyHttpClient(var aClient);
end; function GetHttpClientFactory: IHttpClientConnectionFactory; implementation ............ var
HttpClients: THttpClientConnectionFactory; function GetHttpClientFactory: IHttpClientConnectionFactory;
begin
if not Assigned(HttpClients) then
HttpClients := THttpClientConnectionFactory.Create;
Result := HttpClients;
end; initialization
finalization
if Assigned(HttpClients) then FreeAndNil(HttpClients);
end.
utHttpInterface接口文件示例
unit utHttpInterface; interface uses utBaseInterface; const
IID_IHTTPClientConnectionInfo = '{24C3D6BF-EC3D-4783-AD98-A5C6E4F24F19}';
IID_IHTTPClientConnectionParam = '{0FA49A71-48BF-40CD-9D77-63B233C4F717}';
IID_IHTTPClientConnection = '{78C39E26-A690-4022-9E97-6035768CE75C}';
IID_IHTTPClientConnectionEvent = '{2FB0AC19-9994-4E77-B105-121192943EBC}';
IID_IHttpClientConnectionFactory = '{429C5C2B-C1E3-4871-9631-E3B943619EFD}'; GUID_IHTTPClientConnectionInfo: TGUID = IID_IHTTPClientConnectionInfo;
GUID_IHTTPClientConnectionParam: TGUID = IID_IHTTPClientConnectionParam;
GUID_IHTTPClientConnection: TGUID = IID_IHTTPClientConnection;
GUID_IHTTPClientConnectionEvent: TGUID = IID_IHTTPClientConnectionEvent;
GUID_IHttpClientConnectionFactory = IID_IHttpClientConnectionFactory;
type
IHttpClientConnectionParam = interface
['{0FA49A71-48BF-40CD-9D77-63B233C4F717}']
function TcpParam: ITcpConnectionParam;
function GetMethod: PAnsiChar;
function GetPathAndParams: PAnsiChar;
function GetAgent: PAnsiChar;
function GetHeader: PAnsiChar;
function GetData: PAnsiChar;
function GetUserName: PAnsiChar;
function GetPassword: PAnsiChar;
procedure SetValue(const ServerAddr: PAnsiChar; const ServerPort: Integer; const UserName, Password, Method, PathAndParams, Agent, Header, Data: PAnsiChar);
end; IHTTPClientConnectionInfo = interface(ITcpConnectionInfo)
['{24C3D6BF-EC3D-4783-AD98-A5C6E4F24F19}']
function Auth: PAnsiChar;
end; IHTTPClientConnection = interface;
IHTTPClientConnectionEvent=interface
['{2FB0AC19-9994-4E77-B105-121192943EBC}']
procedure OnHeader(const Http:IHTTPClientConnection; const Header:Pointer; const HeaderLenght:NativeInt);
procedure OnStartReceiveContent(const Http:IHTTPClientConnection; const ContentLength:NativeInt);
procedure OnReceiveProgress(const Http:IHTTPClientConnection; const ContentLenght, ContentReceived:NativeInt);
procedure OnError(const Http:IHTTPClientConnection; const ErrStr:PAnsiChar);
end; THttpClientConnectionEvent = (heHeader, heStartReceiveContent, heReceiveProgress, heError); IHTTPClientConnection = interface
[IID_IHTTPClientConnection]
function Connect: Boolean;
function Info: IHTTPClientConnectionInfo;
function TcpConnection: ITcpConnection;
function DataConnection: IConnection;
function Param:IHttpClientConnectionParam;
end; IHttpClientConnectionFactory = interface
[IID_IHttpClientConnectionFactory]
procedure CreateHttpClient(out Conn: IHTTPClientConnection);
procedure DestroyHttpClient(var aClient);
end; implementation end.
调用prjHttp.DLL的Delphi工程可以包含下面的单元以及上面的接口单元utHttpInterface.pas即可
将utHttpDLL.pas中的
//{$define utHttpDLL)
去掉注释,即可以将http客户端这些代码包含到Delphi工程中。
unit utHttpDLL;
//{$define utHttpDLL}
interface
uses utHttpInterface, utBaseInterface;
var
HttpClientFactory: IHttpClientConnectionFactory;
implementation
{$ifdef utHttpDLL}
uses Windows, SysUtils;
const
DLLName = 'prjHttp.DLL';
type
Proc = function: IInterface;
var
LibHandle: THandle;
function GetHttpClientFactory: IHttpClientConnectionFactory;
begin
Result := HttpClientFactory;
end;
procedure Init;
var
P: Proc;
begin
LibHandle := SafeLoadLibrary(DLLName);
if LibHandle <> INVALID_HANDLE_VALUE then
begin
P := GetProcAddress(LibHandle, 'GetHttpClientFactory');
if Assigned(P) then
HttpClientFactory := IHttpClientConnectionFactory(P);
end
else
raise Exception.Create('无法打开库文件' + DLLName);
if not Assigned(HttpClientFactory) then
raise Exception.Create(DLLName + '找不到指定函数');
end;
procedure Done;
begin
if LibHandle <> INVALID_HANDLE_VALUE then
FreeLibrary(LibHandle);
Pointer(HttpClientFactory) := nil;
end;
{$else}
uses utHttpClient;
procedure Init;
begin
HttpClientFactory:= GetHttpClientFactory;
end;
procedure Done;
begin
Pointer(HttpClientFactory):=nil;
end;
{$endif}
initialization
Init;
finalization
Done;
end.
2.DLL中输出接口对象的生命周期管理
Delphi对接口采用引用计数的方法管理对象生命周期,但是DLL中输出的对象可能不是被Delphi调用,其引用计数不一定正确,因此DLL中接口对象的生命周期不由Delphi编译器自动生成的代码管理,而是程序员自己控制,所以上面
的工厂包括构造和解析两个接口对象的生命周期管理方法。
所有接口对象应该集成自下面的接口,而不应该继承自Delphi自带的TInterfacedObject:
TIntObject = class(TObject, IInterface)
protected
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end; function TIntObject.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
if GetInterface(IID, Obj) then
Result :=
else
Result := E_NOINTERFACE;
end; function TIntObject._AddRef: Integer;
begin
Result := -;
end; function TIntObject._Release: Integer;
begin
Result := -
end;
3.自管理接口对象在Delphi调用注意事项
1)接口赋值
错误代码:(Delphi编译器产生代码会先判断接口指针是否为nil,如果非nil自动调用接口的_Release方法)
var P1:IHttpServer
。。。。。。。。。。。。
P1:=FServer.Param;
P1.SetValue(PWideChar(aName), PAnsiChar(AnsiString(ServerAddr)), ServerPort, , ,, );
建议代码:
var P1:IHttpServer
................
Pointer(P1):=nil;
P1:=FServer.Param; //如果赋值前P1不是nil,程序会线调用P1._Release后再赋值
2)局部接口变量
错误代码:
constructor TTcpServerSplitter.Create(aName:String; ServerAddr: String; ServerPort: Integer;
RemoteAddr: String; RemotePort: Integer);
var
Service:IInterfaceObservable;
P1:ITcpConnectionServerParam;
P2:ITcpConnectionParam;
begin
inherited Create;
FServerEvent:=TTcpConnectionServerEventAdapter.Create(Self as ITcpConnectionServerEvent);
FTcpConnectionEvent:=TTcpConnectionEventAdapter.Create(Self as ITcpConnectionEvent);
FConnectionEvent:=TConnectionEventAdapter.Create(Self as IConnectionEvent); TcpServerFactory.CreateTcpConnectionServer(FServer);
P1:=FServer.Param;
P1.SetValue(PWideChar(aName), PAnsiChar(AnsiString(ServerAddr)), ServerPort, , ,, );
RegistObserver(FServer, FServerEvent);
TcpClientFactory.CreateTcpConnection(FRemote);
P2:=FRemote.Param;
P2.SetValue(PAnsiChar(AnsiString(RemoteAddr)), RemotePort, Self);
RegistObserver(FRemote,FTcpConnectionEvent);
end;
上面代码中运行退出后,Delphi编译器会在此代码后面自动调用P1._Release; P2._Release,
建议代码:
constructor TTcpServerSplitter.Create(aName:String; ServerAddr: String; ServerPort: Integer;
RemoteAddr: String; RemotePort: Integer);
var
Service:IInterfaceObservable;
P1:ITcpConnectionServerParam;
P2:ITcpConnectionParam;
begin
inherited Create;
FServerEvent:=TTcpConnectionServerEventAdapter.Create(Self as ITcpConnectionServerEvent);
FTcpConnectionEvent:=TTcpConnectionEventAdapter.Create(Self as ITcpConnectionEvent);
FConnectionEvent:=TConnectionEventAdapter.Create(Self as IConnectionEvent); TcpServerFactory.CreateTcpConnectionServer(FServer);
P1:=FServer.Param;
P1.SetValue(PWideChar(aName), PAnsiChar(AnsiString(ServerAddr)), ServerPort, , ,, );
RegistObserver(FServer, FServerEvent);
TcpClientFactory.CreateTcpConnection(FRemote);
P2:=FRemote.Param;
P2.SetValue(PAnsiChar(AnsiString(RemoteAddr)), RemotePort, Self);
RegistObserver(FRemote,FTcpConnectionEvent);
Pointer(P1):=nil;
Pointer(P2):=nil;
end;
3)函数返回值为接口指针
如下面的示例中FServer.Param定义为function THttpServer.Param:IHttpServerParam,返回的是接口类型,下面的代码直接调用Param.SetValue方法:
constructor TTcpServerSplitter.Create(aName:String; ServerAddr: String; ServerPort: Integer;
RemoteAddr: String; RemotePort: Integer);
var
Service:IInterfaceObservable;
P1:ITcpConnectionServerParam;
P2:ITcpConnectionParam;
begin
inherited Create;
FServerEvent:=TTcpConnectionServerEventAdapter.Create(Self as ITcpConnectionServerEvent);
FTcpConnectionEvent:=TTcpConnectionEventAdapter.Create(Self as ITcpConnectionEvent);
FConnectionEvent:=TConnectionEventAdapter.Create(Self as IConnectionEvent); TcpServerFactory.CreateTcpConnectionServer(FServer);
FServer.Param.SetValue(PWideChar(aName), PAnsiChar(AnsiString(ServerAddr)), ServerPort, 10000, 10,0, 40000);
RegistObserver(FServer, FServerEvent);
TcpClientFactory.CreateTcpConnection(FRemote);
FRemote.Param.SetValue(PAnsiChar(AnsiString(RemoteAddr)), RemotePort, Self);
RegistObserver(FRemote,FTcpConnectionEvent);
end;
上面的代码,Delphi编译器会自动生成两个接口变量,保存FServer.Param和FRemote.Param,由于FServer和FRemote为TTcpServerSplitter对象的全局变量,所以接口在TTcpServerSplitter对象释放时,被调用_Release
将导致内存访问异常。
constructor TTcpServerSplitter.Create(aName:String; ServerAddr: String; ServerPort: Integer;
RemoteAddr: String; RemotePort: Integer);
var
Service:IInterfaceObservable;
P1:ITcpConnectionServerParam;
P2:ITcpConnectionParam;
begin
inherited Create;
FServerEvent:=TTcpConnectionServerEventAdapter.Create(Self as ITcpConnectionServerEvent);
FTcpConnectionEvent:=TTcpConnectionEventAdapter.Create(Self as ITcpConnectionEvent);
FConnectionEvent:=TConnectionEventAdapter.Create(Self as IConnectionEvent); TcpServerFactory.CreateTcpConnectionServer(FServer);
P1:=FServer.Param;
P1.SetValue(PWideChar(aName), PAnsiChar(AnsiString(ServerAddr)), ServerPort, 10000, 10,0, 40000);
RegistObserver(FServer, FServerEvent);
TcpClientFactory.CreateTcpConnection(FRemote);
P2:=FRemote.Param;
P2.SetValue(PAnsiChar(AnsiString(RemoteAddr)), RemotePort, Self);
RegistObserver(FRemote,FTcpConnectionEvent);
Pointer(P1):=nil;
Pointer(P2):=nil;
end;
4)对象中的接口变量,在对象释放时,需要将接口变量清空。
destructor TTcpServerSplitter.Destroy;
begin
Stop;
Pointer(FServer):=nil;
Pointer(FRemote):=nil;
inherited;
end;
Delphi采用接口实现DLL调用的更多相关文章
- delphi dll调用问题
dll传递string实现方法 delphi中dll传递string的实现方法: dll项目uses第一个引用sharemem单元; 调用的项目uses第一个引用sharemem单元; 调用的单元us ...
- delphi 跨版本DLL调用嵌入窗体实现
delphi 能实现把别的DLL的窗体句柄查到后,贴到PANL之中,此类文章网上不少,而如果是delphi不同版本开发的DLL互调时,一些控件内部的定义有所区别,因为无法(至少目前我觉得理论上不可行) ...
- csharp通过dll调用opencv函数,图片作为参数
[blog 项目实战派]csharp通过dll调用opencv函数,图片作为参数 一直想做着方面的研究,但是因为这个方面的知识过于小众,也是由于自己找资料的能力比较弱,知道今天才找 ...
- 用delphi的THTTPRIO控件调用了c#写的webservice。
用delphi的THTTPRIO控件调用了c#写的webservice. 下面是我调试时遇到的一些问题: 1,导入wsdl文件:file--new----other----wenservice---W ...
- [转]Delphi 中动态链接库(dll)的建立和使用
动态链接库是一个能够被应用程序和其它的DLL调用的过程和函数的集合体,它里面包含的是公共代码或资源.由于DLL代码使用了内存共享技术,在某些地方windows也给了DLL一些更高的权限,因而DLL中可 ...
- Delphi 的接口机制——接口操作的编译器实现过程(2)
接口对象的内存空间 假设我们定义了如下两个接口 IIntfA 和 IIntfB,其中 ProcA 和 ProcB 将实现为静态方法,而 VirtA 和 VirtB 将以虚方法实现: IIntfA = ...
- Delphi 封装Frame到Dll文件
做项目的时候,发现这个Frame很好用,为了省空间.调用和修改方便,就将Frame封装到dll(动态链接库)里面,确实很好使. 效果图如下: 上图是临时测试用的,忘了将Frame的align设置成al ...
- 使用接口的方式调用远程服务 ------ 利用动态调用服务,实现.net下类似Dubbo的玩法。
分布式微服务现在成为了很多公司架构首先项,据我了解,很多java公司架构都是 Maven+Dubbo+Zookeeper基础上扩展的. Dubbo是Alibaba开源的分布式服务框架,它最大的特点是按 ...
- Delphi 的接口机制——接口操作的编译器实现过程(1)
学习COM编程技术也快有半个月了,这期间看了很多资料和别人的程序源码,也尝试了用delphi.C++.C#编写COM程序,个人感觉Delphi是最好上手的.C++的模版生成的代码太过复杂繁琐,大量使用 ...
随机推荐
- GIT团队合作探讨之二--Pull Request
pull request是github/bitbucket给开发人员实现便利合作提供的一个feature.他们提供一个用户友好的web界面在进代码之前来讨论这些变更. 简单说,pull request ...
- application/x-www-form-urlencode 和 multiple/form-data
一.概述 在学习ajax的时候,如果用post请求,需要设置如下代码. ajax.setRequestHeader("content-type","application ...
- selenium+python 数据驱动-txt篇
#循环读取txt文件中的数据,可以作为用户名,密码等使用from selenium import webdriver #创建两个列表user=[]pwd=[]f=open(r'C:\bbs\data\ ...
- 「C语言」数据类型及混合运算与类型转换
深入学习C语言时,有必要先了解一下数据类型的概念,以及它们之间的混合运算与类型转换. 本篇文章便是根据<C语言程序设计教程>和在线翻阅资料后整理而出.(练习题将逐步更新) 目录: ...
- UVa 12169 - Disgruntled Judge(拓展欧几里德)
链接: https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem& ...
- 【转】Android单帧动画Rotate旋转
项目有一个需求,有一个刷新按钮,上面放着一个常见的静止的刷新圆圈,如下图: 一旦用户按了刷新按钮,需要让这个刷新圆圈转动起来,让用户感觉到程序还在运行着,而不是卡死了. 有两个思路,一是将这个图按照旋 ...
- C# Path类 FileStream(文件流) 与 File(文件) 读取的区别
1.采用文件流读取数据是一点一点从文件中读取数据对内存的压力相对较小;而采用文件读取数据是一下全部读取过来对内存造成的压力相对较大 2.File读取: string str = @"E:\Q ...
- nodejs实战的github地址,喜欢的你还等啥
第一章.第二章:使用Express + MongoDB搭建多人博客:https://github.com/nswbmw/N-blog 第三章:使用Redis搭建漂流瓶服务器:https://githu ...
- Spring MVC 框架
一.SpringMVC基础入门,创建一个HelloWorld程序 1.首先,导入SpringMVC需要的jar包. 2.添加Web.xml配置文件中关于SpringMVC的配置 <!--conf ...
- Address already in use
1.错误描述 2011-7-20 11:05:18 org.apache.catalina.core.StandardServer await严重: StandardServer.await: cre ...