关于dll动态加载和静态加载的困惑,麻烦各位高手给我解下惑以下说明,估计大家都熟悉:>>动态链接库简介 
>>动态链接库(DLL,即 “Dynamic-Link Library”)是一个能够被应用程序和其它的DLL调用的过程和函数的集合体,它里面包含的是公共代码或资源。DLL是Windows的基石,所有的Win32 API函数都包含在DLL中。 >>使用DLL有许多优点: >>1、一个DLL可以提供给不同的程序使用,如果有多个程序使用相同的DLL,也只需将DLL在内存中装载一次,这样就节省了内存开销。 >>......最近写代码发现个问题,这个优点1,现在看来是有点问题。照理说,一个dll,无论通过什么方式加载(静态、动态),在内存中,都应该只加载一次,卸载时,如果有一个加载的程序没有卸载dll,这个dll都应该仍然在内存中,直到所有使用者卸载了dll,但是我写了段代码,发现事实却不是这样。代码很简单,主程序exe,加载了动态库:dll3、dll4,这两个dll都加载了dll2,并在窗体中调用dll2的一个函数显示一个公用窗体,为简单,dll3、dll4就直接用静态连接的方式加载了dll2,结果发现,当dll3、dll4的窗体都打开了dll2中的公用窗体,然后卸载dll3或dll4中的一个(卸载时会调用dll2中的释放窗体函数去释放窗体),主程序马上崩溃,另外一个现象是,dll3、dll4两个窗体打开,然后dll4个调用dll2中函数打开公用窗体,然后卸载dll4,这时在dll3的窗体中,调用dll2的函数去打开公用窗体,会报地址错误。手头没有其他工具,就用360->高级->系统进程状态功能,查看这个exe加载dll的情况,一看,发现了奇怪现象,加载dll3(其中静态加载dll2),结果360显示,dll2被加载了两次,然后再加载dll4(其中静态加载dll2),dll2没有再次被加载,卸载dll4,发现,exe进程中的两个dll2都被卸载了,也就是说,虽然dll3仍然在内存中,但dll3静态加载的dll2,却已经被从内存中卸载了,所以才有了上面两个现象:1、此时在dll3中调用dll2的函数,报地址错误,当然了,dll都卸载了;2、如果卸载dll4之前,dll3已经调用dll2中的函数打开了窗体,那么在卸载dll4后,主程序崩溃。解决方法很简单,就是用在dll3、dll4中LoadLibrary动态加载dll2,这时用360工具去看进程加载dll的情况,就完全和前面说的一样,dll3、dll4加载了dll2,内存中只看到dll2加载了一次,卸载dll3、dll4中的一个,dll2仍然在内存中,直到所有加载dll2的dll全部从内存卸载,dll2才从内存中卸载。这个现象怎么解释?静态加载dll为什么会有这样的问题?麻烦各位高手给我解下惑。我用的是delphi6。  以下为代码:Project1.dll代码(其中Unit1就是一个form,没代码):
library Project1;uses
  SysUtils, Unit1 in 'Unit1.pas' {Form1};{$R *.res}type
  Tt = record
    form: TForm1;
    h: THandle;
  end;
var
  a:array of Tt;function ShowA(h: THandle):THandle; stdcall;
var
  i: integer;
begin
  i := length(a);
  setlength(a, i + 1);
  a[i].form := TForm1.Create(nil);
  a[i].form.Caption := IntToStr(i);
  a[i].form.Show ;
  a[i].h := h;
  Result := a[i].form.Handle ;
end;function FreeA():boolean; stdcall;
var
  i: integer;
begin
  for i := 0 to length(a) - 1 do
  begin
    a[i].form.Free ;
  end;
  setlength(a, 0);
  Result := true;
end;exports
  ShowA, FreeA;begin
end.
Project3.dll代码:
library Project3;uses
  SysUtils,
  windows,
  Dialogs,
  Classes;
type
    TShowA = function (h: THandle):THandle;stdcall;
    TFreeA = function:boolean;stdcall;{$R *.res}
  function ShowA3(h: THandle):THandle;stdcall;external 'C:\Program Files\Borland\Delphi6\Projects\dlltest\Project1.dll' name 'ShowA';
  function FreeA3():boolean;stdcall;external 'C:\Program Files\Borland\Delphi6\Projects\dlltest\Project1.dll' name 'FreeA';//  动态加载Project1.dll调用请启用以下代码
//var
//  h3: THandle ;
//  ShowA3: TShowA;
//  FreeA3: TFreeA;function ShowA(h: THandle):THandle; stdcall;
begin
  Result := ShowA3(h);
end;function FreeA():boolean; stdcall;
begin
  Result := FreeA3;
end;procedure LoadA(); stdcall;
begin
//  动态加载Project1.dll调用请启用以下代码
//  h3 := LoadLibrary('C:\Program Files\Borland\Delphi6\Projects\dlltest\Project1.dll');
//  if h3 <> 0 then
//  begin
//    @ShowA3 := GetProcAddress(h3, 'ShowA');
//    @FreeA3 := GetProcAddress(h3, 'FreeA');
//    if (@ShowA3 = nil) or (@FreeA3 = nil) then
//    begin
//      FreeLibrary(h3);
//      ShowMessage('err');
//    end;
//  end;
end;procedure UnLoadA(); stdcall;
begin
//  动态加载Project1.dll调用请启用以下代码
//  FreeLibrary(h3);
end;
exports
  ShowA, FreeA, LoadA, UnLoadA;begin
end.
Project4.dll代码:
library Project4;uses
  SysUtils,
  windows,
  Dialogs,
  Classes;type
    TShowA = function (h: THandle):THandle;stdcall;
    TFreeA = function:boolean;stdcall;{$R *.res}
//C:\Program Files\Borland\Delphi6\Projects\dlltest\
  function ShowA4(h: THandle):THandle;stdcall;external 'Project1.dll' name 'ShowA';
  function FreeA4():boolean;stdcall;external 'Project1.dll' name 'FreeA';//  动态加载Project1.dll调用请启用以下代码
//var
//  h4: THandle ;
//  ShowA4: TShowA;
//  FreeA4: TFreeA;function ShowA(h: THandle):THandle; stdcall;
begin
  Result := ShowA4(h);
end;function FreeA():boolean; stdcall;
begin
  Result := FreeA4;
end;procedure LoadA(); stdcall;
begin
//  动态加载Project1.dll调用请启用以下代码
//  h4 := LoadLibrary('C:\Program Files\Borland\Delphi6\Projects\dlltest\Project1.dll');
//  if h4 <> 0 then
//  begin
//    @ShowA4 := GetProcAddress(h4, 'ShowA');
//    @FreeA4 := GetProcAddress(h4, 'FreeA');
//    if (@ShowA4 = nil) or (@FreeA4 = nil) then
//    begin
//      FreeLibrary(h4);
//      ShowMessage('err');
//    end;
//  end;
end;procedure UnLoadA(); stdcall;
begin
//  动态加载Project1.dll调用请启用以下代码
//  FreeLibrary(h4);
end;
exports
  ShowA, FreeA, LoadA, UnLoadA;
  
begin
end.
Project2.exe主要代码:
type
  TShowA = function (h: THandle):THandle;stdcall;
  TFreeA = function:boolean;stdcall;
  TLoadA = procedure;stdcall;
  TUnLoadA = procedure;stdcall;var
  Form2: TForm2;  h3: THandle;
  LoadA3: TLoadA;
  UnLoadA3: TLoadA;
  ShowA3: TShowA;
  FreeA3: TFreeA;  h4: THandle;
  LoadA4: TLoadA;
  UnLoadA4: TLoadA;
  ShowA4: TShowA;
  FreeA4: TFreeA;  hh3: array of THandle = nil;
  hh4: array of THandle = nil;
  
implementation////////////////////////////////////////////
//                加载dll3                //
////////////////////////////////////////////
procedure TForm2.btn1Click(Sender: TObject);
begin
  h3 := LoadLibrary('C:\Program Files\Borland\Delphi6\Projects\dlltest\Project3.dll'); 
  if h3 <> 0 then
  begin
    @ShowA3 := GetProcAddress(h3, 'ShowA');
    @FreeA3 := GetProcAddress(h3, 'FreeA');
    @LoadA3 := GetProcAddress(h3, 'LoadA');
    @UnLoadA3 := GetProcAddress(h3, 'UnLoadA');
    if (@ShowA3 = nil) or (@FreeA3 = nil) then
    begin
      FreeLibrary(h3);
      ShowMessage('err');
    end
    else
      LoadA3;
  end;
end;  ////////////////////////////////////////////
//             调用dll3中ShowA            //
////////////////////////////////////////////
procedure TForm2.btn2Click(Sender: TObject);
var
  l: integer;
begin
  l := length(hh3);
  setlength(hh3,l + 1);
  hh3[l] := ShowA3(self.Handle);
end;////////////////////////////////////////////
//             调用dll3中FreeA            //
////////////////////////////////////////////
procedure TForm2.btn3Click(Sender: TObject);
begin
  if FreeA3 then ShowMessage('ok');
end;////////////////////////////////////////////
// 调用dll3中卸载dll1函数,动态加载时调用 //
////////////////////////////////////////////
procedure TForm2.btn9Click(Sender: TObject);
begin
  UnLoadA3;
end;////////////////////////////////////////////
//                卸载dll3                //
////////////////////////////////////////////
procedure TForm2.btn7Click(Sender: TObject);
begin
  FreeLibrary(h3);
end;////////////////////////////////////////////
//                加载dll4                //
////////////////////////////////////////////
procedure TForm2.btn4Click(Sender: TObject);
begin
  h4 := LoadLibrary('C:\Program Files\Borland\Delphi6\Projects\dlltest\Project4.dll');
  if h4 <> 0 then
  begin
    @ShowA4 := GetProcAddress(h4, 'ShowA');
    @FreeA4 := GetProcAddress(h4, 'FreeA');
    @LoadA4 := GetProcAddress(h4, 'LoadA');
    @UnLoadA4 := GetProcAddress(h4, 'UnLoadA');
    if (@ShowA4 = nil) or (@FreeA4 = nil) then
    begin
      FreeLibrary(h4);
      ShowMessage('err');
    end
    else
      LoadA4;
  end;
end;////////////////////////////////////////////
//             调用dll4中ShowA            //
////////////////////////////////////////////
procedure TForm2.btn5Click(Sender: TObject);
var
  l: integer;
begin
  l := length(hh4);
  setlength(hh4,l + 1);
  hh4[l] := ShowA4(self.Handle);
end;////////////////////////////////////////////
//             调用dll4中FreeA            //
////////////////////////////////////////////
procedure TForm2.btn6Click(Sender: TObject);
begin
  if FreeA4 then ShowMessage('ok');
end;////////////////////////////////////////////
// 调用dll4中卸载dll1函数,动态加载时调用 //
////////////////////////////////////////////
procedure TForm2.btn10Click(Sender: TObject);
begin
  UnLoadA4;
end;////////////////////////////////////////////
//                卸载dll4                //
////////////////////////////////////////////
procedure TForm2.btn8Click(Sender: TObject);
begin
  FreeLibrary(h4);
end;
编译出1个exe,3个dll,dll3、dll4加载dll1是静态加载的,运行exe:按这个顺序点击按钮就会出现地址错误:加载dll3、加载dll4、卸载dll4、dll3中ShowA。按这个顺序点击按钮,exe会崩溃:加载dll3、加载dll4、dll3中ShowA、dll4中ShowA、dll4中FreeA、卸载dll4,然后显示出“dll3中ShowA”按钮创建的窗体。问题的本质是:
dll3、dll4用静态加载dll1,dll1会被两次加载进内存,但是卸载时dll3、dll4其中一个时,却把两个dll1全部卸载了。
动态加载不存在这个问题。
静态加载如果完全不写路径,dll1在系统搜索路径,运行结果和动态加载一样,如果静态加载dll1使用相对路径(比如:dll\project1.dll),同样存在这个问题。代码已经在delphi5、delphi6下验证有这个问题。

解决方案 »

  1.   

    静态调用DLL是在程序启动的时候就加载进来了,一直到程序退出才会释放;而动态调用是在你调用的时候才加载,在你释放的时候就释放了。
      

  2.   

    测试了一下,的确有这个问题,可能导入表中带路径的话会造成dll的引用计数失效。如果真的是这样应该算是windows的bug了吧,不过导入表带路径这种链接方式也很奇怪,不知道别家的链接器会不会也这样
      

  3.   

    以前也用过dll,出现很多奇怪问题
    原因可能是在dll中使用VCL控件了吧.
    俺 一般用dll只是用来实现一些function或者procedure(不涉及到VCL控件)
    要用到VCL时,基本上用的是package
      

  4.   

    有没有用delphi7的,另外一个论坛的网友说他d7没这个问题,如果有问题我觉得可能是delphi在处理带路径的dll静态加载时的bug,可能高版本的delphi就好了。
      

  5.   

    他没测明白,而且这个跟delphi没关系,有问题也是系统加载pe文件的问题,我用xp sp3重现了你的问题
    一个导入表中带path另一个不带,先unload不带path的会导致它静态引用的dll也被unload