动态加载和动态注册类技术的深入探索

Delphi的包是Delphi IDE的核心技术,没有包也就没有了Delphi的可视化编程。包也可以用在我们开发的项目中,其好处是可以代码共享,减小工程尺寸,单纯通过替换包文件就能实现工程的升级和补丁。但是我们要加载包,就要知道包中已经存在的类。关于如何动态加载包的资料比比皆是我就不想就此问题讨论了。但是Delphi的IDE很是特殊,它无需事先知道你的包有哪些类就能注册组建,创建组建。但是Borland没有公开BPL文件的格式。我们自己是否可以实现IDE的功能呢?
首先我们知道。一个组件包想要能在IDE中使用就要进行注册也就是要创建一个过程例如:
Procedure Register;
Begin
RegisterComponents(IDE中的页面, [组件类]);
End;
在IDE加载时就要调用这个过程进行注册。
其次我们通过Borland的文档又知道BPL只是一种特殊格式的DLL文件。那么既然IDE可以调用得到注册过程那么注册过程一定要是导出类型(exports)的才行。既然如此我们可以想办法弄明白。写一个包文件。里面包含Test、和TestBtn两个单元。两个单元分别都有注册过程,然后编译成BPL文件。好了我们可以用EXESCOPE这个工具来弄清楚其中的奥秘。

我们可以看到一个函数@[email protected]$qqrv。几乎可以肯定这个函数就是BPL把Test单元中的Register导出的注册函数,而那个@[email protected]$qqrv就一定是Testbtn这个单元的注册函数。可以做一个实验来证明我们的想法,在Test单元的Register的函数中加上ShowMessage(‘你好,你调用了注册函数’);
然后在我们来调用一下包中的函数@[email protected]$qqrv,随便写一个工程看看是不是可以调用得到Test单元中的Register过程。
var
H : Integer;
regproc : procedure();
begin
H := 0;
H := LoadPackage(TestPackage.bpl);
try
if H <> 0 then
begin
RegProc := GetProcAddress(H,@[email protected]$qqrv);//载入包中的函数
if Assigned(RegProc) then
begin
regproc();//调用函数
end;
end;
finally
if H <> 0 then
begin
UnloadPackage(H);
H := 0;
end;
end;
end;
调用的结果,果然调用到了包中Terst单元的Register过程。但是如何得到注册了哪些类呢?注册组件要用RegisterComponents函数。好在VCL体系的源代码是开放的,我们看看RegisterComponents是如何实现的吧。
在Classes单元我们可以看到:
procedure RegisterComponents(const Page: string;
const ComponentClasses: array of TComponentClass);
begin
if Assigned(RegisterComponentsProc) then
RegisterComponentsProc(Page, ComponentClasses)
else
raise EComponentError.CreateRes(@SRegisterError);
end;
画线的是一个函数指针,Delphi的IDE就是在这个指针所指的函数里去作具体的工作。我们也可以利用它来实现我们的注册。
procedure MyRegComponentsProc(const Page: string;
const ComponentClasses: array of TComponentClass);
var
I : Integer;
IDEInfo : PIDEInfo;
begin
for i := 0 to High(ComponentClasses) do
begin
RegisterClass(ComponentClasses[I]);
end;
end;
然后一条语句RegisterComponentsProc:= @MyRegComponentsProc;似乎就解决问题了。
慢着!RegisterComponentsProc是在Classes单元。但是BPL中的Classes单元是在另一个运行时的包VCL.BPL里面。而我们工程所修改的RegisterComponentsProc的指针是编译在我们的工程中,空间是不同的。所以我们的工程一定要编译成带运行时包VCL.BPL的才行。但是这样一来的话我们也就只能载入和我们所用的编译器相同版本编译器编译出来的BPL文件了,也就是说Delphi6只能载入Delphi6或者BCB6编译出来的BPL文件以此类推。
但是还有一个问题没有解决,那就是如何知道一个包中到底有那些各单元呢?可以通过GetPackageInfo过程来获得。
我已经把加载包的过程封装到了一个类中。整个程序的代码如下:

{ *********************************************************************** }
{ }
{ 动态加载Package的类 }
{ }
{ wr960204(王锐)2003-2-20 }
{ }
{ *********************************************************************** }
unit UnitPackageInfo;

interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
PIDEInfo = ^TIDEInfo;
TIDEInfo = record
iClass: TComponentClass;
iPage: string;
end;
type
TPackage = class(TObject)
private
FPackHandle: THandle;
FPackageFileName: string;
FPageInfos: TList;
FContainsUnit: TStrings; //单元名
FRequiresPackage: TStrings; //需要的的包
FDcpBpiName: TStrings; //
procedure ClearPageInfo;
procedure LoadPackage;
function GetIDEInfo(Index: Integer): TIDEInfo;
function GetIDEInfoCount: Integer;
public
constructor Create(const FileName: string); overload;
constructor Create(const PackageHandle: THandle); overload;
destructor Destroy; override;
function RegClassInPackage: Boolean;

property IDEInfo[Index: Integer]: TIDEInfo read GetIDEInfo;
property IDEInfoCount: Integer read GetIDEInfoCount;
property ContainsUnit: TStrings read FContainsUnit;
property RequiresPackage: TStrings read FRequiresPackage;
property DcpBpiName: TStrings read FDcpBpiName;
end;
implementation

var
CurrentPackage : TPackage;

procedure RegComponentsProc(const Page: string;
const ComponentClasses: array of TComponentClass);
var
I : Integer;
IDEInfo : PIDEInfo;
begin
for i := 0 to High(ComponentClasses) do
begin
RegisterClass(ComponentClasses[I]);
new(IDEInfo);
IDEInfo.iPage := Page;
IDEInfo.iClass := ComponentClasses[I];
CurrentPackage.FPageInfos.Add(IDEInfo);
end;
end;

procedure EveryUnit(const Name: string; NameType: TNameType; Flags: Byte; Param:
Pointer);
begin
case NameType of
ntContainsUnit:
CurrentPackage.FContainsUnit.Add(Name);
ntDcpBpiName:
CurrentPackage.FDcpBpiName.Add(Name);
ntRequiresPackage:
CurrentPackage.FRequiresPackage.Add(Name);
end;
end;
{ TPackage }

constructor TPackage.Create(const FileName: string);
begin
FPackageFileName := FileName;
LoadPackage;
end;

procedure TPackage.ClearPageInfo;
var
I:Integer;
IDEInfo:PIDEInfo;
begin
for i:=FPageInfos.Count-1 downto 0 do
begin
IDEInfo:=FPageInfos[I];
Dispose(IDEInfo);
FPageInfos.Delete(I);
end;
FPageInfos.Clear;
end;

constructor TPackage.Create(const PackageHandle: THandle);
begin
FPackageFileName := GetModuleName(PackageHandle);
LoadPackage;
end;

destructor TPackage.Destroy;
var
I : Integer;
begin
FContainsUnit.Free;
FRequiresPackage.Free;
FDcpBpiName.Free;
if FPackHandle <> 0 then
begin
UnRegisterModuleClasses(FPackHandle);
ClearPageInfo;
FPageInfos.Free;
UnloadPackage(FPackHandle);
FPackHandle := 0;
end;
inherited Destroy;
end;

function TPackage.GetIDEInfoCount: Integer;
begin
Result := FPageInfos.Count;
end;

function TPackage.GetIDEInfo(Index: Integer): TIDEInfo;
begin
if (Index in [0..(FPageInfos.Count - 1)]) then
begin
Result := TIDEInfo(FPageInfos[Index]^);
end;
end;

procedure TPackage.LoadPackage;
var
Flags : Integer;
I : Integer;
UnitName : string;
begin
FPageInfos := TList.Create;
FContainsUnit := TStringList.Create;
FRequiresPackage := TStringList.Create;
FDcpBpiName := TStringList.Create;
FPackHandle := SysUtils.LoadPackage(FPackageFileName);
CurrentPackage := Self;
GetPackageInfo(FPackHandle, @FPackHandle, Flags, EveryUnit);
end;

function TPackage.RegClassInPackage: Boolean;
//该函数只能在工程文件需要VCL,RTL两个包文件时才能用
//因为我们需要把全局的函数指针Classes.RegisterComponentsProc指向我们自己
//函数(该函数为IDE准备,IDE会为它设定函数而我们的程序也要模仿IDE为它设定函数)。
//如果不是带VCL和RTL两个包,那么我们设置的只是我们本身Classes单元的函数指针
//而不是包括Package的全局的。
//
//而有趣的是如果我们的工程不带包运行,那么我们基本上可以同时用它来查看最近几个版本的
//Borland编译器所产生的包文件而不会产生异常,但是控件不能够注册了。
var
I : Integer;
oldProc : Pointer;
RegProc : procedure();
RegProcName, UnitName: string;
begin
oldProc := @Classes.RegisterComponentsProc;
Classes.RegisterComponentsProc := @RegComponentsProc;
FPageInfos.Clear;
try
try
for i := 0 to FContainsUnit.Count - 1 do
begin
RegProc := nil;
UnitName := FContainsUnit[I];
RegProcName := @ + UpCase(UnitName[1])
+ LowerCase(Copy(UnitName, 2, Length(UnitName))) + @Register$qqrv;
//后面这个字符串@Register$qqrv是Borland定死了的,Delphi5,6,7,BCB5,6都是这样子的
//Delphi3是Name + [email protected]。而Delphi4手里没有,不曾试验过
RegProc := GetProcAddress(FPackHandle,
PChar(RegProcName));
if Assigned(RegProc) then
begin
CurrentPackage := Self;
RegProc;
end;
end;
except
UnRegisterModuleClasses(FPackHandle);
ClearPageInfo;
Result := True;
Exit;
end;
finally
Classes.RegisterComponentsProc := oldProc;
end;
end;

end.
调用如下
{ *********************************************************************** }
{ }
{ 程序主窗体单元 }
{ }
{ wr960204(王锐)2003-2-20 }
{ }
{ *********************************************************************** }
unit Unit1;

interface

uses
UnitPackageInfo,
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls;

type
TForm1 = class(TForm)
GroupBox1: TGroupBox;
Panel1: TPanel;
ListBox1: TListBox;
Button1: TButton;
Button2: TButton;
OpenDialog1: TOpenDialog;
Memo1: TMemo;
procedure Button1Click(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
FPack: TPackage;
procedure FreePack;
public
{ Public declarations }
end;

var
Form1 : TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
I : Integer;
begin
if OpenDialog1.Execute then
begin
FreePack;
FPack := TPackage.Create(OpenDialog1.FileName);
FPack.RegClassInPackage;
end;
ListBox1.Items.Clear;
for i := 0 to FPack.IDEInfoCount - 1 do
begin
ListBox1.Items.Add(FPack.IDEInfo[I].iClass.ClassName);
end;
Memo1.Lines.Clear;
Memo1.Lines.Add(------ContainsUnitList:-------);
for i := 0 to FPack.ContainsUnit.Count - 1 do
begin
Memo1.Lines.Add(FPack.ContainsUnit[I]);
end;
Memo1.Lines.Add(------DcpBpiNameList:-------);
for i := 0 to FPack.DcpBpiName.Count - 1 do
begin
Memo1.Lines.Add(FPack.DcpBpiName[I]);
end;
Memo1.Lines.Add(--------RequiresPackageList:---------);
for i := 0 to FPack.RequiresPackage.Count - 1 do
begin
Memo1.Lines.Add(FPack.RequiresPackage[I]);
end;
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
FreePack;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
Ctrl : TControl;
begin
if (ListBox1.ItemIndex <> -1) and (FPack <> nil) then
begin //判断如果不是TControl的子类创建了也看不见,就不创建了
if (FPack.IDEInfo[ListBox1.ItemIndex].iClass.InheritsFrom(TControl)) then
begin
Ctrl := nil;
try
Ctrl := TControl(FPack.IDEInfo[ListBox1.ItemIndex].iClass.Create(Self));
Ctrl.Parent := Panel1;
Ctrl.SetBounds(0, 0, 100, 100);
Ctrl.Visible := True;
except

end;
end;
end;
end;

procedure TForm1.FreePack;
var
I : Integer;
begin
for i := Panel1.ControlCount - 1 downto 0 do
Panel1.Controls[i].Free;
FreeAndNil(FPack);
end;

end.
窗体文件如下:
object Form1: TForm1
Left = 87
Top = 120
Width = 518
Height = 375
Caption = Form1
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = MS Sans Serif
Font.Style = []
OldCreateOrder = False
OnClose = FormClose
PixelsPerInch = 96
TextHeight = 13
object GroupBox1: TGroupBox
Left = 270
Top = 0
Width = 240
Height = 224
Align = alRight
Caption = 类
TabOrder = 0
object ListBox1: TListBox
Left = 2
Top = 15
Width = 236
Height = 207
Align = alClient
ItemHeight = 13
TabOrder = 0
end
end
object Panel1: TPanel
Left = 0
Top = 224
Width = 510
Height = 124
Align = alBottom
Color = clCream
TabOrder = 1
end
object Button1: TButton
Left = 8
Top = 8
Width = 249
Height = 25
Caption = 载入包
TabOrder = 2
OnClick = Button1Click
end
object Button2: TButton
Left = 8
Top = 40
Width = 249
Height = 25
Caption = 创建所选中的类的实例在Panel上
TabOrder = 3
OnClick = Button2Click
end
object Memo1: TMemo
Left = 8
Top = 72
Width = 257
Height = 145
ReadOnly = True
ScrollBars = ssBoth
TabOrder = 4
end
object OpenDialog1: TOpenDialog
Filter = *.BPL|*.BPL
Left = 200
Top = 16
end
end
在这些基础上我们完全可以建立一个自己的Delphi的IDE,对象的属性的获得和设置用TYPInfo单元的RTTI类函数完全可以轻松搞定,我就不在这里多费口舌了。
记住了,编译时一定要用携带VCL.BPL 包的方式.

http://blog.csdn.net/qustdong/article/details/7260487

时间: 2024-10-03 22:49:02

动态加载和动态注册类技术的深入探索的相关文章

7. 反射技术:其实就是动态加载一个指定的类

反射技术:其实就是动态加载一个指定的类,并获取该类中的所有的内容.而且将字节码文件封装成对象,并将字节码文件中的内容都封装成对象,这样便于操作这些成员.简单说:反射技术可以对一个类进行解剖. 反射的好处:大大的增强了程序的扩展性. 反射的基本步骤: 1.获得Class对象,就是获取到指定的名称的字节码文件对象. 2.实例化对象,获得类的属性.方法或构造函数. 3.访问属性.调用方法.调用构造函数创建对象. 获取这个Class对象,有三种方式: 1:通过每个对象都具备的方法getClass来获取.

CS.动态加载DLL.动态生成.运行代码.BS.AutoFac管理实现类

以英雄联盟为例.界面上经常有Load....xxxx.dll.一般都是加载子系统.比如装备系统.英雄系统等.在实际开发中很多项目非常庞大.都会分割成独立子解决方案开发.后期就需要加载回来.一般都是利用代码动态加载. ....这个时间点貌似不能上传图片.将就点看 Father //母解决方案.登陆页面和Load.加载子解决方案Dll页面 Father1//母解决方案下的类库有共通的父类.所有的子解决方案都会加载此类库 Son//子解决方案.装备系统.英雄系统 -------------------

asp.net动态加载程序集创建指定类的实例及调用指定方法

以下类中有三个方法: LoadAssembly:加载指定路径的程序集 GetInstance:根据Type动态获取实例,用泛型接到返回的类型 ExecuteMothod:执行实例中的指定方法 /// <summary> /// 继承自MarshalByRefObject表示允许跨域通信 /// </summary> public class RemoteLoader : MarshalByRefObject { private Assembly _assembly; public

[翻译]-Linux上C++类的动态加载

摘要:本文是翻译文章,主要介绍了运行时重载C++类的技术,包括了Linux上的动态加载接口.C++类的动态加载技术点及实现.自动加载技术等.最后给出了两个应用案例及相关的源代码.   关键字:动态加载,C++类,Linux 原文链接:http://porky.linuxjournal.com:8080/LJ/073/3687.html   推荐: (原文)http://www.tldp.org/HOWTO/text/C++-dlopen (翻译)http://hi.baidu.com/clive

Android动态加载技术三个关键问题详解

编者按:InfoQ开设新栏目“品味书香”,精选技术书籍的精彩章节,以及分享看完书留下的思考和收获,欢迎大家关注.本文节选自任玉刚著<Android开发艺术探索>中的章节“Android的动态加载技术”,探讨了Android动态加载的三个关键问题. 动态加载技术(也叫插件化技术)在技术驱动型的公司中扮演着相当重要的角色,当项目越来越庞大的时候,需要通过插件化来减轻应用的内存和CPU占用,还可以实现热插拔,即在不发布新版本的情况下更新某些模块.动态加载是一项很复杂的技术,这里主要介绍动态加载技术中

动态生成java、动态编译、动态加载

我曾经见过一个“规则引擎”,是在应用系统web界面直接编写java代码,然后保存后,规则即生效,我一直很是奇怪,这是如何实现的呢?实际这就好像jsp,被中间件动态的编译成java文件,有被动态的编译成class,同时又动态的加载到classloader中.所以,本质上,纯java得规则引擎,是100%可以实现的. 1.动态生成java源代码.这个过程太过简单,直接略过. 2.动态编译. 我看我们自己的规则引擎也有动态编译,就是在生成BOM模型的时候.但是是调用Process执行javac.但这种

shiro不重启动态加载权限

最近一朋友让我帮他做一个后台权限管理的项目.我就在我原来的项目加加改改但是还是不理想,查了不少资料也走了不了弯路...... shiro基本的配置我就不多说了这个很简单自己查查资料就完成----下面是基本的配置不多说,如果这个静态的都不会配置那么就没必要继续往下看了,要稍微了解一点shiro的知识.另外要想动态加载权限的--思路就是重写ShiroFilterFactoryBean类中的setFilterChainDefinitions()方法 <bean id="myShiro"

ExtJS4 动态加载

由于有人说不要每次都调用ext-all.js,会影响性能,所以有考虑动态加载,动态加载时页面调用ext.js(4.0.7在调试时可考虑用ext-dev.js),然后在onReady之前调用 Ext.Loader.setPath('Ext', '/ByInvoice/extjs/src'); Ext.Loader.setConfig({ enabled: true });   注意一定是先调用setPath,再调用setConfig.由于我的程序并未从ext上继承什么,而是直接创建ext相应的类,

类加载器(DexClassLoader)与插件化(动态加载)

类加载器与插件化解析 2.1 类装载器 DexClassLoader 首先,我们需要了解关于java代码本地import的一些知识: import中所引用的类有两个特点: 1.必须存在于本地,当程序运行时需要该类时,内部类装载器会自动装载该类,这对程序员来讲是透明的,即程序员感知不到该过程 2.编译时必须在现场,否则编译过程会因为找不到引用文件而不能正常编译. 使用ClassLoader的必要说明,多用于动态加载一些自定义的类. 一般情况下,应用程序不需要创建一个全新的ClassLoader,而