我的天,这段儿太忙,再加上我上大富翁老慢,所以就一直拖……
没有将这个贴子选进B计划,有些遗憾啊! 哈哈……
好的,现在给出这个贴子,请关注线程编程和动态链接库的定制
的朋友指正。
=================
=================
1. DLL工作原理
--------------
我只浅要地讲一下工作原理,以便于后面的叙述。
DLL是window为节约线程交互时间和提高代码共用性能而引入的。
它的特点在于一个DLL在系统中只被调入一次,当进程(如一个.exe)
操作申请一个.dll时,操作系统先查看系统内部是否已经调入该dll,
如果已经调入,操作系统便只在当前进程空间中制作一个.DLL公共变
量的拷贝,并且使用原进程空间中的DLL代码。
也就是说,任何时候,系统中只可以有一个.DLL的实例,但可以有任
意多个该DLL的数据拷贝。DLL的多个引用之间的代码是重叠的,而数
据是分开的。
2. 关于在进程间通讯,即DLL全局变量的问题。
--------------
好了,我们现在想到的事是,基于上述的原因,各进程间如果要涉及
数据交换就完了。因为各个DLL的各个实例通常运行在不同的地址空间
里。也就是说,一个.DLL文件通常被多个不同的.EXE调用,因此,数
据在各个.EXE独立的进程空间里。
1). 进程间共享数据的第一途径是DLL初始化。
DLL初始化中,可以给在interface中定义的全局变量赋初值。这个初
值在DLL的各个拷贝中都是相同的。至少,在申请到这个的最初,是一
样的。当然,必须记住的是,这只是在最初时相同的。
这句话在微软的标准文档中是这样解释的:“当加装了一个模块(EXE/DLL)
的两份拷贝,在可写数据还没有被修改时,在WinNT的进程共享数据”。
2). 进程间共享数据的第二途径是DLL全局内存块
说“全局内存块”,这种说法是不标准的。因为它的实际说法是“内存
映像文件”。这就好比我们打开了一个文件,A线程对文件的操作,对B
线程来说,是可见的。只不过,这个文件是打开在内存中的而已。
(一个内核的问题是,Window可能将这个映像文件建在磁盘上,但,这只
是操作系统的事,在程序设计这个级上,我们感觉它只是在内存中。)
这显然可以理解,任何对文件的操作都是实际的,你能够在不同的进程/
线程中同样的感知到同一个“内存映像文件”的修改。
3. 关于在线程间通讯,即线程全局变量的问题
--------------
线程间变量问题的解决是方便的,因为Delphi中可以象一个单元似的写
一个线程代码,你在单元接口声明的全局变量对所有线程来说,就是可
见的。除非你声明成了threadVar。
4. ISAPI-DLL的特殊性
--------------
很有意思的是,ISAPI-DLL通常只被一个.EXE调用。这是由IIS/PWS Server
的特殊性所决定的。因为IIS/PWS需要将ISAPI-DLL映射到自己的地址空
间,然后对由client(浏览器)端向这个DLL发出的http请求作出分配,并
交由ISAPI解释。所以,一个ISAPI-DLL通常只在一个.EXE中,有一个代码
实例,并且,在这个.EXE的代码空间中,可能同时拥有多个ISAPI线程。
所以,在ISAPI-DLL中所要处理的只是线程间通讯的问题,而不是进程间
的通讯问题。
5. 时钟问题的提出
--------------
这里的时钟问题的提出是偶然的,而且你可能只会在ISAPI-DLL的编写中
会遇见。下面讨论的方法也是目前能够使用的唯一可行方法。
当时,我正在写一个聊天室系统,聊天室系统中要求对异常断线的用户
作出检测。这样,我需要在ISAPI中专门写一个线程来检测用户(浏览器)
端在较长时间没有发服务器发出一个登录请求。这个线程每1.5分钟发生
一次,每次扫描一遍全部用户列表,对登录记数器作出检测,当记数器小
于1时,就自动注销这个用户。
很显然,解决这个问题的最直接方法就是在ISAPI中实现一个时钟线程,
每1.5分钟触发一次就可以了。
前面的讨论已经讲过不能用Timer对象了。
Wuyi提到了用SetTimer(),这个方法的确有可行之处。我曾经用了这种方
法,很有趣,在95/98环境中可以,但在NT中就不行。——我没有在任何
资料中看到对setTimer()这个函数有使用限制的!——后来,我同公司的
另一个技术人员讨论时,想到了在IIS这一级上,可能对setTimer()作了限
制,的确很有可能,因为IIS(ISAPI-DLL)这一级是远端操作NT Server的,
微软的确可能在这一级上对SetTimer()作出限制。
由于ISPAI-Filter(过滤器)中服务器级别的,我相信在Filter中可以使用
setTimer(),但在ISAPI-DLL(CGI)上的确没有办法使用。
6. 时钟问题的解决
--------------
想了四天,很简单。但得换个思路。^-^
——请创建一个线程,在这个线程的Execute()中,做一个死循环(放心,
我的确需要这样一个只有.dll死掉才停止的进程),在循环中用一个sleep()函
数虚拟一个时间的延时就成了。
哈哈,很简单吧。的确很简单。
上面的问题全解决了:
1) 由于新线程与.dll的所有线程位于同一地址空间(同一.EXE)内,所以可以
安全地访问任何全局变量。不用考虑进程间交互的问题。
2) sleep()是一个安全的延时函数,它使当前线程停止对CPU时钟的使用,直
到延时结束。所以不需要考虑该线程因为sleep()导致效率或者阻塞问题。
7. 关于大家答案中的一些问题
--------------
to huizhang :“Dll中全局变量都无法设置又怎样设置全局时钟呢?”
DLL中可以有全局变量,上面的讨论中提到了两种实现全局变量的方法:初始
化变量和内存映象文件。
to wsh :“DLL和Host App 可以是同一个线程啊!而且常常是!”
Host App是指一个宿主的应用程序,应用程序会被初始化成一个进程。并且,
每一个进程会拥有一个主线程。一个Host App对一个.dll的引用是将.dll载入
了应用程序的进程空间,并创建了一个处理线程。这意味着DLL和Host App并
不是同一个线程,只是在第一次调用时会和Host App处于同一地址空间而已。
而且,通常DLL与Host App并不处于同一个地址空间,新的应用程序调用只会
得到一个DLL的共享数据拷贝而已,代码已经不在同一地址空间了。
to duz:“真的不行,就应当用c来写这个DLL,用函数SetTimer就行了”
在ISAPI-DLL这一级上,Delphi与C的处理是差不多的。Delphi没有为ISAPI写
太多的东西。
上面讨论的技术在C和Delphi中是一致的。——我想,在C写
的ISAPI中,也可能无法使用setTimer(),我想可能是microSoft IIS的限制。
to pegasus:
什么好招???哈哈……
我暂时不关闭本问题吧。放在这儿大家看看,有什么不对的地方,请指正啊!
分是Wuyi的啊,如果大家还有什么新思路,还可以再抢抢…… ^0^