begin FDLLHandle:=LoadLibrary('AddFunction.dll');//获得句柄 if FDLLHandle>0 then try PAddFunction:=GetProcAddress(FDLLHandle, 'AddFunc'); //获得DLL中AddFunc过程的入口地址 if PAddFunction<>nil then // do something else showmessage('dll 没有找到。'); finally FreeLibrary(FDLLHandle); end else showmessage('dll 没有找到。'); end
//主程序 begin FDLLHandle:=LoadLibrary('AddFunction.dll'); //该变量设置为全局 传递给单击按钮事件的过程 if FDLLHandle<=0 then begin showmessage('dll 没有找到。'); exit(); end;//End for if FDLLHandle<=0 //开始主程序消息循环 //do something end. //end for main
按钮的单击事件映射到下面的过程 procedure TForm.Button_Click(Sender: TObject); begin try PAddFunction:=GetProcAddress(FDLLHandle, 'AddFunc'); if PAddFunction<>nil then // do something else showmessage('AddFunction.dll 没有找到。'); finally FreeLibrary(FDLLHandle); end//End for try end;//end for Proc Button_Click
的过程可能还没有运行完。如果直接使用: if FDLLHandle>0 then FreeLibrary(FDLLHandle); 那么正在运行的结果是进程死了。无论如何也无法关闭,除非直接杀死进程。请问有什么办法知道该DLL中的过
程仍然在运行。从而阻止直接释放DLL? 例如改写成为: if FDLLHandle>0 then if 该DLL所以过程都不在运行了 then FreeLibrary(FDLLHandle); else begin showmessage('请先结束所以任务后再退出主程序'); //不再结束主程序,返回主程序消息循环 // Do something end;//end for else of
中国风网 2004-1-12 16:07:42 -------------------------------------------------------------------------------- 动态链接库是一个能够被应用程序和其它的DLL调用的过程和函数的集合体,它里面包含的是公共代码或资源。由于DLL代码使用了内存共享技术,在某些地方windows也给了DLL一些更高的权限,因而DLL中可以实现一些一般程序所不能实现的功能,如实现windows的HOOK、ISAPI等。 同时,DLL还为不同语言间代码共享提供了一条方便的途径。因而DLL在编程时应用较为广泛,本文将介绍如何在 Delphi 中建立和使用DLL。 一.DLL 库内存共享机制 从使用效果看,DLL和unit 很像,它们都可以被别的工程模块所调用,但二者在内部的实现机制上确存在着差别。如果一个程序模块中用uses语句引用了某个unit,编译程序在编译该模块时,便会连同unit一起编译,并把编译后的可执行代码链接到本程序模块中,这就是一个程序模块能够调用所引用unit中过程和函数的原因。 当同一个unit被多个工程所引用时,则每个工程中都含有该unit的可执行代码,当含有该unit的多个工程同时执行时,unit的可执行代码会随不同工程而多次被调入内存,造成内存资源的浪费。DLL则不同,它即使被某个工程调用,编译后仍是独立的。 也就是说编译后,一个DLL库形成一个单独的可执行文件,而不与任何其它的可执行文件连接在一起,因而DLL库并不从属于某个特定的工程,当多个工程调用同一个DLL库时只有第一个工程把DLL库调入内存,其余工程并不重复调入同一个DLL库到内存,而是到同一个共享内存区读取。并且,DLL的执行代码是在程序运行期间动态调入的,而不是如unit在程序运行时就与整个工程一起调入内存。这样便可消除unit带来的相同代码多处占用内存的弊病。 二 Delphi中DLL库的建立 在Delphi环境中,编写一个DLL同编写一个一般的应用程序并没有太大的区别。事实上作为DLL主体的DLL函数的编写,除了在内存、资源的管理上有所不同外,并不需要其它特别的手段。 一般工程文件的格式为: program 工程标题; uses 子句; 程序体 而DLLs工程文件的格式为: library 工程标题; uses 子句; exprots 子句; 程序体 它们主要的区别有两点: 1.一般工程文件的头标用program关键字,而DLL工程文件头标用library 关键字。不同的关键字通知编译器生成不同的可执行文件。用program关键字生成的是.exe文件,而用library关键字生成的是.dll文件; 2.假如DLL要输出供其它应用程序使用的函数或过程,则必须将这些函数或过程列在exports子句中。而这些函数或过程本身必须用export编译指令进行编译。 在Delphi主菜单file 中选new...项,在弹出的窗口中双击DLL图标,便会自动给出DLL源模块框架,如下: Library project1; {...注释...} uses SysUtils, Classes; begin end. 接下来便可在USES和begin之间加入想在该DLL中实现的过程和函数的定义,并用export和exprots保字把它们引出,以便别的模块引用,在begin和end之间加入初始化代码,初始化代码是用来对DLL变量初始化的。应注意,即便无初始化代码begin与end也不可省略,如下例: library minmax; function Min(X, Y: Integer): Integer; export; begin if X < Y then Min := X else Min := Y; end; function Max(X, Y: Integer): Integer; export; begin if X > Y then Max := X else Max := Y; end; exports Min index 1, Max index 2; begin end. 经编译后,并以minmax.DLL存盘后,一个DLL库文件便形成了 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>你的问题是: 把 string 数组 变成 tstringlist型的 然后墙制转换成 pchar 型 回头 主程序中调用时,强制转化回来,就可以了 是 stirng 的数组了 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
都在D中就简单了: Dll中: library Project1;uses SysUtils, Classes, windows;{$R *.res} procedure getp(var sl:TStringList);stdcall; begin if sl<>nil then begin sl.Add('test1'); sl.Add('test2'); sl.Add('test3'); end; end;exports getp;begin end.使用dll部分: procedure getp(var sl:TStringlist);stdcall;external 'project1.dll';implementation{$R *.dfm}procedure TForm1.BitBtn1Click(Sender: TObject); var s:TStringList; i:integer; begin s:=TStringList.Create; getp(s); for i:=0 to s.Count-1 do ListBox1.Items.Add(s[i]); s.Free; end;end.
,得用pchar 型
我知道,典型的调用DLL中的某个过程,可以这样实现(Delphi语句):
begin
FDLLHandle:=LoadLibrary('AddFunction.dll');//获得句柄
if FDLLHandle>0 then
try
PAddFunction:=GetProcAddress(FDLLHandle, 'AddFunc');
//获得DLL中AddFunc过程的入口地址
if PAddFunction<>nil then
// do something
else
showmessage('dll 没有找到。');
finally
FreeLibrary(FDLLHandle);
end
else
showmessage('dll 没有找到。');
end
现在核心程序需要多次使用同一DLL中的过程。而且又不能使用多线程编程。例如,按下某个按钮后执行DLL中
的AddFunc过程。按多次该按钮就执行多次该DLL中的AddFunc过程。因此,上面的程序不需要这样写。只需要在
主程序中获得DLL句柄,在按钮事件中调用同一个DLL过程:
//主程序
begin
FDLLHandle:=LoadLibrary('AddFunction.dll');
//该变量设置为全局 传递给单击按钮事件的过程
if FDLLHandle<=0 then
begin
showmessage('dll 没有找到。');
exit();
end;//End for if FDLLHandle<=0
//开始主程序消息循环
//do something
end. //end for main
按钮的单击事件映射到下面的过程
procedure TForm.Button_Click(Sender: TObject);
begin
try
PAddFunction:=GetProcAddress(FDLLHandle, 'AddFunc');
if PAddFunction<>nil then
// do something
else
showmessage('AddFunction.dll 没有找到。');
finally
FreeLibrary(FDLLHandle);
end//End for try
end;//end for Proc Button_Click
这样就不需要多次载入DLL。因为只需要载入一次,每次传递句柄即可。
我也已经实现了这种方式的调用。问题是现在我的主程序在关闭的时候出现了问题:主程序关闭的时候,DLL中
的过程可能还没有运行完。如果直接使用:
if FDLLHandle>0 then FreeLibrary(FDLLHandle);
那么正在运行的结果是进程死了。无论如何也无法关闭,除非直接杀死进程。请问有什么办法知道该DLL中的过
程仍然在运行。从而阻止直接释放DLL?
例如改写成为:
if FDLLHandle>0 then
if 该DLL所以过程都不在运行了 then
FreeLibrary(FDLLHandle);
else
begin
showmessage('请先结束所以任务后再退出主程序');
//不再结束主程序,返回主程序消息循环
// Do something
end;//end for else of
请教如何得知DLL是否可以释放了?
---------------------------------------------------------------
我的建议是将上面的DLL的调用封装成一个类,提供方法供程序调用,在类的构造方法中调用LoadLibrary,析构函数中调用FreeLibrary,提供GetProcAddress的接口函数,这样可以适用同一DLL的多个函数,记数大于零,表明DLL仍在使用。基本框架我写了:
TDLLObject
private
FDLLHandle: HINSTANCE;// dll的句柄
FReferenceCount: Integer; // 已调用函数个数
public
constructor Create(sDLLName: string);
destructor Destroy(); override;
function Reference(): boolean; // 返回已调用函数个数
function GetProcAddr(sProcName: string): FARPROC; // 获得函数地址
procedure FreeProcReference(); // 函数适用完,将调用记数减1
end;
函数你自己实现吧,应该比较简单。
---------------------------------------------------------------
你只调用过一次LoadLibrary,那么DLL的引用计数便只有一次,所以FreeLibrary也仅需调用一次。DLL中的函数调用多少次都没有关系。
我怀疑你的DLL中的函数是不是创建了窗体或用到外部控件,这样的话需要先释放或关闭那些东西。这是别人写的 看看
图1 DLL的静态调用
该DLL的程序代码如下:library AddNum;
uses
SysUtils,
Classes;{$R *.res}function AddNumber(Num1,Num2:integer):integer;stdcall; //定义求和函数
begin
result:=Num1+Num2;
end;
exports
AddNumber; //引出求和函数
begin
end.
主程序在调用该DLL时,首先在interface部分声明要调用的函数:function AddNum(Num1,Num2:integer):integer;stdcall;external 'AddNum.dll'
name 'AddNumber';
然后在按钮控件的事件中写入如下代码:procedure TForm1.Button1Click(Sender: TObject);
var
Number1,Number2:integer;
Sum:integer;
begin
Number1:=strtoint(Edit1.Text);
Number2:=strtoint(Edit2.Text);
Sum:=AddNum(Number1,Number2); //调用求和函数计算结果
Edit3.Text:=inttostr(Sum);
end;
2.动态调用方式 这个示例程序创建了一个显示日期的DLL,其中包含一个窗体,如图2所示。
图2 DLL的动态调用
程序中定义了一个ShowCalendar函数,返回在这个窗体中设定的日期。函数定义如下:function ShowCalendar(AHandle: THandle; ACaption: String): TDateTime;
var
DLLForm: TDLLForm;
begin
Application.Handle := AHandle;
DLLForm := TDLLForm.Create(Application); //创建并显示窗体
try
DLLForm.Caption := ACaption;
DLLForm.ShowModal; //显示方式为模式化
Result := DLLForm.calDLLCalendar.CalendarDate; //返回设定日期
finally
DLLForm.Free; //用完后卸载该窗体
end;
end;
在DLL的工程文件中用exports ShowCalendar; 语句引出该函数。下面通过一个简单的应用程序测试一下该DLL文件。新建一个工程文件,在窗体中放置一个Label控件和一个按钮控件,在按钮控件的OnClick事件中编写如下代码:procedure TMainForm.Button1Click(Sender: TObject);
var
OneHandle : THandle; //定义一个句柄变量
begin
OneHandle := LoadLibrary('Clendar.dll'); //动态载入DLL,并返回其句柄
try
if OneHandle <> 0 then //如果载入成功则获取ShowCalendar函数的地址
@ShowCalendar := GetProcAddress(OneHandle, 'ShowCalendar');
if not (@ShowCalendar = nil) then
//如果找到该函数则在主窗体的Label1中显示DLL窗体中设定的日期
Label1.Caption := DateToStr(ShowCalendar(Application.Handle, Caption))
else
RaiseLastWin32Error;
finally
FreeLibrary(OneHandle); //调用完毕收回DLL占用的资源
end;
end;
从以上程序中可以看到DLL的动态调用方式比静态调用方式的优越之处。DLL例程在用到时才被调入,用完后就被卸载,大大减少了系统资源的占用。在调用LoadLibrary函数时可以明确指定DLL的完整路径,如果没有指定路径,运行时首先查找应用程序载入的目录,然后是Windows系统的System目录和环境变量Path设定的路径。
Delphi中动态链接库(DLL)的建立和使用1
中国风网 2004-1-12 16:07:42
--------------------------------------------------------------------------------
动态链接库是一个能够被应用程序和其它的DLL调用的过程和函数的集合体,它里面包含的是公共代码或资源。由于DLL代码使用了内存共享技术,在某些地方windows也给了DLL一些更高的权限,因而DLL中可以实现一些一般程序所不能实现的功能,如实现windows的HOOK、ISAPI等。 同时,DLL还为不同语言间代码共享提供了一条方便的途径。因而DLL在编程时应用较为广泛,本文将介绍如何在 Delphi 中建立和使用DLL。 一.DLL 库内存共享机制 从使用效果看,DLL和unit 很像,它们都可以被别的工程模块所调用,但二者在内部的实现机制上确存在着差别。如果一个程序模块中用uses语句引用了某个unit,编译程序在编译该模块时,便会连同unit一起编译,并把编译后的可执行代码链接到本程序模块中,这就是一个程序模块能够调用所引用unit中过程和函数的原因。 当同一个unit被多个工程所引用时,则每个工程中都含有该unit的可执行代码,当含有该unit的多个工程同时执行时,unit的可执行代码会随不同工程而多次被调入内存,造成内存资源的浪费。DLL则不同,它即使被某个工程调用,编译后仍是独立的。 也就是说编译后,一个DLL库形成一个单独的可执行文件,而不与任何其它的可执行文件连接在一起,因而DLL库并不从属于某个特定的工程,当多个工程调用同一个DLL库时只有第一个工程把DLL库调入内存,其余工程并不重复调入同一个DLL库到内存,而是到同一个共享内存区读取。并且,DLL的执行代码是在程序运行期间动态调入的,而不是如unit在程序运行时就与整个工程一起调入内存。这样便可消除unit带来的相同代码多处占用内存的弊病。 二 Delphi中DLL库的建立 在Delphi环境中,编写一个DLL同编写一个一般的应用程序并没有太大的区别。事实上作为DLL主体的DLL函数的编写,除了在内存、资源的管理上有所不同外,并不需要其它特别的手段。 一般工程文件的格式为:
program 工程标题;
uses 子句;
程序体
而DLLs工程文件的格式为:
library 工程标题;
uses 子句;
exprots 子句;
程序体
它们主要的区别有两点: 1.一般工程文件的头标用program关键字,而DLL工程文件头标用library 关键字。不同的关键字通知编译器生成不同的可执行文件。用program关键字生成的是.exe文件,而用library关键字生成的是.dll文件; 2.假如DLL要输出供其它应用程序使用的函数或过程,则必须将这些函数或过程列在exports子句中。而这些函数或过程本身必须用export编译指令进行编译。 在Delphi主菜单file 中选new...项,在弹出的窗口中双击DLL图标,便会自动给出DLL源模块框架,如下: Library project1;
{...注释...}
uses
SysUtils, Classes;
begin
end. 接下来便可在USES和begin之间加入想在该DLL中实现的过程和函数的定义,并用export和exprots保字把它们引出,以便别的模块引用,在begin和end之间加入初始化代码,初始化代码是用来对DLL变量初始化的。应注意,即便无初始化代码begin与end也不可省略,如下例: library minmax;
function Min(X, Y: Integer): Integer; export;
begin
if X < Y then Min := X else Min := Y;
end;
function Max(X, Y: Integer): Integer; export;
begin
if X > Y then Max := X else Max := Y;
end;
exports
Min index 1,
Max index 2;
begin
end. 经编译后,并以minmax.DLL存盘后,一个DLL库文件便形成了 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>你的问题是: 把 string 数组 变成 tstringlist型的 然后墙制转换成 pchar 型 回头 主程序中调用时,强制转化回来,就可以了 是 stirng 的数组了
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
同时 在主程序中定义一个 和 dll的 string 数组类型的一样的数据 用于读取指针指向的数组
Dll中:
library Project1;uses
SysUtils,
Classes,
windows;{$R *.res}
procedure getp(var sl:TStringList);stdcall;
begin
if sl<>nil then
begin
sl.Add('test1');
sl.Add('test2');
sl.Add('test3');
end;
end;exports
getp;begin
end.使用dll部分:
procedure getp(var sl:TStringlist);stdcall;external 'project1.dll';implementation{$R *.dfm}procedure TForm1.BitBtn1Click(Sender: TObject);
var
s:TStringList;
i:integer;
begin
s:=TStringList.Create;
getp(s);
for i:=0 to s.Count-1 do
ListBox1.Items.Add(s[i]);
s.Free;
end;end.