Delphi-DLL编程

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

Delphi-DLL编程
一、开使你的第一个DLL专案
1.File->Close all->File->New﹝DLL﹞
代码:
//自动产生Code如下
library Project2;
//这有段废话
uses
SysUtils,
Classes;
{$R *.RES}
begin
end.
2.加个Func进来:
代码:
library Project2;
uses
SysUtils,
Classes;
Function MyMax ( X , Y : integer ) : integer ; stdcall ;
begin
if X > Y then
Result := X
else
Result := Y ;
end ;
//切记:Library 的名字大小写没关系,可是DLL-Func的大小写就有关系了。

// 在DLL-Func-Name写成MyMax与myMAX是不同的。

如果写错了,立即// 的结果是你叫用到此DLL的AP根本开不起
来。

//参数的大小写就没关系了。

甚至不必同名。

如原型中是(X,Y:integer)但引
// 用时写成(A,B:integer),那是没关系的。

//切记:要再加个stdcall。

书上讲,如果你是用Delphi写DLL,且希望不仅给// Delphi-AP也希望BCB/VC-AP等使用的话,那你最好加个Stdcall ; 的指示//参数型态:Delphi有很多种它自己的变量型态,这些当然不是DLL所喜欢的// ,Windows/DLL的母语应该是C。

所以如果要传进传出DLL的参数,我们// 尽可能照规矩来用。

这两者写起来,后者会麻烦不少。

如果你对C不熟
// 的话,那也没关系。

我们以后再讲。

{$R *.RES}
begin
end.
3.将这些可共享的Func送出DLL,让外界﹝就是你的Delphi-AP 啦﹞使用:光如此,你的AP还不能用到这些,你还要加个Exports才行。

代码:
{$R *.RES}
exports
MyMax ;
begin
end.
4.好了,可以按Ctrl-F9编译了。

此时可不要按F9。

DLL不是EXE┌不可单独执行的,如果你按F9,会有ErrorMsg的。

这时如果DLL有Error,请修正之。

再按Ctrl-F9。

此时可能有Warning,不要紧,研究一下,看看就好。

再按Ctrl-F9,此时就『Done , Compiled 』。

同目录就会有个*.dll 。

恭喜,大功告成了。

二、进行测试:开个新application:
1.加个TButton
代码:
ShowMessage ( IntToStr(MyMax(30,50)) ) ;
2.告知Exe到那里抓个Func
代码:
//在Form,interface,var后加
Function MyMax ( X , Y : integer ) : integer ; stdcall ; external ’MyTestDLL.dll’ ;
// MyTestDLL.dll为你前时写的DLL项目名字
// DLL名字大小写没关系。

不过记得要加extension的 .DLL。

在Win95或NT,
// 是不必加extension,但这两种OS,可能越来越少了吧。

要加extension
可以了,简单吧。

上面的例子是不是很简单?熟悉Delphi的朋友可以看出以上代码和一般的Delphi程序的编写基本是相同的,只是在TestDll函数后多了一个stdcall参数并且用exports语句声明了TestDll函数。

只要编译上面的代码,就可以
在Delphi中静态调用DLL
调用一个DLL比写一个DLL要容易一些。

首先给大家介绍的是静态调用方法,稍后将介绍动态调用方法,并就两种方法做一个比较。

同样的,我们先举一个静态调用的例子。

unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics,
Controls, Forms, Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Edit1: TEdit;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
//本行以下代码为我们真正动手写的代码
function TestDll(i:integer):integer;stdcall;
external ’Delphi.dll’;
procedure TForm1.Button1Click(Sender: TObject);
begin
Edit1.Text:=IntToStr(TestDll(1));
end;
end.
上面的例子中我们在窗体上放置了一个编辑框(Edit)和一个按钮(Button),并且书写了很少的代码来测试我们刚刚编写的Delphi.dll。

大家可以看到我们唯一做的工作是将TestDll函数的说明部分放在了implementation中,并且用external语句指定了Delphi.dll的位置。

(本例中调用程序和Delphi.dll 在同一个目录中。

)让人兴奋的是,我们自己编写的TestDll函数很快被Delphi认出来了。

您可做这样一个实验:输入“TestDll(”,很快Delphi就会用fly-by 提示条提示您应该输入的参数是什么,就像我们使用Delphi中定义的其他函数一样简单。

注意事项有以下一些:
一、调用参数用stdcall
和前面提到的一样,当引用DLL中的函数和过程时也要使用stdcall参数,原因和前面提到的一样。

二、用external语句指定被调用的DLL文件的路径和名称
正如大家看到的,我们在external语句中指定了所要调用的DLL 文件的名称。

没有写路径是因为该DLL文件和调用它的主程序在同一目录下。

如果该DLL文件在C:\,则我们可将上面的引用语句写为external ’C:\Delphi.dll’。

注意文件的后缀.dll必须写上。

三、不能从DLL中调用全局变量
如果我们在DLL中声明了某种全局变量,如:var s:byte 。

这样在DLL中s这个全局变量是可以正常使用的,但s不能被调用程序使用,既s不能作为全局变量传递给调用程序。

不过在调用程序中声明的变量可以作为参数传递给DLL。

四、被调用的DLL必须存在
这一点很重要,使用静态调用方法时要求所调用的DLL文件以及要调用的函数或过程等等必须存在。

如果不存在或指定的路径和文件名不正确的话,运行主程序时系统会提示“启动程序时出错”或“找不到*.dll 文件”等运行错误。

在Delphi中动态调用DLL
动态调用DLL相对复杂很多,但非常灵活。

为了全面的说明该问题,这次我们举一个调用由C++编写的DLL的例子。

首先在C++中编译下面的DLL源程序。

#include
extern ”C” _declspec(dllexport)
int WINAPI TestC(int i)
{
return i;
}
编译后生成一个DLL文件,在这里我们称该文件为Cpp.dll,该DLL中只有一个返回整数类型的函数TestC。

为了方便说明,我们仍然引用上面的调用程序,只是将原来的Button1Click过程中的语句用下面的代码替换掉了。

procedure TForm1.Button1Click(Sender: TObject);
type
TIntFunc=function(i:integer):integer;stdcall;
var
Th:Thandle;
Tf:TIntFunc;
Tp:TFarProc;
begin
Th:=LoadLibrary(’Cpp.dll’); {装载DLL}
if Th>0 then
try
Tp:=GetProcAddress(Th,PChar(’TestC’));
if Tp<>nil
then begin
Tf:=TIntFunc(Tp);
Edit1.Text:=IntToStr(Tf(1)); {调用TestC函数}
end
else
ShowMessage(’T estC函数没有找到’);
finally
FreeLibrary(Th); {释放DLL}
end
else
ShowMessage(’Cpp.dll没有找到’);
end;
大家已经看到了,这种动态调用技术很复杂,但只要修改参数,如修改LoadLibrary(’Cpp.dll’)中的DLL名称为’Delphi.dll’就可动态更改所调用的DLL。

一、定义所要调用的函数或过程的类型
在上面的代码中我们定义了一个TIntFunc类型,这是对应我们将要调用的函数TestC的。

在其他调用情况下也要做同样的定义工作。

并且也要加上stdcall调用参数。

二、释放所调用的DLL
我们用LoadLibrary动态的调用了一个DLL,但要记住必须在使用完后手动地用FreeLibrary将该DLL释放掉,否则该DLL将一直占用内存直到您退出Windows或关机为止。

现在我们来评价一下两种调用DLL的方法的优缺点。

静态方法实现简单,易于掌握并且一般来说稍微快一点,也更加安全可靠一些;但是静态方法不能灵活地在运行时装卸所需的DLL,而是在主程序开始运行时就装载指定的DLL直到程序结束时才释放该DLL,另外只有基于编译器和链接器的系统(如Delphi)才可以使用该方法。

动态方法较好地解决了静态方法中存在的不足,可以方便地访问DLL中的函数和过程,甚至一些老版本DLL中新添加的函数或过程;但动态方法难以完全掌握,使用时因为不同的函数或过程要定义很多很复杂的类型和调用方法。

对于初学者,笔者建议您使用静态方法,待熟练后再使用动态调用方法。

使用DLL的实用技巧
一、编写技巧
1 、为了保证DLL的正确性,可先编写成普通的应用程序的一部分,调试无误后再从主程序中分离出来,编译成DLL。

2 、为了保证DLL的通用性,应该在自己编写的DLL中杜绝出现可视化控件的名称,如:Edit1.Text 中的Edit1名称;或者自定义非Windows定义的类型,如某种记录。

3 、为便于调试,每个函数和过程应该尽可能短小精悍,并配合具体详细的注释。

4 、应多利用try-finally来处理可能出现的错误和异常,注意这时要引用SysUtils单元。

5 、尽可能少引用单元以减小DLL的大小,特别是不要引用可视化单元,如Dialogs单元。

例如一般情况下,我们可以不引用Classes 单元,这样可使编译后的DLL减小大约16Kb。

二、调用技巧
1 、在用静态方法时,可以给被调用的函数或过程更名。

在前面提到的C++编写的DLL例子中,如果去掉extern ”C”语句,C++会编译出一些奇怪的函数名,原来的TestC函数会被命名为@TestC$s 等等可笑的怪名字,这是由于C++采用了C++ name mangling技术。

这个函数名在Delphi中是非法的,我们可以这样解决这个问题:改写引用函数为
function TestC(i:integer):integer;stdcall;
external ’Cpp.dll’;name ’@TestC$s’;
其中name的作用就是重命名。

2 、可把我们编写的DLL放到Windows目录下或者Windows\system目录下。

这样做可以在external语句中或LoadLibrary语句中不写路径而只写DLL的名称。

但这样做有些不妥,这两个目录下
有大量重要的系统DLL,如果您编的DLL与它们重名的话其后果简直不堪设想,况且您的编程技术还不至于达到将自己编写的DLL放到系统目录中的地步吧!
三、调试技巧
1 、我们知道DLL在编写时是不能运行和单步调试的。

有一个办法可以,那就是在Run|parameters 菜单中设置一个宿主程序。

在Local页的Host Application栏中添上宿主程序的名字就可进行单步调试、断点观察和运行了。

2 、添加DLL的版本信息。

开场白中提到了版本信息对于DLL是很重要的,如果包含了版本信息,DLL的大小会增加2Kb。

增加这么一点空间是值得的。

很不幸我们如果直接使用Project|options菜单中Version选项是不行的,这一点Delphi的帮助文件中没有提到,经笔者研究发现,只要加一行代码就可以了。

如下例:
library Delphi;
uses
SysUtils,
Classes;
{$R *.RES}
//注意,上面这行代码必须加在这个位置
function TestDll(i:integer):integer;stdcall;
begin
Result:=i;
end;
exports
TestDll;
begin
end.
3 、为了避免与别的DLL重名,在给自己编写的DLL起名字的时候最好采用字符数字和下划线混合的方式。

如:jl_try16.dll。

4 、如果您原来在Delphi 1或Delphi 2中已经编译了某些DLL的话,您原来编译的DLL是16位的。

只要将源代码在新的Delphi 3或Delphi 4环境下重新编译,就可以得到32位的DLL了。

[后记]:除了上面介绍的DLL最常用的使用方法外,DLL还可以用于做资源的载体。

例如,在Windows 中更改图标就是使用的DLL中的资源。

另外,熟练掌握了DLL的设计技术,对使用更为高级的OLE、COM 以及ActiveX编程都有很多益处。

/doc/c09009345.html,/keylife/iblog_s how.asp?xid=30816
DLL比较复杂时,可以为它的声明专门创建一个引入单元,这会使该DLL变得更加容易维护和查看。

引入单元的格式如下:unit MyDllImport; {Import unit for MyDll.dll }
interface
procedure MyDllProc;

implementation
procedure MyDllProc;external 'MyDll' index 1;

end.
这样以后想要使用MyDll中的例程时,只要简单的在程序模块中的uses子句中加上MyDllImport即可。

其实这仅仅是种方便开发的技巧,大家打开Windows等引入windows API的单元,可以看到类似的做法。

动态(显式)调用DLL
前面讲述静态调用DLL时提到,DLL会在启动调用程序时即被调入。

所以这样的做法只能起到公用DLL以及减小运行文件大小的作用,而且DLL装载出错会立刻导致整个启动过程终止,哪怕该DLL在运行中只起到微不足道的作用。

使用动态调用DLL的方式,仅在调用外部例程时才将DLL装载内存(引用记数为0时自动将该DLL从内存中清除),从而节约了内存空间。

而且可以判断装载是否正确以避免调用程序崩溃的情况,最多损失该例程功能而已。

动态调用虽然有上述优点,但是对于频繁使用的例程,因DLL的调入和释放会有额外的性能损耗,所以这样的例程则适合使用静态引入。

调用范例
DLL动态调用的原理是首先声明一个函数/过程类型并创建一个指针变量。

为了保证该指针与外部例程指针一致以确保赋值正确,函数/过程的声明必须和外部例程的原始声明兼容(兼容的意思是1、参数名称可以不一样;2、参数/返回值类型至少保持可以相互赋值,比如原始类型声明为Word,新的声明可以为Integer,假如传递的实参总是在Word的范围内,就不会出错)。

接下来通过windows API函数LoadLibrary引入指定的库文件,LoadLibrary的参数是DLL文件名,返回一个THandle。

如果该步骤成功,再通过另一个API函数GetProcAddress获得例程的入口地址,参数分别为LoadLibrary的指针和例程名,最终返回例程的入口指针。

将该指针赋值给我们预先定义好的函数/过程指针,然后就可以使用这个函数/过程了。

记住最后还要使用API函数FreeLibrary来减少DLL
引用记数,以保证DLL使用结束后可以清除出内存。

这三个API函数的Delphi声明如下:
Function LoadLibrary(LibFileName:PChar):THandle;
Function
GetProcAddress(Module:THandle;ProcName:PChar):TfarProc;
Procedure FreeLibrary(LibModule:THandle);
将前面静态调用DLL例程的代码更改为动态调用,如下所示:
type
TDllProc = function (PathName : Pchar):boolean;stdcall;
var
LibHandle: THandle;
DelPath : TDllProc;
begin
LibHandle := LoadLibrary(PChar('FileOperate.dll'));
if LibHandle >= 32 then begin
try
DelPath := GetProcAddress(LibHandle,PChar('DeleteDir'));
if DirectoryExists(ShellTreeView.Path) then
if Application.MessageBox(Pchar('确定删除目录'+QuotedStr(ShellTreeView.Path)+'吗?'),
'Information',MB_YESNO) = IDYes then
if DelPath(PChar(ShellTreeView.Path)) then
showmessage('删除成功');
finally
FreeLibrary(LibHandle);
end;
end;
end;
16位DLL的动态调入
下面将演示一个16位DLL例程调用的例子,该例程是
windows9x中的一个隐藏API函数。

代码混合了静态、动态调用两种方式,除了进一步熟悉外,还可以看到调用16位DLL的解决方法。

先解释一下问题所在:
我要实现的功能是获得win9x的“系统资源”。

在winNT/2000下是没有“系统资源”这个概念的,因为winNT/2000中堆栈和句柄不再象win9X那样被限制在64K大小。

为了取该值,可以使用win9x的user dll 中一个隐藏的API函数GetFreeSystemResources。

该DLL例程必须动态引入。

如果静态声明的话,在win2000里执行就会立即出错。

这个兼容性不解决是不行的。

所以必须先判断系统版本,如果是win9x再动态加载。

检查操作系统版本的代码是:var
OSversion : _OSVERSIONINFOA;
FWinVerIs9x: Boolean;
begin
OSversion.dwOSVersionInfoSize := sizeof(_OSVERSIONINFOA);
GetVersionEx(OSversion);
FWinVerIs9x := OSversion.dwPlatformId = VER_PLATFORM_WIN32_WINDOWS;
End;
以上直接调用API函数,已在Windows单元中被声明。

function LoadLibrary16(LibraryName: PChar): THandle; stdcall; external kernel32 index 35; procedure FreeLibrary16(HInstance: THandle); stdcall; external kernel32 index 36;
function GetProcAddress16(Hinstance: THandle; ProcName: PChar): Pointer; stdcall; external kernel32 index 37;
function
TWinResMonitor.GetFreeSystemResources(SysResource: Word): Word;
type
TGetFreeSysRes = function (value : integer):integer;stdcall;
TQtThunk = procedure();cdecl;
var
ProcHandle : THandle;
GetFreeSysRes : TGetFreeSysRes;
ProcThunkH : THandle;
QtThunk : TQtThunk;
ThunkTrash: array[0..$20] of Word;
begin
Result := 0;
ThunkTrash[0] := ProcHandle;
if FWinVerIs9x then begin
ProcHandle := LoadLibrary16('user.exe');
if ProcHandle >= 32 then begin
GetFreeSysRes := GetProcAddress16(ProcHandle,Pchar('GetFreeSystemResources') );
if assigned(GetFreeSysRes) then begin
ProcThunkH := LoadLibrary(Pchar('kernel32.dll'));
if ProcThunkH >= 32 then begin
QtThunk := GetProcAddress(ProcThunkH,Pchar('QT_Thunk'));
if assigned(QtThunk) then
asm
push SysResource //push arguments
mov edx, GetFreeSysRes //load 16-bit procedure pointer
call QtThunk //call thunk
mov Result, ax //save the result
end;
end;
FreeLibrary(ProcThunkH);
end;
end;
FreeLibrary16(ProcHandle);
end
else Result := 100;
end;
首先,LoadLibrary16等三个API是静态声明的(也可以动态声明,我这么做是为了减少代码)。

由于LoadLibrary无法正常调入16位的例程(微软啊!),所以改用LoadLibrary16、FreeLibrary16、GetProcAddress16,它们与LoadLibrary、FreeLibrary、GetProcAddress的意义、用法、参数都一致,唯一不同的是必须用它们才能正确加载16位的例程。

在定义部分声明了函数指针TGetFreeSysRes 和TQtThunk。

Stdcall、cdecl参数定义堆栈的行为,必须根据原函数定义,不能更改。

假如类似一般的例程调用方式,跟踪到这一步:if assigned(GetFreeSysRes) then begin GetFreeSysRes已经正确加载并且有了函数地址,却无法正常使用GetFreeSysRes(int) 所以这里动态加载(理由也是在win2k下无法执行)了一个看似多余的过程QT_Thunk。

对于一个32位的外部例程,是不需要QT_Thunk的,但是,对于一个16位的例程,就必须使用如上汇编代码(不清楚的朋友请参考Delphi语法资料)
asm
push SysResource
mov edx, GetFreeSysRes
call QtThunk
mov Result, ax
end;
它的作用是将压入参数压入堆栈,找到GetFreeSysRes的地址,用QtThunk来转换16位地址到32位,最后才能正确的执行并返回
值!
以上16位DLL的部分在小仓系列中曾经提到过
Delphi开发DLL常见问题
字符串参数
前面曾提到过,为了保证DLL参数/返回值传递的正确性,尤其是为C++等其他语言开发的宿主程序使用时,应尽量使用指针或基本类型,因为其他语言与Delphi的变量存储分配方法可能是不一样的。

C++中字符才是基本类型,串则是字符型的线形链表。

所以最好将string强制转换为Pchar。

如果DLL和宿主程序都用Delphi开发,且使用string(还有动态数组,它们的数据结构类似)作为导出例程的参数/返回值,那么添加ShareMem为工程文件uses语句的第一个引用单元。

ShareMem是Borland 共享的内存管理器Borlndmm.dll的接口单元。

引用该单元的DLL的发布需要包括Borlndmm.dll,否则就得避免使用string。

初始化COM库
如果在DLL中使用了TADOConnection之类的COM组件,或者ActiveX控件,调用时会提示“标记没有引用存储”等错误,这是因为没有初始化COM。

DLL中不会调用CoInitilizeEx,初始化COM库被认为是应用程序的责任,这是Borland的实现策略。

你需要做的是1、引用Activex单元,保证CoInitilizeEx函数被正确调用了
2、在单元级加入初始化和退出代码:
initialization
Coinitialize(nil);
finalization
CoUninitialize;
end.
3、在结束时记住将连接和数据集关闭,否则也会报地址错误。

在DLL中建立及显示窗体
凡是基于窗体的Delphi应用程序都自动包含了一个全局对象
Application,这点大家是很熟悉的。

值得注意的是Delphi创建的DLL 同样有一个独立的Application。

所以若是在DLL中创建的窗体要成为应用程序的模式窗体的话,就必须将该Application替换为应用程序的,否则结果难以预料(该窗体创建后,对它的操作比如最小化将不会隶属于任何主窗体)。

在DLL中要避免使用ShowMessage而用MessageBox。

创建DLL中的模式窗体比较简单,把Application.Handle属性作为参数传递给DLL例程,将该句柄赋与Dll的Application.Handle,然后再用Application创建窗体就可以了。

无模式窗体则要复杂一些,除了创建显示窗体例程,还必须有一个对应的释放窗体例程。

对于无模式窗体需要十分小心,创建和释放例程的调用都需在调用程序中得到控制。

这有两层意思:一要防止同一个窗体实例的多次创建;二由应用程序创建一个无模式窗体必须保证由应用程序释放,否则假如DLL中有另一处代码先行释放,再调用释放例程将会失败。

下面是DLL窗体的代码:
unit uSampleForm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls, StdCtrls;
type
TSampleForm = class(TForm)
Panel: TPanel;
end;
procedure CreateAndShowModalForm(AppHandle : THandle;Caption : PChar);export;stdcall;
function CreateAndShowForm(AppHandle : THandle):LongInt;export;stdcall;
procedure CloseShowForm(AFormRef : LongInt);export;stdcall;
implementation
{$R *.dfm}
//模式窗体
procedure CreateAndShowModalForm(AppHandle : THandle;Caption : PChar);
var
Form : TSampleForm;
str : string;
begin
Application.Handle := AppHandle;
Form := TSampleForm.Create(Application);
try
str := Caption;
Form.Caption := str;
Form.ShowModal;
finally
Form.Free;
end;
end;
//非模式窗体
function CreateAndShowForm(AppHandle : THandle):LongInt;
var
Form : TSampleForm;
begin
Application.Handle := AppHandle;
Form := TSampleForm.Create(Application);
Result := LongInt(Form);
Form.Show;
end;
procedure CloseShowForm(AFormRef : LongInt);
begin
if AFormRef > 0 then
TSampleForm(AFormRef).Release;
end;
end.
DLL工程单元的引出声明:
exports
CloseShowForm,
CreateAndShowForm,
CreateAndShowModalForm;
应用程序调用声明:
procedure CreateAndShowModalForm(Handle : THandle;Caption : PChar);stdcall;external 'FileOperate.dll';
function CreateAndShowForm(AppHandle : THandle):LongInt;stdcall;external 'FileOperate.dll'; procedure CloseShowForm(AFormRef : LongInt);stdcall;external 'FileOperate.dll';
除了普通窗体外,怎么在DLL中创建TMDIChildForm呢?其实与创建普通窗体类似,不过这次需要传递调用程序的Application.MainForm作为参数:
function ShowForm(mainForm:TForm):integer;stdcall
var
Form1: TForm1;
ptr:PLongInt;
begin
ptr:=@(Application.MainForm);//先把DLL的MainForm句柄保存起来,也无须释放,只不过是替换一下
ptr^:=LongInt(mainForm);//用调用程序的mainForm替换DLL的MainForm
Form1:=TForm1.Create(mainForm);//用参数建立
end;
代码中用了一个临时指针的原因在Application.MainForm是只读属性。

MDI窗体的FormStyle不用设为fmMDIChild。

引出DLL中的对象
从DLL窗体的例子中可以发现,将句柄做为参数传递给DLL,DLL 能指向这个句柄的实例。

同样的道理,从DLL中引出对象,基本思路是通过函数返回DLL中对象的指针,将该指针赋值到宿主程序的变量,使该变量指向内存中某对象的地址。

对该变量的操作即对DLL中的对象的操作。

本文不再详解代码,仅说明需要注意的几点规则:
1、应用程序只能访问对象中的虚拟方法,所以要引用的对象方法必须声明为虚方法;
2、 DLL和应用程序中都需要相同的对象及方法定义,且方法定义顺序必须一致;
3、 DLL中的对象无法继承;
4、对象实例只能在DLL中创建。

声明虚方法的目的不是为了重载,而是为了将该方法加入虚拟方法表中。

对象的方法与普通例程是不同的,这样做才能让应用程序得到方法的指针。

DLL毕竟是结构化编程时代的产物,基于函数级的代码共享,实现对象化已经力不从心。

现在类似DLL功能,但对对象提供强大支持的新方式已经得到普遍应用,象接口(COM/DCOM/COM+)之类的技术。

进程内的服务端程序从外表看就是一个dll文件,但它不通过外部例程引出应用,而是通过注册发布一系列接口来提供支持。

它与DLL从使用上有两个较大区别:需要注册,通过创建接口对象调用服务。

可以看出,DLL
虽然通过一些技巧也可以引出对象,但是使用不便,而且常常将
对象化强制转为过程化的方式,这种情况下最好考虑新的实现方法。

相关文档
最新文档