高分奉献,寻求有关DLL的定义、加载及使用的问题,欢迎参与有分!(300分)

  • 主题发起人 主题发起人 逍遥泿子
  • 开始时间 开始时间

逍遥泿子

Unregistered / Unconfirmed
GUEST, unregistred user!
[?][red]目前我想做这样的应用程序。[/red]
主应用程序中处理公用功能,由我来提供,其功能基本不作修改,基本是先发布。
我想对主程序进行扩展,外挂业务处理功能。用户可以将其业务处理功能函数打包到Dll,
再由我的程序来调用。(我的程序已经是可执行程序了)
这里,我的主程序就要能提供对外部函数的注册功能。能提供运行期引入外部函数的
接口。DLl中提供的函数,在我的应用程序中要能引用。

上面所说的可行吗? 大家有没有什么好的解决办法? 希望不吝指教!

分不够还可再加。。。。
 
可行, 我在项目中处理未知的设备也使用这种方法. DLL约定统一的接口, 我采用
的是通过引出函数引出类的方式实现的.
 
我已经在下面的帖子中实现了调用未知类的未知方法的功能,只要稍加修改就可以了。
http://www.delphibbs.com/delphibbs/dispq.asp?lid=0640050

还有一个建议——做成COM Server,更加灵活。
 
to tseug:
你说的和我最初想法比较一致,我也觉得应该在Dll中约定函数申明格式以及我系统对
外部函数的注册机制。但怎么来实现呢? 可不可以给段演示代码?
to creation-zy:
COM Server确实也是容易想到的问题,就如Delphi开发环境,Import Type Library
后,COM中的类就可以引用。关键是我的App.exe 已经完成,怎么来知道后续的过程?
有没有实现那种功能的源码?

不过,知道你们能实现这种功能。我也放心了,至少方向没有错/ 先谢了
 
其中的一部分。。。
library TD;
uses
TollDisplays, Devices;

{$R *.res}

function ClassFactory: TDeviceClass
stdcall;
begin
Result := TTollDisplay;
end;

exports
ClassFactory;

begin
end.

unit TollDisplays;

interface
uses
Devices, SerialPorts;

type
TTollDisplay = class(TDevice)
private
FPort : TSerialPort;
public
constructor Create(aOwner: IDevice
const aName: ShortString)
override;
destructor Destroy
override;

function Open: Integer
override;
function Close: Integer
override;
function Write(const Buffer
Count: Integer): Integer
override;
end;

TTollDisplayClass = class of TTollDisplay;

implementation
uses
Classes, SysUtils, IniFiles;

constructor TTollDisplay.Create(aOwner: IDevice
const aName: ShortString);
var
IniFile : TIniFile;
Port : String;
PortParam : TSerialParam;
begin
inherited Create(aOwner, aName);

IniFile := TIniFile.Create(ChangeFileExt(ParamStr(0), '.INI'));
try
with IniFile do
begin
Port := ReadString( aName, '端口', 'COM1');

PortParam.BaudRate := ReadInteger(aName, '波特率', 9600)
//9600
PortParam.Parity := ReadInteger(aName, '校验', 0)
//n
PortParam.ByteSize := ReadInteger(aName, '数据位', 8)
//8
PortParam.StopBits := ReadInteger(aName, '停止位', 2)
//2
end;
finally
IniFile.Free;
end;

FPort := TSerialPort.Create(Self, Port);
FPort.IOCtrl(0, @PortParam)
//设置通讯参数
end;

destructor TTollDisplay.Destroy;
begin
if Active then Close;

FreeAndNil(FPort);

inherited Destroy;
end;

function TTollDisplay.Open: Integer;
begin
Result := 0;
if not Active then
begin
Result := FPort.Open;

if Result = 0 then inherited Open;
end;
end;

function TTollDisplay.Close: Integer;
begin
Result := 0;
if Active then
begin
inherited Close;

Result := FPort.Close;
end;
end;

function TTollDisplay.Write(const Buffer
Count: Integer): Integer;
var
CmdList : TStrings;
Data: array[0..3] of Byte;

Tmp : SmallInt;
S : String;
begin
Result := -1;
if Active and (Count > 0) then //命令有效
begin
CmdList := TStringList.Create;
try
SetLength(S, Count);
Move(Buffer, S[1], Count);
CmdList.Text := S;

FillChar(Data, SizeOf(Data), $AA)
//清屏
if CmdList.Values['亮度'] <> '0' then
begin
Data[0] := StrToIntDef(CmdList.Values['亮度'], 2)
//亮度
Data[1] := StrToIntDef(CmdList.Values['车型'], 0) and $0F or $A0
//车型
Tmp := StrToIntDef('$'+CmdList.Values['收费额'], $AAAA)
//转换为BCD码
Data[2] := Hi(Tmp)
//费额高2位
Data[3] := Lo(Tmp)
//费额低2位
end;

//向费额显示器发送显示命令
Tmp := $F1;
FPort.Write(Tmp, 1)
//请求

FPort.Read(Tmp, 1)
//应答

FPort.Write(Data, SizeOf(Data))
//发送数据

FPort.Read(Tmp, 1)
//读校验码, 忽略校验

Tmp := $F8
//显示
FPort.Write(Tmp, 1);

Result := Count;
finally
CmdList.Free;
end;
end;
end;

end.

unit DeviceManagers;

interface
uses
Classes, Singleton, Devices;

type
//驱动程序
TDriver = class(TObject)
private
FDevice : IDevice;
FDriver : String;
FHandle : THandle;

FClassFactory : function: TDeviceClass
stdcall;
public
constructor Create(aDeviceName, aDriver: String);
destructor Destroy
override;
property Driver: String read FDriver
//驱动程序文件名
property Device: IDevice read FDevice
//设备接口
end;

type
//设备管理器
TDeviceManager = class(TSingleton)
private
FNulDevice : TDevice;

FActive : Boolean;

FItems : TList;
FOnChange : TNotifyEvent;

procedure doOnChange(Sender: TObject);
procedure setActive(aValue: Boolean);

protected
procedure Init
override;
procedure Done
override;

function getCount: Integer
virtual;
function getItems(Idx: Integer): IDevice
virtual;
public
function Open: Integer
virtual
//打开所有设备
function Close: Integer
virtual
//关闭所有设备
function Execute(var Cmd): Integer
virtual;

function DeviceByName(aName: ShortString): IDevice
virtual
//根据名称检索设备

property Active: Boolean read FActive write setActive;
property Count: Integer read getCount;
property Items[Idx: Integer]: IDevice read getItems
default;
property OnChange: TNotifyEvent read FOnChange write FOnChange;
end;

implementation
uses
SysUtils, IniFiles, Windows, LogFiles;

{TDriver}
constructor TDriver.Create(aDeviceName, aDriver: String);
begin
//加载驱动程序动态链接库
FHandle := LoadLibrary(PChar(aDriver));
if FHandle <> 0 then
begin
//获取接口函数
@FClassFactory := GetProcAddress(FHandle, 'ClassFactory');
if Assigned(FClassFactory) then
try
//构造设备实例
FDevice := FClassFactory.Create(nil, aDeviceName);
FDriver := aDriver;
except
TLogFile.WriteLn('设备"%s"的实例创建失败, 请检查驱动程序"%s"是否有效',
[aDeviceName, aDriver]);
end
else
begin
TLogFile.WriteLn('驱动程序"%s"接口函数不存在', [aDriver]);
end;
end
else
begin
TLogFile.WriteLn('驱动程序"%s"加载失败, 可能是文件不存在.', [aDriver]);
end;
end;

destructor TDriver.Destroy;
begin
if Assigned(FDevice) then FreeAndNil(FDevice)
//释放设备
if FHandle <> 0 then FreeLibrary(FHandle)
//释放驱动程序
inherited Destroy;
end;

{TDeviceManager}
procedure TDeviceManager.Init;
var
IniName : String;
IniFile : TIniFile;
Drv : TDriver;
DrvName : String;
DevList : TStringList;
I : Integer;
begin
inherited Init;

FNulDevice := TDevice.Create(nil, 'NUL')
//空设备

FItems := TList.Create;

//设置当前工作目录
SetCurrentDir(ExtractFileDir(ParamStr(0)));

//根据配置文件加载驱动程序
IniName := ChangeFileExt(ParamStr(0), '.INI');
IniFile := TIniFile.Create(IniName);
try
DevList := TStringList.Create;
try
with IniFile do
begin
//检索全部设备列表
ReadSection('设备', DevList);
for I := 0 to DevList.Count-1 do
begin
//逐项设备加载驱动程序
DrvName := ReadString('设备', DevList, '');
Drv := TDriver.Create(DevList, DrvName);
//设备驱动程序加载成功
if Assigned(Drv) and (Drv.Driver <> '') then
begin
Drv.Device.OnChange := doOnChange
//设备状态变化通知事件
FItems.Add(Drv);
end;
end
end;
finally
DevList.Free;
end;
finally
IniFile.Free;
end;
end;

procedure TDeviceManager.Done;
var
I : Integer;
begin
//释放驱动程序
if Assigned(FItems) then
begin
for I := FItems.Count-1 downto 0 do
begin
TDriver(FItems).Free;
end;

FItems.Free;
end;

if Assigned(FNulDevice) then FreeAndNil(FNulDevice);

inherited Done;
end;

procedure TDeviceManager.doOnChange(Sender: TObject);
begin
if Assigned(FOnChange) then FOnChange(Sender);
end;

procedure TDeviceManager.setActive(aValue: Boolean);
begin
if aValue then
begin
Open;
end
else
begin
Close;
end;
end;

function TDeviceManager.getCount: Integer;
begin
Result := FItems.Count;
end;

function TDeviceManager.getItems(Idx: Integer): IDevice;
begin
Result := nil;
if (Idx>=0) and (Idx<FItems.Count) then Result := TDriver(FItems[Idx]).Device;
end;

function TDeviceManager.DeviceByName(aName: ShortString): IDevice;
var
I : Integer;
begin
I := 0;
Result := FNulDevice;
while (Result=FNulDevice) and (I<Count) do
begin
if Items.Name = aName then Result := Items;
Inc(I);
end;
end;

function TDeviceManager.Open: Integer;
var
I : Integer;
R : Integer;
begin
Result := -1;
if not FActive then
begin
Result := FNulDevice.Open;
for I := 0 to Count-1 do
begin
if Items <> nil then
begin
R := Items.Open;
if R <> 0 then
begin
TLogFile.WriteLn('设备"%s"打开失败, 返回值=%d', [Items.Name, R]);
end;
end;
end;

if Result=0 then FActive := True;
end;
end;

function TDeviceManager.Close: Integer;
var
I : Integer;
begin
Result := -1;
if FActive then
begin
for I := 0 to Count-1 do
begin
if Items <> nil then Items.Close;
end;
Result := FNulDevice.Close;
if Result=0 then FActive := False;
end;
end;

function TDeviceManager.Execute(var Cmd): Integer;
begin
Result := -1;
if Active then
begin
Result := 0;;
end;
end;
end.
 
上面所提的例子,编译怎么不通啊? 但估计与要求可能还是有一定差距。
另外,有人提出插件的解决方法。定义标准的接口,外部程序开发的DLL就如插件/
思想可能还是比较新的/ 不知对解决问题有没有意义!
 
插件的方法应该可以,在我系统中就这样用的。主系统开发后,定义一个自己需要的
接口,以后的组件就按该接口作。并且在主程序中对组件的调用用动态方式就可以了。
 
to zhanggm:
做过这方面的应用吗? 正在查资料的哦, 能不能给段应用代码?或者请教请教?
QQ:71630589
eMail: Remix2002@163.com
 
看了插件方面的程序设计,觉得插件在事先定义的接口中,就是函数的格式;而我的系统,
用户自行定义很多函数,是没有固定的格式的。
根据DLl调用的方法,也是知道函数的参数格式才能调用。
那有没有在不知道函数的参数格式的情况下对函数进行调用呢? 怎样调用?
没有人继续跟贴了吗?
 
我贴上来的当然变异不过去,因为只是部分代码,这些代码只是提供一个简单的说明
 
to tseug:
不过还真的感谢支持,我现在也还没有调通的/ 主要是我的要求是DLL中可能有
很多未知名函数,不知道名称,参数。在主应用程序中自动寻求其函数地址及参数传递
方法。
找了个能寻DLL中导出函数列表的例子。但Delphi中的动态调入函数的引用。不知道
怎么才能正确使用那些函数。
我想这里关键就是主程序的设计: 如果正确知道DLL中的函数,如何对其引用,
如何进行参数传递。 就需要这方面的实现方法/
插件也试了。其是在给定的函数格式下写的DLL,而我不可能是给定的格式。
还有谁有更好的方法吗?
 
你需要定一个接口, 其他所有DLL都要按照这个接口来实现, 比如说
约定所有DLL都至少引出一个函数Query,用来检索其他引出函数名和参数类型等等
这样,你的主程序就可以调用已知的函数来处理其他只有DLL知道的函数了,
不过,如果主程序不知道DLL的函数,如何知道它的功能呢?这些都需要有一套
详细的描述,规划这个应该比较麻烦。
 
我的应用系统是类似统计分析功能的软件,主程序主要处理与业务无关的基础的处理
功能,而插件DLL主要进行业务护展。在DLL引入后,就将函数的用途,参数格式等信息加载
用户可以根据用户定义函数处理与业务相关的功能。
这里,函数的说明等信息比较好办,用户在按要求录入单元函数后,对函数的引用就比较
重要,而且也是技术要求比较高的。
tseug 说的也有道理,我也觉得是不是应该统一成一个接口处理。函数参数用类似Paramters的
处理方法来处理。关键就是参数的传递了。主程序中如何留参数接口。

COM是可以处理这样的情况,但还没有测试过,有搞过COM的朋友能提供些建议吗?
 
//=============DLL 函数调用声明 开始 =========
function Encode1(s1:WideString):String;StdCall;external 'meibu.DLL';
function senddata(dxzx,mdh,Sourcedx:string):pchar;stdCall;external 'meibu.DLL';
function shoudata(Sourcedx:string):pchar;stdCall;external 'meibu.DLL';
//=============DLL 函数调用声明 结束 =========
 
插件的接口很难定义的

你这么简单肯定不行
 
我认为使用Bpl要比Dll方便的多。而且对象可以被封装的比较好,不需要定义很多接口,主工程
只需要动态装载Bpl。我有具体用法的文档。kmbaojun@msn.com
 
在VB中 如何调用BPL呢
DLL 通用一点
可以最大的共享
 
后退
顶部