Delphi采用接口实现DLL调用

Posted 小宇飞刀(xieyunc)

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 := 0
  else
    Result := E_NOINTERFACE;
end;

function TIntObject._AddRef: Integer;
begin
  Result := -1;
end;

function TIntObject._Release: Integer;
begin
  Result := -1
end;

3.自管理接口对象在Delphi调用注意事项

1)接口赋值

  错误代码:(Delphi编译器产生代码会先判断接口指针是否为nil,如果非nil自动调用接口的_Release方法)

  var P1:IHttpServer
。。。。。。。。。。。。
    P1:=FServer.Param;
    P1.SetValue(PWideChar(aName), PAnsiChar(AnsiString(ServerAddr)), ServerPort, 10000, 10,0, 40000);

  

  建议代码:

  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, 10000, 10,0, 40000);
  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, 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;


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调用C++的DLL中char*参数的问题

delphi编写dll遇到的问题

关于delphi中动态调用dll时出现的问题

Delphi 调用DLL TStream作为参数

请帮忙解答一下,在delphi下编写dll文件遇到的错误

delphi如何调用dll函数