做者:岑心 html
该文已经收藏,写得太好了,备份一份算法
做者原处:http://www.cnblogs.com/shangdawei/p/4058452.html数据库
引 言
相信有些计算机知识的朋友都应该据说过“DLL”。尤为是那些使用过windows操做系统的人,都应该有过屡次重装系统的“悲惨”经历——不管再怎样当心,没有驱动损坏,没有病毒侵扰,仍然在使用(安装)了一段时间软件后,发现windows系统愈来愈庞大,操做愈来愈慢,还不时的出现曾经能使用的软件没法使用的状况,致使最终不得不重装系统。这种状况经常是因为dll文件的大量安装和冲突形成的。这一方面说明DLL的不足,另外一方面也说明DLL的重要地位,以致咱们没法杜绝它的使用。
DLL(动态连接库,Dynamic Link Library)简单来讲是一种可经过调用执行的已编译的代码模块。DLL是windows系统的早期产物。当时的主要目的是为了减小应用程序对内存的使用。只有当某个函数或过程须要被使用时,才从硬盘调用它进入内存,一旦没有程序再调用该DLL了,才将其从内存中清除。光说整个windows系统,就包括了成百上千个dll文件,有些dll文件的功能是比较专业(好比网络、数据库驱动)甚至能够不安装的。假如这些功能所有要包括在一个应用程序(Application program)里,windows将是一个数百M大小的exe文件。这个简单的例子很容易解释DLL的做用,而调用DLL带来的性能损失则变得可被忽略不计。
多个应用程序调用同一个DLL,在内存里只有一个代码副本。而不会象静态编译的程序那样每个都必须所有的被装入。装载DLL时,它将被映射到进程的地址空间,同时使用DLL的动态连接并不是将库代码拷贝,而仅仅记录函数的入口点和接口。
同时DLL还能带来的共享的好处。一家公司开发的不一样软件可能须要一些公用的函数/过程,这些函数/过程多是直接的使用一些内部开发的DLL;一些经常使用的功能则能够直接使用windows的标准DLL,咱们常说的windows API就是包含在windows几个公用DLL文件里的函数/过程;理论上(若是不牵涉做者的版权),知道一个DLL的声明及做用(函数定义的输入参数及返回值),咱们彻底能够在不清楚其实现(算法或编译方式)的状况下直接使用它。
假如一个DLL中函数/过程的算法获得了更新,BUG获得了修正,整个dll文件会获得升级。通常来讲为了保证向下兼容,调用声明与返回结果应该保持不变。但实际上,即便是同一家开发的DLL,随着功能的改善,也很难保证某个调用执行彻底不变。在使用其余人开发的DLL时这种糟糕状况更加的严重。好比我在一个绘图程序里使用了某著名图形软件商旧版本的DLL包,我全部的调用都是根据他发布的旧版的声明来执行的。假设用户安装了该软件商的一个新软件,致使其中部分DLL被更新升级,假如这些DLL已经有过改动,直接后果将是个人软件再也不稳定甚至没法运行!不要轻视这种状况,事实上它是很广泛的,好比windows在修正BUG和升级过程当中,就不断改动它包含的那些DLL。每每新版DLL不是简单的增长新的函数/过程,而是更换甚至取消了原有的声明,这时候咱们再也没法保证全部程序都运行正常。
DLL除了上面提到的改善计算机资源利用率、增长开发效率、隐藏实现细节外,还能够包含数据和各类资源。好比开发一个软件的多国语言版,就可使用DLL将依赖于语言的函数和资源分离出来,而后让各地的用户安装不一样对应的DLL,以获取本地字符集的支持。再好比一个软件必须的图形、图标等资源,也能够直接放在dll文件中统一安装管理。 编程
在进行后面的讲解以前,我想你们应该先清楚一个概念:例程声明的是一个指针变量,调用函数/过程,实际上是经过指针转入该函数/过程的执行代码。
咱们先尝试用Delphi来创建一个本身的DLL文件。这个DLL包含一个标准的目录删除(包含子目录及文件)函数。
创建DLL
经过Delphi创建一个DLL是很容易的。New一个新Project,选择DLL Wizard,而后会生成一个很是简单的单元。该单元不象通常的工程文件以program开始,而是以library开始的。
该工程单元缺省引用了SysUtils、Classes两个单元。能够直接在该单元的uses以后,begin … end部分以前添加函数/过程代码,也能够在工程中添加包含代码的单元,而后该单元将会被自动uses。
接下来是编写DLL例程的代码。若是是引用单元里的例程,须要经过声明时添加export后缀引出。假如是直接写在library单元中的,则没必要再写export了。
最后一步是在library单元的begin语句之上,uses部分及函数定义之下添加exports部分,并列举须要引出的例程名称。注意仅仅是名称,不包含procedure或function关键字,也不须要参数、返回值和后缀。
exports语句后的语法有三种形式(例程指具体的函数/过程):
exports例程名;
exports例程名 index 索引值;
exports例程名 name新名称;
索引值和新名称便于其余程序肯定函数地址;也能够不指定,若是没有使用Index关键字,Delphi将按照exports后的顺序从1开始自动分配索引号。Exports后可跟多个例程,之间以逗号分隔。
编译,build最终的dll文件。
需注意的格式
为了保证生成的DLL能正确与C++等语言兼容,须要注意如下几点:
尽可能使用简单类型或指针做为参数及返回值的类型。这里的简单类型是指C++的简单类型,因此string字符串类型最好转换成Pchar字符指针。直接使用string的DLL例程在Delphi开发的程序中调用是没有问题的(有资料指出需加入ShareMem作为第一单元以确保正确),但若是使用C++或其余语言开发的程序调用,则不能保证参数传递正确;
虽然过程是容许的,可是最好习惯所有写成函数。过程则返回执行正确与否的true/false;
对于参数的指示字好比const(只读)、out(只写)等等,为保证调用的兼容性,最好使用缺省方式(缺省var,便可读写的地址);
使用stdcall声明后缀,以保证正确的异常处理。16位DLL没法经过这种方式处理异常,因此还得在例程最外层用Try … Except将异常处理掉;
通常不使用far后缀,除非为了保持与16位兼容。
范例代码
DLL工程单元: windows
library FileOperate; uses SysUtils, Classes, uDirectory in 'uDirectory.pas'; {$R *.res} exports DeleteDir; begin end.函数功能实现单元 : unit uDirectory; interface uses Classes, SysUtils; function DeleteDir( DirName : Pchar ) : boolean; export; stdcall; implementation function DeleteDir( DirName : Pchar ) : boolean; var FindFile : TSearchRec; s : string; begin s := DirName; if copy( s, length( s ), 1 ) <> '/' then s := s + '/'; if DirectoryExists( s ) then begin if FindFirst( s + '*.*', faAnyFile, FindFile ) = 0 then begin repeat if FindFile.Attr <> faDirectory then begin // 文件则删除 DeleteFile( s + FindFile.Name ); end else begin // 目录则嵌套自身 if ( FindFile.Name <> '.' ) and ( FindFile.Name <> '..' ) then DeleteDir( Pchar( s + FindFile.Name ) ); end; until FindNext( FindFile ) <> 0; FindCLose( FindFile ); end; end; Result := RemoveDir( s ); end; end.
Delphi中初始化有几种方法。一种是利用Unit的Initalization与Finalization这两个小节(不知道“单元小节”?你该先去恶补Delphi语法了)进行该单元中变量的初始化工做。注意,DLL虽然在内存中只有一个副本,可是例程隶属于调用者的不一样进程空间。若是想初始化公共变量来达到多进程共享是不可行的,同时也要注意公共变量带来的冲突问题。 数组
二是在library单元的begin … end部分进行DLL的初始化。假如想在DLL结束时有对应代码,则能够利用DLL自动建立的一个ExitProc过程变量,这是一个退出过程的指针。创建一个本身的过程,并将该过程的地址赋与ExitProc。由于ExitProc是DLL建立时就存在的,因此在begin … end部分就应该进行此步操做。同时创建一个临时指针变量保存最初的ExitProc值,在本身的退出过程当中将ExitProc值赋回来。这样作是为了进行本身的退出操做后,能完成缺省的DLL退出操做(与在重载的Destory方法中inherated的意义是同样的,完成了本身的destory,还须要进行缺省的父类destory才完整)。
示例以下: 网络
library MyDLL; var OldExitProc : pointer; // 公共变量,为的保存最初的ExitProc指针以便赋回 procedure MyExitProc; begin // 对应初始化的结束代码 ExitProc := OldExitProc; // 本身的退出过程当中要记住将ExitProc赋回 end; begin // 初始化代码 OldExitProc := ExitProc; ExitProc := @MyExitProc; end.
第三种方法和ExitProc相似,在System单元中预约义了一个指针变量DllProc(该方法须要引用 Windows单元)。在使用DLLProc时, 必须先写好一个具备如下原型的程序: 数据结构
procedure DLLHandler(dwReason: DWORD); stdcall;
并在library的begin..end.之间, 将这个DLLHandler程序的执行地址赋给DLLProc中, 这时就能够根据参数Reason的值分别做出相应的处理。示例以下: 函数
library MyDLL; procedure MyDLLHandler( dwReason : DWORD ); begin case dwReason of DLL_Process_Attach : ; // 进程进入时 DLL_Process_Detach : ; // 进程退出时 DLL_Thread_Attach : ; // 线程进入时 DLL_Thread_Detach : ; // 线程退出时 end; end; begin // 初始化代码 DLLProc := @MyDLLHandler; MyDLLHandle( DLL_Process_Attach ); end.
可见,经过DLLProc 在处理多进程时比ExitProc更增强大和灵活。 工具
DLL已经有了,接下来咱们看如何调用并调试它。普通的DLL是不须要注册的,可是要包含在windows搜索路径中才能被找到。搜索路径的顺序是:当前目录;Path路径;windows目录;widows系统目录(system、system32)。
引入DLL例程的声明方法
在须要使用外部例程(DLL函数/过程)的代码以前预约义该函数/过程。即按DLL中的定义原样声明,且仅须要声明。同时加上external后缀引入,与export引出相对应。根据exports的三种索引语法,也有三种肯定例程的方式(以函数声明为例):
function 函数名(参数表):返回值;external ’DLL文件名’;
function 函数名(参数表):返回值;external ’DLL文件名’ index 索引号;
function 函数名(参数表):返回值;external ’DLL文件名’ name 新名称;
若是不肯定例程名称,能够用索引方式引入。若是按原名引入会发生冲突,则能够用“新名称”引入。
进行声明后DLL函数的使用就和通常函数相同了。静态调用方式简单,但在启动调用程序时即调入DLL做为备用过程。若是此DLL文件不存在,那么启动时即会提示错误并马上终止程序,无论定义是否使用。
快速查看DLL例程定义可使用Borland附带的工具tdump.exe(在Delphi或BCB的bin目录下),示例以下:
Tdump c:/windows/system/user32.dll > user32.txt
而后打开user32.txt文件,找到Exports from USER32.dll行,之下的部分就是DLL例程定义了,好比:
VA Ord. Hint Name -------- ---- ---- ---- 00001371 1 0000 ActivateKeyboardLayout 00005C20 2 0001 AdjustWindowRect 0000161B 3 0002 AdjustWindowRectEx
Name列就是例程的名称,Ord就是该例程索引号。注意,该工具是不能获得例程的参数表的。若是参数错误,调用DLL例程会引发堆栈错误而致使调用程序崩溃。
调用代码
创建一个普通工程,在Main窗体上放置一个TShellTreeView控件(Samples页),再放置一个按钮,添加代码以下:
function DeleteDir( DirName : Pchar ) : boolean; stdcall; external 'FileOperate.dll'; procedure TForm1.Button1Click( Sender : TObject ); begin if DirectoryExists( ShellTreeView.Path ) then if Application.MessageBox( Pchar( '肯定删除目录' + QuotedStr( ShellTreeView.Path ) + '吗?' ), 'Information', MB_YESNO ) = IDYes then if DeleteDir( Pchar( ShellTreeView.Path ) ) then showmessage( '删除成功' ); end;
该范例调用的就是前面创建的DLL。
注意,声明时要包括stdcall后缀,这样才能保证调用Delphi开发的DLL的例程中相似PChar这样的参数值传递正确。你们有兴趣能够试验一下,不加入stdcall或者safecall后缀执行上面代码,将不能保证成功传递字符串参数给DLL函数。
调试方法
在Delphi主菜单Run项目中选择Parameters,打开“Run Parameters”对话框。
在Host Application中填入一个宿主程序(该程序调用了将要调试的DLL),还能够在Parameters中输入参数。保存内容,而后就能够在DLL工程中设置断点、跟踪/单步执行了。
Run该DLL工程,而后将运行宿主程序。执行会调用DLL的操做,而后就能跟踪进入该DLL的代码,接下来的调试操做和普通程序是同样的。
由于操做系统或其余软件影响的缘由,可能会出现进行了上述步骤仍然没法正常跟踪/中断DLL代码的状况。
这时能够试试在菜单Project |Options 对话框的 Linker 页面里将 EXE and DLL Options 中的Include TD32 debug info及include remote debug symbols两个选项选中。
假如仍是不能中断 那只好另外创建一个引用执行代码单元的应用程序,写代码调用例程调试完成后再编译DLL了(其实该方法有时候蛮方便的,但有时候亦很是麻烦)。
引入文件
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装载内存(引用记数为0时自动将该DLL从内存中清除),从而节约了内存空间。并且能够判断装载是否正确以免调用程序崩溃的状况,最多损失该例程功能而已。
动态调用虽然有上述优势,可是对于频繁使用的例程,因DLL的调入和释放会有额外的性能损耗,因此这样的例程则适合使用静态引入。
调用范例
DLL动态调用的原理是首先声明一个函数/过程类型并建立一个指针变量。为了保证该指针与外部例程指针一致以确保赋值正确,函数/过程的声明必须和外部例程的原始声明兼容(兼容的意思是一、参数名称能够不同;二、参数/返回值类型至少保持能够相互赋值,好比原始类型声明为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;
字符串参数
前面曾提到过,为了保证DLL参数/返回值传递的正确性,尤为是为C++等其余语言开发的宿主程序使用时,应尽可能使用指针或基本类型,由于其余语言与Delphi的变量存储分配方法多是不同的。C++中字符才是基本类型,串则是字符型的线形链表。因此最好将string强制转换为Pchar。
若是DLL和宿主程序都用Delphi开发,且使用string(还有动态数组,它们的数据结构相似)做为导出例程的参数/返回值,那么添加ShareMem为工程文件uses语句的第一个引用单元。ShareMem是Borland共享的内存管理器Borlndmm.dll的接口单元。引用该单元的DLL的发布须要包括Borlndmm.dll,不然就得避免使用string。
在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。
初始化COM库
若是在DLL中使用了TADOConnection之类的COM组件,或者ActiveX控件,调用时会提示 “标记没有引用存储”等错误,这是由于没有初始化COM。DLL中不会调用CoInitilizeEx,初始化COM库被认为是应用程序的责任,这是Borland的实现策略。
你须要作的是一、引用Activex单元,保证CoInitilizeEx函数被正确调用了
二、在单元级加入初始化和退出代码:
initialization Coinitialize(nil); finalization CoUninitialize; end.
三、 在结束时记住将链接和数据集关闭,不然也会报地址错误。
引出DLL中的对象
从DLL窗体的例子中能够发现,将句柄作为参数传递给DLL,DLL能指向这个句柄的实例。一样的道理,从DLL中引出对象,基本思路是经过函数返回DLL中对象的指针,将该指针赋值到宿主程序的变量,使该变量指向内存中某对象的地址。对该变量的操做即对DLL中的对象的操做。
本文再也不详解代码,仅说明须要注意的几点规则:
应用程序只能访问对象中的虚拟方法,因此要引用的对象方法必须声明为虚方法;
DLL和应用程序中都须要相同的对象及方法定义,且方法定义顺序必须一致;
DLL中的对象没法继承;
对象实例只能在DLL中建立。
声明虚方法的目的不是为了重载,而是为了将该方法加入虚拟方法表中。对象的方法与普通例程是不一样的,这样作才能让应用程序获得方法的指针。
DLL毕竟是结构化编程时代的产物,基于函数级的代码共享,实现对象化已经力不从心。如今相似DLL功能,但对对象提供强大支持的新方式已经获得广泛应用,象接口(COM/DCOM/COM+)之类的技术。进程内的服务端程序从外表看就是一个dll文件,但它不经过外部例程引出应用,而是经过注册发布一系列接口来提供支持。它与DLL从使用上有两个较大区别:须要注册,经过建立接口对象调用服务。能够看出,DLL虽然经过一些技巧也能够引出对象,可是使用不便,并且经常将对象化强制转为过程化的方式,这种状况下最好考虑新的实现方法。
注:本文代码在Delphi六、7中调试经过。
附:本文参考了“Delphi5开发人员指南”等书及资料。
1。每一个函数体(包括exports和非exports函数)后面加 'stdcall;', 以编写出通用的dll
2。exports函数后面必须加'export;'(放在'stdcall;'前面)
3。对于非exports函数可使用string类型,并且建议使用string类型进行参数传递
4。对于exports函数请使用PChar类型作参数传递
5。若是exports调用其余函数,建议在exports函数体内使用变量过渡,而后再调用其余函数; 也就说:尽可能不要把exports的参数再做为参数调用其余函数。
6。exports函数中若是有回传参数:若是是非地址型的(如integer,boolean等基本类型)请 使用var前缀,若是是地址型的请不要使用var前缀(如PChar或数组等)。 对不使用var前缀要回传的参数请使用内存拷贝类函数,如StrPCopy,CopyMemory,Move等。 缘由:dll和主应用程序并不能很好的共用一块内存,因此必须进行内存拷贝才能正确将dll 中的内容回传(拷贝)到主应用程序中。也所以对回传的地址标识类参数,在调用dll以前必须 进行内存分配,例如Delphi中:AllocMem(n integer),Pb中:Space(nlong)。 注意在调用dll处dll函数声明时,如果delphi参数声明同dll中的参数声明(回传地址型的参数无需加 var),如果pb回传参数必须加ref 前缀。
7。非exports函数的参数必须遵循规则:回传参数加前缀var,你彻底能够对待非exports函数同在Delphi应用 里写函数同样
8。非exports函数中若是有数组参数,不管是否回传,请加var前缀,它是地址调用
9。在dll中布尔型请注意bool和boolean的区别,在调用方环境中将可能引发不一样的结果
10。在dll函数中尽可能避免使用delphi特有的数据类型或类,如TStringList等
11。减小use列表中没必要要单元的引用,以减小dll的大小
12。dll的调试:可使用showmessage(需use dialogs)来调试,也能够[run]->[Parameters]中配置宿主 程序来单步跟踪dll的执行状况
13。请注意dll中申请的全部内存必须正确释放,不然dll可能在被调用n次以后会出现地址引用错误
14。在调用dll时候:
1)运行环境:能够直接放在应用程序同目录下,也能够放在一个文件夹下,若是放在一个文件夹下
你必须将此文件夹路径设置到环境变量中,你能够在应用程序中设置,也能够在dll中设置:
var PathBuf : array [ 0 .. 2048 ] of Char; Pathstr : string; begin FillChar( PathBuf, 2048, ' ' ); windows.GetEnvironmentVariable( 'PATH', PathBuf, 2048 ); Pathstr := string( PathBuf ); Pathstr := Trim( Pathstr ); if Pos( lowerCase( AppPath + 'tuxedo/dll' ), lowerCase( Pathstr ) ) <= 0 then begin Pathstr := Pathstr + ';' + AppPath + 'tuxedo/dll'; SetEnvironmentVariable( 'PATH', PAnsiChar( Pathstr ) ); end; end;
2)开发环境:若delphi同运行环境没什么区别,它是直接编译生成应用程序,并运行应用程序;
若PB,必须将dll的路径相对PB的开发工具的应用程序来设置,如放到pb9.0.exe同目录下,固然你能够
设置[HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows/CurrentVersion/App Paths/]下面对应你的开
发工具的应用程序名称目录下设置dll所在的路径(分号隔开添加既可,不要将原来的路径覆盖)
在dll中获取dll的路径:
var Buffer:array [0..255] of char; tmpstr:String; begin GetModuleFileName(HInstance, Buffer, SizeOf(Buffer)); tmpstr:=ExtractFilePath(Buffer); //... end;
提示信息尽可能不要在dll中showmessage,最好是做为信息参数传回,宿主程序再根据结果来进行信息提示, 这样也能够不引用Dialogs单元