Delphi Libraries(Delphi 库)
简介:
Object Pascal 中“动态掉入库(dynamically loadable library)”
在 Windows下 以名称为“动态链接库” 后缀名为“.DLL” 形式的文件出现在系统当中.
在 Linux 下以名称为“共享目标库”后缀名以“.so” 形式的文件出现在系统当中.
本文主要讲解的是在Windows 中使用 Borland Delphi 开发软件 进行对 ”动态链接库-DLL” 规则讲解.
Libraries (库)的定义: 它是一个例程的集合.
Libraries (库)的形式: 像单元文件一样, “动态链接库”包含共享的代码和资源,但这个库是一个可以单独编译的可执行文件,它在运行时被连接到使用它的程序当中.
调用动态链接库:
在Delphi 当中 Windows 系统例程(Windows API) 存放在Uses 的子单元当中.事先就给开发者们把API函数连接到了Delphi的系统单元文件当中(系统单元是不可见的).方便调用.
你可以直接调用操作系统的例程,但他们直到运行时才被连接到你的程序当中.这说明在编译程序时它们不会被编译验证.
这也说明当你调入一个“动态链接库”中的例程是不会被编译验证的.当你调用外部“动态链接库”,如果“动态链接库”没有相应的例程则会出现无法定位而报错.
调用动态链接库有两种调用方法: 1.静态调入 2.动态调入
1.静态调入
使用 external 指示字声明”动态链接库”名称.
在 Windows 下: procedure DoSomething; external 'MYLIB.DLL';
在 Linux 下: procedure DoSomething; external 'mylib.so';
需要注意的有:
1.MyLIB.DLL 或者 mylib.so 这2个文件必须在程序的根目录下才能顺利调入.
2.例程名也必须与“动态链接库”当中的例程名相同才能顺利的调入.
若你在程序中包含这个声明,“动态掉入库”在程序启动时被调入一次,在程序的整个运行期间,标志符 DoSomething 总是指同一个共享库中的同一个入口点。
静态调入的缺点:例程占用内存, 直到程序结束.
2.动态调入
动态调入就是利用操作系统中的库函数(API函数)来调入一个外部“动态链接库”中的例程.
通常我们使用 LoadLibrary,FreeLibrary,GetProcAddress. 这3个函数来动态调用一个外部的“动态链接库”
{这3个函数的使用方法我已经详细的写在了博客当中.连接地址为:
http://www.cnitblog.com/Archer/archive/2009/03/28/55831.html}
动态调入的优点:解决了静态调用时,例程占用内存的问题.
Writing dynamically loadable libraries(编写动态调入库)
Writing dynamically loadable libraries(编写动态调入库)
“动态链接库”与程序文件唯一不同的就是: 以关键字Library 开始.取代了(program).
编写动态链接库需要注意以下几点.
1.只有被库明确输出(exports)的例程才能被其他库或程序导入.
2. 若要你的库对其它语言编写的程序是可见的,最安全的办法是在声明输出函数时指定 stdcal 调用约定,其它语言或许不支持 Object Pascal 默认的 register调用约定。
库的源文件通常简化为包含一个 uses 子句、一个 exports 子句和初始化代码(指的是过程和函数的block代码)。
The exports clause(exports 子句)
当一个例程在 exports子句中列出时,它将被输出,才能被其他库或程序 导入,调用.
exports entry1, ..., entryn;
每个 entry可以是一个过程、函数或变量(它必须在 exports 子句之前声明)的名称,后面跟参数列表(只有当输出重载的例程时)和一个可选的 name 说明符,你可以使用单元名限定过程或函数的名称。
需要注意的有:
1. “动态链接库”输出例程建立不要使用索引说明符.
2. 名称说明符包括指示字name 后面跟一个字符串常量. 若entry没有名称说明符,例程被输出时使用声明的原始名称,包括拼写和大小写。
exports
DoSomethingABC name 'DoSomething';
当在动态调入库中输出重载的函数或过程时,你必须在 exports 子句中指定它的参数列表,
exports
Divide(X, Y: Integer) name 'Divide_Ints',
Divide(X, Y: Real) name 'Divide_Reals';
Library initialization code(库初始化代码)
一个库的块(block)所包含的语句构成了库的初始化代码,每当库被调入时,这些代码执行一次。
Global variables in a library(库中的全局变量)
在库中声明的全局变量不能被object Pascal 程序导入.
一个库一次能被多个程序使用(调用),每一个程序都把这个库加载到自己进程空间(内存空间)里(这是一个库的拷贝),且每个拷贝有自己的全局变量集合。对于在多个库间(或一个库的多个实例间)共享内存,它们必须使用内存映射文件。
这里说明库是被加载到内存当中的.在内存中文件是以16进制定位内存地址的.
Libraries and system variables(库和系统变量)
在 System单元声明的几个变量对自己引入的程序库有特殊影响.
1.IsLibrary 变量
IsLibrary变量是在System.pas单元中定义的全局标志变量之一。
使用 IsLibrary 变量来确定代码是作为<程序>还是<库>执行,IsLibrary 在程序中总是 False,在库中总是 True;
如果IsLibrary的值为True则表明程序模块(自身)是一个动态链接库,反之就是一个可执行程序。
此函数常用来判断调用的程序模块(自身)是“动态链接库”或者是“可执行程序”.
2. Hinstance (Handle instance 句柄实例)
hinstance是这个<应用程序>的<实例句柄>,也就是系统将可执行文件的映象加载到进程空间的基本地址,如果需要程序内包含的<资源(如图片资源)>的时候就需要这个实例句柄了。
当你启动一个程序时,操作系统会给这个程序起一个名字,这个名字就叫 Handle。
当你启动一个程序时,操作系统会将这个程序装载到某个内存空间,这个空间的起始地址就是HInstance。在 NT 系统这个 HInstance 一般都是 400000h。
Handle是windows系统里的句柄,是系统识别目标对象的一个标识,对于整个操作系统来说各句柄应该是唯一的,不会重复。而且Handle一般是由Windows系统分配的,程序无法控制。 而HInstance 是程序内部的句柄,实际值即Windows系统给应用程序分配的内存基址,也是该程序体内(包括主体EXE和调用的DLL)相互识别调用的一个标识,对于程序内部来书说,HInstance是唯一的,不会重复。但对于Windows系统来说,各程序EXE的HInstance大部分可以说是一样,一般都是默认的$00400000。这个值在大多数编译器中都可以修改。如DELPHI里可以在工程Options->Link->ImageBase里修改。 简单来说,大概应该可以这样来理解,Handle可以说是Windows对象句柄,HInstance可以说是程序模块句柄。 |
参考: {http://www.delphibbs.com/delphibbs/dispq.asp?lid=3136263}
3. DLLProc
DLLProc 变量允许一个库监测(监视)操作系统对它的入口点(entry point)的调用,这个特征通常只是由支持<多线程>的库使用。
在过程体中,你能依据哪个参数被传递给过程来指定要采取地行动。
要监测操作系统调用,创建一个回调过程,它接收一个整数参数,
此函数有以下几个参数
DLL_Process_Attach: //整个DLL的初始化代码
DLL_Process_Detach: //该值表示库作为完全退出的结果或调用FreeLibrary的结果,从调用库的进程中被分离。 {表示对DLL的善后代码}
DLL_Thread_Attach: //该值表示当前进程创建了一个新的线程(仅适用于Windows)。
DLL_Thread_Detach: //该值表示线程完全退出(仅适用于Windows)
Exceptions and runtime errors in libraries(库的异常和运行时错误)
当在动态链接库中有发生异常,<但没有处理时>,它把异常传播到库的外面到达调用者.则出现错误提示,导致程序结束.
如果调用程序或库是用 object Pascal 编写的. 可通过用tey...except 语句处理这些异常.
如果调用程序或库是用其它语言编写的,异常被当作操作系统的异常(异常代码:$0EEDFACE)进行处理。在操作系统异常记录的 ExceptionInformation 数组的第一个入口中,包含了异常地址,第二个入口包含一个指向 Object Pascal 异常对象的引用。
通常,你不应该使异常扩散到库的外面。在 Windows 下,Delphi 异常映射到操作系统的异常模型
若一个库没有使用 SysUtils 单元,它不支持异常处理。这种情况下,若库发生运行时错误,调用程序将终止。因为库没有办法知道它是否从一个 Object Pascal 程序进行调用,它不能调用程序的退出过程,程序只是简单地被终止,并从内存中清除。
Shared-memory manager (共享内存管理器)
当使用Delphi 自带的内存管理器时:
在Windows 下. 如果DLL 输出的例程是长字符串或者动态数组作为参数或者作为函数返回值(不管是直接的,还是通过记录或对象封装的),那么DLL 和它的客户端程序(或DLL)必须使用ShareMem 单元.
(由于库和程序之间内存管理的管理域限制,所以需要申请ShareMem单元,使他们能够共用一个内存管理器)
当一个程序或 DLL 调用 New或 GetMem分配内存,而在另一个模块中调用 Dispose或 FreeMem来释放内存时。需要用到ShareMem单元,.
ShareMem单元应当在程序或库的 uses子句中第一个列出。
ShareMem 是 BORLANDMM.DLL 内存管理器的接口单元,它允许在模块间共享动态分配的内存。
BORLANDMM.DLL必须连同使用ShareMem单元的程序和DLL一同发布。 当程序或DLL使用ShareMem
时,它的内存管理器被 BORLANDMM.DLL 中的取代。
现在常用的内存管理器有FastShareMem
为了方便阅读提供PFD版本: http://www.cnitblog.com/Files/Archer/Delphi%20library.zip