对象的释放技术(难)(200分)

D

dcba

Unregistered / Unconfirmed
GUEST, unregistred user!
对象生成以后如果不用了,释放掉可以节省系统资源,有时也是必需的。要释放一个对象,只要我们调用他的free方法就行了。但是有时我们想在某个对象的某个事件发生后将其释放掉,比如有一个button,现在想在他的onclick事件产生后将button释放掉。
这样的程序可以这样写
procedure tform1.Button1Click(Sender: TObject);
begin
freeandnil(Button1);
end;
程序完全正常运行
但是下面的这样的程序为什么就会出错呢?该怎样改呢?我早窗体上只放了一个button
unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, OleServer, SHDocVw_TLB, StdCtrls;

type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
wb:TWebBrowser;
procedure OnBeforeNavigate (ASender: TObject
const pDisp: IDispatch;
var URL: OleVariant;var Flags: OleVariant;var TargetFrameName: OleVariant;
var PostData: OleVariant;var Headers: OleVariant;var Cancel: WordBool);
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
wb:TWebBrowser;
begin
wb:=TWebBrowser.Create(nil);
TWinControl(Wb).Parent:=self;
wb.OnBeforeNavigate2:=self.OnBeforeNavigate;
wb.Navigate('etang.com');
end;

procedure TForm1.OnBeforeNavigate(ASender: TObject
const pDisp: IDispatch;
var URL, Flags, TargetFrameName, PostData, Headers: OleVariant;
var Cancel: WordBool);
begin
cancel:=true;
Freeandnil(Asender);
end;

end.
 
当然会出错了,因为在OnBeforeNavigate2事件触发以后,WebBrowser还要执行其他代码的
执行其他代码的时候要访问WebBrowser自身,而你在OnBeforeNavigate2就把WebBrowser给
Free掉了,不报错都难。 [:)]
 

Freeandnil(Asender);
改成

Freeandnil(button1);
 
难道就没有办法释放掉了么?我在程序里使用了cancel:=true;
我的目的就是只要能自动释放掉webbrowser,就行了
 
我要释放的是webbrowser,不是button1
 
我把程序改成这样就不会有错。窗体上多加了一个edit1//没有用
unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, OleServer, SHDocVw_TLB, StdCtrls, ExtCtrls,OleCtrls,ActiveX;

type
TForm1 = class(TForm)
Button1: TButton;
Notebook1: TNotebook;
Edit1: TEdit;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
//wb:TWebBrowser;
procedure OnBeforeNavigate2 (
Sender: TObject
const pDisp: IDispatch
var URL, Flags, TargetFrameName,
PostData, Headers: OleVariant
var Cancel: WordBool);
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
wb:TWebBrowser;
p:pointer;
i:integer;
j:real;
begin
p:=self.Notebook1.pages.objects[0];
wb:=TWebBrowser.Create(p);
TWinControl(Wb).Parent:=TwinControl(p);
[blue]wb.Navigate('www.etang.com');[/blue]
[red]j:=1.25895;
for i:=1 to 99 do
j:=j*1/3.33154*3.33155;
Edit1.text:=inttostr(round(j));
[/red]
[blue][black]wb.OnBeforeNavigate2:=self.OnBeforeNavigate2;[/black][/blue]
end;

procedure TForm1.OnBeforeNavigate2(
Sender: TObject
const pDisp: IDispatch
var URL, Flags, TargetFrameName,
PostData, Headers: OleVariant
var Cancel: WordBool);
begin
cancel:=true;
with sender as TWebBrowser do
begin
Freeandnil(sender);
end;
end;
end.

我发现只要让webbrowser先navigate,一小会时间(几毫秒),然后再触发onnavigate事件,在free
就不会出错。这是为什么?
大家不信试试。
电极button1后,现在窗体上出现一个白的webbrower,然后马上就没了。
但是我又发现一个问题,就是在点一下button,还是可以运行,但是这样点下去,对点了几次就会有发生错误
这是为什么?
 
据我追踪vcl源码发现
try
try
WindowProc(Message);
finally
FreeDeviceContexts;
FreeMemoryContexts;
end;
except
Application.HandleException(Self);//在此捕捉到错误
end;
可以肯定你这样释放是行不通的,
明显发现WindowProc消息循环在你free后还要对此对象进行操作(这可能和delphi的
具体的对TWebBrowser控件的封装有很大关系,不象button那么简单,TWebBrowser
在触发完那个消息后还会触发一个消息(也许被delphi封装了,或者根本就是com自己干得)
即是说他还InvokeEvent了另一个 事件)
而这个Event是delphi调用的还能搞定,如果是ole内部的机制那就难了。。老兄
建议别钻牛角尖了。。
 
就是我所说的InvokeEvent了另一个 事件已经处理完毕了
 
那为什么button1点击以两次还是没事的呢?
我的初衷是当用户点击了某个连接时,如果弹出新窗口,因为我写的代码会在webbrowser.onnewwindow2事件里
产生一个新的webbrowser,用他去连接到新的地址(URL),但是我想判断如果是下载类型的连接
即会下载一个文件,这样就删除新产生的webbrowser,然后用其他的控件来下载。这样可以实现么?
或者说,如何再webbrowser的onnewwindow2事件里得知即将连接到的URL呢?
 
在《DELPHI 4 入门与精通》(估计5,6也一样)提到:

设置一个全局变量,如Last(TComponent),设置Last为webbrowser,然后用个定时器注销它。

 
procedure TForm1.Timer1Timer(Sender: TObject);
begin
// if there is an object to destroy
if Assigned (ToDestroy) then //这里用的是ToDestroy
begin
// moves the input focus to the next control
SelectNext (ToDestroy, True, true);
// destroy the object
ToDestroy.Free;
// no more objects to destroy
ToDestroy := nil;
end;
// update the form caption
Caption := Format ('CountObj: %d custom buttons',
[TCountButton.GetTotal]);
end;
 
每一个组件在被注销时都会调用Notification,你只需重载Notification,在其中将URL传递出去。
 
自定义一个消息,在 OnBeforeNavigate 中 Post 此消息,在消息处理中释放他:

const
WM_FREENAVIGATER = WM_USER + 120;

type
TForm1 = class(TForm)
....
private
procedure WMFreeNavigater(var Msg: TMessage)
message WM_FREENAVIGATER;
....

procedure TForm1.WMFreeNavigater(var Msg: TMessage);
begin
FreeAndNil(TWebBrowse(Msg.lParam));
end;

procedure TForm1.OnBeforeNavigate(ASender: TObject
const pDisp: IDispatch;
var URL, Flags, TargetFrameName, PostData, Headers: OleVariant;
var Cancel: WordBool);
begin
cancel:=true;
// Freeandnil(Asender);
PostMessage(Handle, WM_FREENAVIGATER, 0, LongWord(wb));
end;

因为 PostMessage 仅仅把消息放入消息队列,立刻返回,所以这样做应该没有问题。

 
>>beta:
你这个方法在书中也提到了,Cantu还是厉害啊。
 
wk_knife: 哪本书?俺也去借来瞅瞅。

 
你用的这一版的delphi对于TWebBrowser的自动封装有点小问题,
TWebBrowser是从TWinControl继承下来的(如果我没记错的话是这样,
而且我手头没有DELPHI,如果下面有点小出入,你照着我说的这个
想法去修改代码,尝试一下就可以了),当你的浏览器窗口收到
WM_CLOSE消息之后,它会自动关闭并释放掉,这时这个WinControl就
会出错,你应该在TWebBrowser的消息循环里面检测一下WM_CLOSE之类
的关闭窗口的消息,if Msg=WM_CLOSE then Free,应该就可以了。
 
问题用beta的方法解决了,妙兔无花的方法我试试看
谢谢各位
 
就在《DELPHI 4 入门与精通》是一行注释
 
顶部