多线程DLL如何安全退出?(200分)

  • 主题发起人 主题发起人 wolaixue
  • 开始时间 开始时间
W

wolaixue

Unregistered / Unconfirmed
GUEST, unregistred user!
DLL中创建了一个线程(TestThread := TTestThread.Create(False);),线程执行代码如下:
type
TTestThread = class(TThread)
protected
procedure Execute;
override;
end;

var
TestThread: TTestThread;
implementation
procedure TTestThread.Execute;
begin
FreeOnTerminate := True;
while not Terminateddo
begin
//做些什么...
Sleep(0);
end;
end;

finalization
TestThread.Terminate;
TestThread.WaitFor;
end.

该DLL被主程序FreeLibrary或ExitProcess时(主程序LoadLibrary该DLL)会执行finalization
之后的代码,结果发现:FreeLibrary没问题,可在未调用FreeLibrary而直接ExitProcess时以上
代码就会出问题(FreeLibrary时TestThread还有机会执行,ExitProcess时却没有机会,因此
WaitFor就是死等了),甚至不执行以上两句代码也会出问题.
我想实现的是:在主程序没有机会调用FreeLibrary而直接ExitProcess的情况下,如何避免程序
出错!或者问如何在程序即将退出时结束TestThread线程!
 
在DLL里写一个县城类的外套,不要用finalization
 
FreeOnTerminate := True;
因为这个, 你的线程结束后已经被释放了, 所以TestThread
已经无效了, 不能再TestThread.WaitFor了。。。
 
to 张无忌:
不是很明白你这么说的含义.
不用TThread类,直接调用CreateThread生成"死循环"线程也是一样会出错!
finalization部分的代码是DLL在FreeLibrary或进程中止时调用的,未用
DLLEntryPoint.
to tseug:
我会试试.
不过,我的看法是在正常情况下:
FreeOnTerminate := True;
......
TestThread.Terminate;
TestThread.WaitFor;
这种代码好象不会够成问题.TestThread.Terminate;并不会马上中止线程,
只是置一个标志而已,WaitFor不会出错.
 
你的代码不出错是偶然的...Teminate方法的确是只设一个标志而已, 但是通常来说
Execute方法中要检测这个标志, 检测到就会退出, 如果你恰好推出了, 那么WaitFor
就会出错, 所以, 我如果用WaitFor方法, 一般都把FreeOnTerminate:= False;
然后
在WaitFor后面手工释放,如TestThread.Free;
 

这里我们不必讨论TThread的问题,我现在最关心的问题是:
如果一个DLL在加载运行后创建了一个线程(TThread或直接用CreateThread),而这个线程是
"死循环"执行的,如何保证进程中止时(可能没有调用FreeLibrary来卸载这个DLL)系统不会
出错?
举例:
1.New Application: Project1.exe
增加Button1和Button2,相关代码:
var
Form1: TForm1;
hLib: Cardinal;
......
procedure TForm1.Button1Click(Sender: TObject);
begin
if hLib = 0 then
LoadLibrary('project2.dll');
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
if hLib > 0 then
FreeLibrary(hLib);
end;

编译好project1.exe代用.
2.New DLL: project2.dll
New Form
也增加Button1和Button2,相关代码如下:
type
TTestThread = class(TThread)
protected
procedure Execute;
override;
end;
......
var
Form1: TForm1;
TestThread: TTestThread;
......
procedure TTestThread.Execute;
begin
FreeOnTerminate := True;//不是必须
while not Terminateddo
begin
//做些什么...
Sleep(0);
end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
if not Assigned(TestThread) then
TestThread := TTestThread.Create(False);
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
if Assigned(TestThread) then
bein
TestThread.Terminate;
TestThread.WaitFor;
TestThread := nil;
end;

end;

initialization
Form1 := TForm1.Create(Application);
Form1.Show;
finalization
if Assigned(TestThread) then
bein
TestThread.Terminate;
TestThread.WaitFor;
TestThread := nil;
end;

if Assigned(Form1) then
begin
Form1.Free;
Form1 := nil;
end;

end.

运行project1.exe后按Button1,装入project2.dll后再按project2.dll中的窗口上的Button1
创建线程,然后再按主程序窗口的Button2后再关闭窗口,程序正常退出;
同上,只是在关闭主程序窗口前不按Button2,则会出现问题!
 
to wolaixue:
这个容易,就你最后的例子:
如果是因为关闭主进程时没有调用Button2里面的代码:
if hLib > 0 then
FreeLibrary(hLib);
造成的话,应该把代码写到TForm或者Application的析构函数里面去_fastcall ~TForm1( )或_fastcall virtual ~TApplication(void);
保证代码被执行。
或者能不能吧终止线程的代码写到DLL的End Point里,SDK上所说ExitProcess会调用End Point的,使得进程可以“干净的”退出。
不过它有这样一句话:2.All of the threads in the process terminate their execution.
这样死循环的线程可能就跑不到这里了:while not Terminateddo


 
to spidertong:
if hLib > 0 then
FreeLibrary(hLib);得不到执行是我故意这样设置的,目的就是要测试不执行这段代
码和执行这段代码结果有何不同.其实完全可以放在finalization部分,只要是正常退出程
序,这段代码肯定能执行(除非在这段代码执行前调用了ExitProcess等).
我关心的问题是在没有调用这段代码的情况下,DLL中创建的线程怎么办?
中止线程的代码放在End Point里和放在finalization部分效果应该是一样的啊!
我发现,用FreeLibray卸载DLL时DLL还有机会处理多线程,而在ExitProcess时,好象只能
执行一个主线程了(LoadLibrary时DLL的入口函数也只能运行一个线程),无法用我的代码保
证附属线程的中止,只能等系统去中止这个线程,可是这样一来系统却要出错.我分析出错的
原因可能是ExitProcess执行过程中,DLL所创建的线程得到CPU时间准备执行却定位到不能
访问的内存地址了(DLL已被卸载).
要是能有办法在ExitProcess之前执行中止线程的代码就好了,也许有人会想到用HOOKAPI
的办法,但假设EXE不是自己写的,而这个EXE是经过压缩加密了的呢(引入表被加密过)?

 
一、关于我的经验
我用D6开发了一个多线程的串口DLL,运行于Win2k和Win98下,一切正常!
经过我的测试表明,不管使用显式释放,还是故意忽略(当然,这样端口
会一直占用直到应用程序退出为止),都不会出现什么错误提示、资源
漏洞等等的任何问题。
二、关于多线程
在开发多线程的时候,因为实在太容易了(至少表面看起来是这样的),
让很多人容易忽略一些至关重要的问题:代码的执行时刻!
以下是典型的例子:
TestThread.Terminate;
TestThread.WaitFor;
咋一看,没有问题呀?严格上说,应该是某些场合下没有问题!
如果设置 TestThread.FreeOnTerminate := True的话,问题
就可能出现:因为可能在执行TestThread.WaitFor之前,
TestThread.Free已经被调用了!这是就会出问题。
不怕麻烦的解决方法:
为了安全起见,不要怕麻烦,令FreeOnTerminate := False,
然后:
TestThread.Terminate;
TestThread.WaitFor;
// 因为对象不会自动释放,所以安全!
TestThread.Free;
省却麻烦的办法:
如果坚持:TestThread.FreeOnTerminate := True
那么:
TestThread.Terminate;
Sleep(0);
// 让Windows分配时间让线程释放!
至于是否必要增加 TestThread := nil,那就见仁见智了。
 
To gztomash:
太好了,你有这方面的经验!
急于想知道,我上面举的例子(project1.exe和project2.dll)你能调通吗?
至于
TestThread.Terminate;
TestThread.WaitFor;
在TestThread.FreeOnTerminate := True时的问题先放一边.我最关心的问题不是
这个,因为我试过直接用API不用TThread类存在一样的问题.
 
我觉得你应该好好看看我上次的贴子,里面提及的内容相当重要!
也是基于我上次的理论,稍微修改了一下你的程序,在D6 + SP2 + Win2kSP3
下调试通过!
// Project1.exe
var
hLib: THandle;
procedure TForm1.Button1Click(Sender: TObject);
begin
if hLib = 0 then
hLib := LoadLibrary('Project2.dll');
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
if hLib <> 0 then
if FreeLibrary(hLib) then
hLib := 0;
end;

//*****************************************************************
// Project2.dll
type
TForm1 = class(TForm)
Button2: TButton;
Button1: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

TTestThread = class(TThread)
protected
procedure Execute;
override;
end;

var
Form1: TForm1;
TestThread: TTestThread;
implementation
{$R *.dfm}
{ TTestThread }
procedure TTestThread.Execute;
begin
inherited;
FreeOnTerminate := True;
while not Terminateddo
Sleep(0);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
if not Assigned(TestThread) then
TestThread := TTestThread.Create(False);
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
if Assigned(TestThread) then
begin
TestThread.Terminate;
Sleep(0);
TestThread := nil;
end;
end;

initialization
Form1 := TForm1.Create(Application);
Form1.Show;
finalization
if Assigned(TestThread) then
begin
TestThread.Terminate;
Sleep(0);
TestThread := nil;
end;
if Assigned(Form1) then
begin
Form1.Free;
Form1 := nil;
end;
 
To gztomash:
刚刚测试,问题依旧!不信你按Project1.exe的Button1后按Project2.dll的Button1再
直接关闭Project1.exe(不按Button2)!
我的系统是win98,d6sp2
 
to :wolaixue 就你上次的回复
这里我倒有个疑问,finalization是Delphi的关键字还是什么?
End Point的效果和他一样吗,有没有试过End Point的效果
还有:再把project2.dll写得简洁些,我有点怀疑不是他生成的线程的问题.
为什么这样说呢,因为如果上面你给出的代码如果是完整的话,线程除了他本身并没有申请其他的资源,
如果出错是因为finalization 没有执行导致线程没有关闭的话,那么还有一个严重的问题就是在DLL里面创建的窗体也没有被释放.
他所申请的系统资源比哪个简单的现成还要多,起码申请了一个消息队列(应该在主线程中),虽然他处于进程的地址空间内(这里有点不肯定),
但是应该是进程还是DLL来释放呢?
建议New DLL: project2.dll 写成这样试试
initialization
TestThread1 := TTestThread.Create(False);
finalization
if Assigned(TestThread) then
bein
TestThread.Terminate;
TestThread.WaitFor;
TestThread := nil;
end;
 
呵呵!Win2k下面确确实实,实实在在是没有问题的啦!
我没用Win9x好长一段时间了,没办法测试了……
 
to gztomash:
2000下没问题!可9x下有问题,问题尚未解决!
to spidertong:
DELPHI的DLL中UNIT的initialization部分在DLL加载时执行,finalization部分
在卸载时执行,这是没问题的!DLLProc倒象是有问题.
 
在结束窗口用编delay来等待结束线程,我就是这样做的,没问题
 
没有试过没有窗体的DLL?
 
哦,对了,DLL创建的Fom1可以由Application代为释放
试试用WaitForSingleObject等待线程释放
 
后退
顶部