关于dll动态加载和静态加载的困惑,麻烦各位高手给我解下惑(200)

  • 主题发起人 主题发起人 FreeAndNil
  • 开始时间 开始时间
F

FreeAndNil

Unregistered / Unconfirmed
GUEST, unregistred user!
以下说明,估计大家都熟悉:>>动态链接库简介 >>动态链接库(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。
 
测试代码到笔记中下载:http://www.delphibbs.com/keylife/iblog_show.asp?xid=31758附件中的是静态加载的,动态加载只需把静态加载的两行代码注释掉,再把Project3.dpr、Project4.dpr中目前注释的语句启用即可。另外说一下,这个是昨天写的,我的确是在360中看到加载一个project3.dll,内存中有两个project1.dll,可今天刚才运行测试代码,发现一个dll只加载一次project1.dll了,郁闷,不过加载project3.dll和project4.dll后,project1.dll的确是加载了两次。
 
是不是重定位前一个拷贝、重定位后一个拷贝?如果这样,也属正常哇。建议动态加载,灵活、经济。
 
问题已经解决,我只想知道为什么会这样。
 
我不是很清楚,但我也有您相似的问题,希望也能加以掌握
 
那是因为你路径引用错误的原因.你把所有的C:/....的绝对路径全删掉然后重新编译试下
 
louemusic,“那是因为你路径引用错误的原因”,不明白,好像没有听说过静态加载dll不能用绝对路径的说法,虽然我去掉绝对路径后,的确解决了一部分问题,即,当project1.dll与project3.dll、project4.dll在同一目录下,直接写project1.dll静态加载,和动态加载一样,没有问题,但是,我在testdll目录下建立了个dll目录,把project1.dll放到dll目录下,在project3.dll、project4.dll静态加载project1.dll的地方,用相对路径:'dll/project1.dll',结果和使用绝对路径是一样的。project3.dll、project4.dll中静态链接project1.dll使用了绝对路径,但project1.dll毕竟是同一个,怎么会重复加载进内存?而且,卸载project3.dll、project4.dll中的一个,为什么另外一个加载的project1.dll也被卸载了呢?而且,一般应用,都会把文件分目录进行管理,使用相对路径,也会出现和使用绝对路径的问题,而动态加载dll,无论使用相对路径还是绝对路径,都没有问题,这是为什么呢?静态加载、动态加载dll有什么区别?还是delphi本身的问题?
 
我想LZ的测试用例搞错了,对于DLL而言无论其加载方式如何,在同一主程序中仅加载一次无论其子程序通过什么方式都仅调用一次。
 
jonathan236,测试代码在http://www.delphibbs.com/keylife/iblog_show.asp?xid=31758 ,请下载测试下看看我有没有搞错,或者指出哪里写错了。
 
在同一个进程中一个DLL被加载两次的情况,你的测试用例没有问题!对于最后面的问题,不难理解:进程在启用成功后会将所有静态资源都扫进内存,并做计数,在进程内部多次调用时会有递增计数,直到该调用主体被释放。DLL4与DLL3都静态调用DLL1,因此计数是2。因此直到这两个DLL都释放后进程才释放DLL1
 
jonathan236,老实说,你的确没有好好看我对问题的描述和测试代码。“DLL4与DLL3都静态调用DLL1,因此计数是2。因此直到这两个DLL都释放后进程才释放DLL1”这个是众所周知的,这也是我的代码所表现出来与这个说法不一致的地方,测试、观察过程,测试代码,在问题、附件中已经描述的很清楚,请再看一下、测一下,顺便说一下,我用的是delphi6(之前忘记说了),其他版本不清楚。
 
不好意思,我IQ低,看不懂你最终所要描述的"精髓问题"所在!至于你的代码,抱歉我现在更看不懂了!
 
jonathan236,没关系的,IQ低不是你的错,谦虚点就可以了,我不介意把现象说的更清楚些的。打开我的测试代码,编译出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”按钮创建的窗体。如果你有查看进程的工具(没有的话用360),就可以看到,使用绝对路径静态加载dll会有什么问题。
 
奇怪的现象,静态加载dll貌似我这里也报错了,和楼主说的一样,我也是delphi6,关注一下。
 
接楼上操作并无报错.加载DLL3;加载DLL4;DLL3中SHOWA:2032702,OK;DLL4中SHOWA:2360589,OKDLL4中FREEA:1,0K;DLL4中卸载DLL1;因为我的文件放在I:/SOFT/,所以把代码中所有的C:/PROGRAM FILES/BORLADN...替换为了I:/SOFT/DLLTEST.D7环境
 
使用绝对路径静态加载DLL没什么问题!先说出现2个DLL1的原因:DLL加载都有个取优先级的路径的情况,如果是相对路径会在本目录中找,找不到会从系统目录中找,此时加载成功的在同一主程序中会分配一个地址空间,无论其被加载几次都指向同一个地址!而绝对路径加载就不一样了,它直接从指定的路径中找,找到就分配一个地址,不做同级比较,该主程序中是否存在此资源!你可以验证下:一个是相对路径一个是绝对路径,如果相先绝后,则有2个,如果绝先相后则只有一个。另一个问题:按这个顺序点击按钮,exe会崩溃:加载dll3、加载dll4、dll3中ShowA、dll4中ShowA、dll4中FreeA、卸载dll4,然后显示出“dll3中ShowA”按钮创建的窗体。这个BUG有时候会报有时候不报。这个实际一个主程序中存在两个相同资源,而DLL3或DLL4访问的不是自己的资源地址所致!从本质上是一种内存交叉问题,加个ShareMem就行了!
 
回jonathan236:>>先说出现2个DLL1的原因:DLL加载都有个取优先级的路径的情况,如果是相对路径会在本目录中找,找不到会从系统目录中找,此时加载成功的在同一主程序中会分配一个地址空间,无论其被加载几次都指向同一个地址!而绝对路径加载就不一样了,它直接从指定的路径中找,找到就分配一个地址,不做同级比较,该主程序中是否存在此资源!你可以验证下:一个是相对路径一个是绝对路径,如果相先绝后,则有2个,如果绝先相后则只有一个。我想,你只测试了完全不写路径加载dll,这个现象louemusic之前已经说了,我也回复了,请往上看一下讨论。其实这个情况只适用于完全不写路径,且dll在系统搜索路径下的情况,如果你写了相对路径,比如dll/project1.dll,无论先加载绝对还是相对路径的dll,都是一样加载两次,请你测试验证。>>另一个问题:按这个顺序点击按钮,exe会崩溃:加载dll3、加载dll4、dll3中ShowA、dll4中ShowA、dll4中FreeA、卸载dll4,然后显示出“dll3中ShowA”按钮创建的窗体。这个BUG有时候会报有时候不报。这个实际一个主程序中存在两个相同资源,而DLL3或DLL4访问的不是自己的资源地址所致!从本质上是一种内存交叉问题,加个ShareMem就行了!你加ShareMem测试了吗?如果你测试了没问题那就奇怪了,我测试就没通过,因为问题根本不是内存交叉问题,而是project1.dll已经全部被从内存卸载了,dll都不在了,调用其中的函数还能不报错?我想你说的“有时候会报有时候不报”是结合问题1的代码的测试结果,即,如果完全不写加载路径,project1.dll只加载一次时,卸载dll3、dll4中的一个,project1.dll仍然在内存中,这当然是不会报错,和我说的问题前提不同。这个代码在delphi5、delphi6下都是一样的问题,delphi7没有,不能验证。绝对路径静态加载dll,两个dll是在各自的内存中运行,你可以通过把dll1中创建的form的caption写成数组下标看到,dll3、dll4中创建的form的数组下标都是从0开始,而动态加载的是公用内存,下标是相互递增的,问题是,把dll两次加载进内存,但是卸载时dll3、dll4其中一个时,却把两个dll1全部卸载了,这才是问题本质所在(也是我想弄明白的)。
 
新手,学习中 。
 
这个问题有点意思,研究了一下,大概是这样的参考 windows_2000_source_code/win2k/private/ntos/dll/ldrsnap.cBOOLEANLdrpCheckForLoadedDll ( IN PWSTR DllPath OPTIONAL, IN PUNICODE_STRING DllName, IN BOOLEAN StaticLink, IN BOOLEAN Wx86KnownDll, OUT PLDR_DATA_TABLE_ENTRY *LdrDataTableEntry )...staticlink: if ( StaticLink ) { i = LDRP_COMPUTE_HASH_INDEX(DllName->Buffer[0]); Head = &LdrpHashTable; Next = Head->Flink; while ( Next != Head ) { Entry = CONTAINING_RECORD(Next, LDR_DATA_TABLE_ENTRY, HashLinks);#if DBG LdrpCompareCount++;#endif if (RtlEqualUnicodeString(DllName, [red]&Entry->BaseDllName[/red], TRUE )) { *LdrDataTableEntry = Entry; return TRUE; } Next = Next->Flink; } } if ( StaticLink ) { return FALSE; }其中用到的数据结构: PLDR_MODULE = ^LDR_MODULE; _LDR_MODULE = packed record InLoadOrderModuleList : LIST_ENTRY; InMemoryOrderModuleList : LIST_ENTRY; InInitializationOrderModuleList : LIST_ENTRY; BaseAddress : PVOID; EntryPoint : PVOID; SizeOfImage : ULONG; FullDllName : UNICODE_STRING; BaseDllName : UNICODE_STRING; Flags : ULONG; LoadCount : SHORT; TlsIndex : SHORT; HashTableEntry : LIST_ENTRY; TimeDateStamp : ULONG; end; LDR_MODULE = _LDR_MODULE;注意红色部分代码,静态链接的情况下判断DLL是否加载仅检查文件名是否一致,忽略DLL完整路径所以dll3、dll4加载了dll1时并不是我们认为的只加载1次,后面引用计数加1,而是加载了2次,进程中存在两个dll1后面FreeLibrary的代码我没仔细看,不过很显然,释放dll3/dll4其中任意一个,遍历检查DLL是否需要释放时,不管是用文件名或是完整路径,两个dll1都会被匹配到并被释放,最终导致错误的结果
 
另外,翻阅了一下MSDN,没有找到有关静态链接时导出函数名称的命名规则说明如果哪位知道这方面资料的话,请贴出来和大家分享不过从代码上看,静态链接时应该是只能使用文件名而不能使用相对或绝对路径,否则就是MS的BUG了
 
后退
顶部