We help IT Professionals succeed at work.

Delphi, How can a Component on ActiveX window determine when the user exits or closes window? (WM_CLOSE)

cyman73
cyman73 asked
on
309 Views
Last Modified: 2010-04-05
Hello all,

I have a ActiveX.ocx and is used with IE 6 or 7.  I want to delay the destruction/closing of the component/window if the component is busy (i.e. downloading).  

I tried
procedure TComponent.OnClose(var Msg:TMessage); message WM_CLOSE;
but it seems not to work.

I tried
procedure TComponent.OnClose(var Msg:TMessage); message WM_DESTROY;
but I am getting an access violation.

So I want to prevent the destruction of the component and window closing until the component is no longer busy.   Any ideas and examples would be greatly appreciated.

I am able to close the ActiveX window without any issues when the component is inactive, however, when I close it while it is active I get access violation errors.

Thank you.

 

Comment
Watch Question

Russell LibbySoftware Engineer, Advisory
CERTIFIED EXPERT
Top Expert 2005

Commented:

When the controlling client drops the last interface count, YOU are responsible for cleaning up your Com/ActiveX interface; be it an Ole Control or a simple automation object. This notification comes to you by way of the BeforeDestuction call in your object class (you will need to manually add it, and override it), which is your cue to perform any cleanup that you must do before returning. Once done, the last ref count is dropped and the base level (COM) class will perform the destruction of your window.

As far as delaying the call in the BeforeDestuction event, I would recommend against it. It would be better to cleanup as quickly as possible and return. What is it exactly that you are doing in the component (you mentioned downloading)? A little more info might elicit further suggestions regarding handling.

Russell

Author

Commented:
Thanks Russell, I did the BeforeDestruction method.  I was inheriting the Destroy Procedure and cleaning memory at that point.  Using BeforeDestruction takes place before WM_DESTROY and Destroy.  When I clean up the code, I also abort the Http, if Http.State <> httpReady.    

My problem is that I still have an access error after the ActiveX window is closed.  

I narrowed this issue to one procedure DISPLAYLAST.  When the procedure is executing, and the close is initiated, the access exception occurs.  If this particular procedure is not executing, then the Active X closing is fine.

So my question is if WS_CLOSE is initiated by the user while my procedure DISPLAYLAST is executing(where I either download images, and or display images to bitmaps...) and the Bitmaps are freed during code cleanup, then does the DISPLAYLAST procedure immediately exit or does it have to finish before the component is destroyed.  Does this make sense?

Thanks.
Russell LibbySoftware Engineer, Advisory
CERTIFIED EXPERT
Top Expert 2005

Commented:

But your missing one small thing.... you are not going to get a WM_CLOSE message, your hosting app is. Your only ties to the application are via COM.

As to the DisplayLast procedure, I can't say without looking at the code. If the code is processing something (async / or with message processing) then you run into the possibility of destroying the underpinnings before the procedure has a chance to complete. You should set up some handling so that you know (a) when the procedure starts (b) when the procedure finishes, (c) add some checking in the routine to allow you to cancel out and (d) a way to stall the destructor (eg peekmessage loop) while waiting for the procedure to finish.

again, with no code context to go from, it makes it difficult to say.

Russell


 

Author

Commented:
Thanks Russell,

I understand what you are saying about WM_CLOSE now.  So I am going to use the BeforeDestruction call.  Maybe my StopTimeout is not properly used to stall the destructor, see code below.

So here is what I am doing in the  BeforeDestruction call.  Since I am debugging ActiveX I am generating Log output to determine the line of execution. The log output looks like this at the end
***DisplayLast - Begin***
***DisplayLast - bCurrentlyUpdating = True
:
:
***DisplayLast - Http.GetAsync
***BeforeDestruction - Begin***
***BeforeDestruction - Http.State <> httpReady
***StopTimeout - Inteval valid and bCurrentlyUpdating=True
***StopTimeout - Inteval valid and bCurrentlyUpdating=True
***StopTimeout - Inteval valid and bCurrentlyUpdating=True
***StopTimeout - Inteval valid and bCurrentlyUpdating=True
***StopTimeout - Inteval valid and bCurrentlyUpdating=True
***StopTimeout - Inteval valid and bCurrentlyUpdating=True
***StopTimeout - Inteval valid and bCurrentlyUpdating=True
***StopTimeout - Inteval valid and bCurrentlyUpdating=True
This repeats until the TIMEOUT.
***FreeObjects - Begin***
***FreeObjects - End***
***BeforeDestruction - End***

Then I get the access violation after FreeObjects executes.  All procedures involved have try except statements for the entire procedures, so I should be seeing any of the exceptions in my log.

procedure TComponent.BeforeDestruction;
begin
  WriteLog('***BeforeDestruction - Begin***'); //-=>Debug
 
  // bStopRequested is checked thoughout DisplayLast to determine  (c) from above
  Stop;

  try
     if (Http.State <> httpReady) then
     begin
        WriteLog('***BeforeDestruction - Http.State <> httpReady'); //-=>Debug
        Http.Abort;
     end;
  except
     WriteLog('*Exception during Http.Abort');  //-=>Debug
  end;
   
  // Pause to make sure that the DisplayLast procedure has exited
  StopTimeout(TIMEOUT);

  // Clean Up
  FreeObjects;

  // Call inherited
  inherited BeforeDestruction;

  WriteLog('***BeforeDestruction - End***'); //-=>Debug
end;

procedure TComponent.Stop;
begin
  bStopRequested := True;
  bAllowUserInput := False;
end;

procedure TComponent.StopTimeout(Value: Longword);
var
lwStartTime, lwCurrentTime, lwInterval: Longword;

begin
  // Initialization
  lwInterval := 0;
  lwStartTime := GetTickCount;

  while lwInterval < Value
  do begin
     lwCurrentTime := GetTickCount;
     lwInterval := lwCurrentTime - lwStartTime;
     WriteLog('***StopTimeout - Inteval valid and bCurrentlyUpdating=True');
     Application.ProcessMessages;

     // bCurrentlyUpdating is set to false at the end of the DisplayLast procedure
     if not bCurrentlyUpdating then
     begin
        WriteLog('***StopTimeout - bCurrentlyUpdating=false***');
        Break;
     end;
  end;

end;

Russell LibbySoftware Engineer, Advisory
CERTIFIED EXPERT
Top Expert 2005

Commented:
>> Then I get the access violation after FreeObjects executes.  All procedures involved have try except statements for the entire procedures, so I should be seeing any of the exceptions in my log.

It sounds very much like a case where your code is attemping to access an object that you have destroyed, perhaps in the cleanup (or tail end) of the DisplayLast routine. Also, do you have callback events for the HTTP object (assuming its some wrapper around the HTTPRequest interface from MS).

You may want to :

- get list of your objects addresses after creation (as pointer types), that way when / if the access violation occurs, you can determine if the offending address matches one of the objects, or is something different.
- use FreeAndNil in your FreeObjects routine if your not already doing so
- use 'if Assigned({ObjectInstance} then' wrappers around your object access calls in any events (callbacks) and in the DisplayLast procedure.

I also noticed your log clip did not display:

'***StopTimeout - bCurrentlyUpdating=false***'

I am to understand that the routine is not finished yet?

---
Russell


Author

Commented:
That is what I am understanding also, the routine does not finish!!!!  How can this be?

I just tried commenting out the FreeObjects line and I still get the same results.  That is  "Access violation at address 0XXXXXXX in module Component.ocx.  Write of address 0XXXXXXX"

I need to try your suggestion of objects addresses after creation.  

Is there a way to create a dump in Delphi?  Actually is there a way to get the address of each line of code in my application?

Russell LibbySoftware Engineer, Advisory
CERTIFIED EXPERT
Top Expert 2005

Commented:
Yes, compile with linker options | map file set to detailed. This will create a map file that dumps the address for every line of code
 

Author

Commented:
I have made some progress in debugging the DisplayLast routine.  Apparently my WriteLog procedure did not have an error handler.  I included one and I now ShowMessages.  These messages allowed me to see that the DisplayLast routine was being executed after the  and I even see where the exception is taking place.

for instance the code in BeforeDestruction says

bStopRequested := True;  //Used to exit DisplayLast routine

the place in DisplayLast routine where I get an exception is below the Http.GetAsync

try
  if not bStopRequested then
    ShowMessage('bStopRequested=true');
except
  ShowMessage('Exception');
end;

Am I correctly assuming here that the component was already "destroyed" since I am getting an exception when I try to access one of the component's variables?

It seems that once the destructor is called, the execution sequence does not revert back to the component until the component is destroyed.  
How can I revert back to finish componen't routines before destruction?

Thanks.

Russell LibbySoftware Engineer, Advisory
CERTIFIED EXPERT
Top Expert 2005

Commented:

>> Am I correctly assuming here that the component was already "destroyed" since I am getting an exception when I try to access one of the component's variables?

Most likely, but that is an educated guess based on the results; can't say without seeing the code. So what does your DisplayLast routine look like? Do you keep yourself in a loop using something along the lines of  (psuedo code)

while not done do
{
  get / process messages
}

If so, you might take a look at using events from the Http object (you never mentioned what it is though), asssuming it exposes events. That way your not spinning in a loop that has the possibility of being destroyed before the routine finishes.

>> It seems that once the destructor is called, the execution sequence does not revert back to the component until the component is destroyed.  How can I revert back to finish componen't routines before destruction?

You cant control the sequence flow directly. But you should be breaking out of whatever it is that the DisplayLast is doing before letting the destructor finish up. Again, without knowing how your are processing / idling in this loop, I can't say.


Russell LibbySoftware Engineer, Advisory
CERTIFIED EXPERT
Top Expert 2005

Commented:
Btw, if your using Francois Piette's THTTPCli component to perform the async download, you could alternatively try my UrlDownload located @:

http://users.adelphia.net/~rllibby/downloads/urldown.zip

which would also let you handle URL downloads in an async manner (you can also queue multiple requests) and allows for timeouts. Just bind the event handlers and perform whatever you need to in the:

OnUrlComplete, OnUrlError, or OnUrlTimeout.

That way you are not spinning in the DisplayLast routine. You should also find that the destructor cleans up properly and should not raise any exceptions.

Just a suggestion,
Russell

Author

Commented:
Thanks Russell,

Yes that is the Http component I am using, F. Piette's THttpCli.  I have the HttpRequestDone procedure.  

I need to get to call Stop prior to the BeforeDestruct.  I also tried to override the Destructor by declaring my own inherited Destructor...

destructor Destroy; override;

destructor TComponent.Destroy;
begin
try
  WriteLog(' Destroy '); //-=>Debug  **This executes after the BeforeDestruction and before the resumption of the DisplayLast routine.
except
  WriteLog(' Destroy - Exception '); //-=>Debug
end;

  inherited;
              
end;


Display Last looks something like this.  I think I have to think outside the box on this one.  I need to learn more...


procedure TComponent.DisplayLast;
begin
  bCurrentlyUpdating := True;

  for i = 0 to noImages-1 do
  begin
    :
    bClientBusy := True;
    Http.GetAsync;
    ClientBusyTimeout(TIMEOUT);  //This loop is terminated by the Stop -> bClientBusy := False; or HttpRequestDone.

   // THEN THE DESTRUCTOR CONTINUES TO BE CALLED PRIOR TO CONTINUATION HERE

    if not bStopRequested then  // This is found thoughout the DisplayLast routine.  This is where I get exception in this line, after Destructor executes.
    begin
 
    end else
    begin
      Break;
    end;
  end;

  bCurrentlyUpdating := False;
end;

procedure TComponent.ClientBusyTimeout(Value: Longword);
var
lwStartTime, lwCurrentTime, lwInterval: Longword;

begin
// Initialization
lwInterval := 0;
lwStartTime := GetTickCount;

while lwInterval < Value
do begin
      lwCurrentTime := GetTickCount;
      lwInterval := lwCurrentTime - lwStartTime;
      Application.ProcessMessages;

      if not bClientBusy then
                    Break;
      end;

      bClientBusy := False;
end;

procedure TComponent.HttpRequestDone(Sender: TObject; RqType: THttpRequest; Error: Word);
begin
      bClient1Busy := False;
end;

procedure TComponent.BeforeDestruction;
begin
  WriteLog('***BeforeDestruction - Begin***'); //-=>Debug
 
  // bStopRequested is checked thoughout DisplayLast to determine  (c) from above
  Stop;

  try
     if (Http.State <> httpReady) then
     begin
        WriteLog('***BeforeDestruction - Http.State <> httpReady'); //-=>Debug
        Http.Abort;
     end;
  except
     WriteLog('*Exception during Http.Abort');  //-=>Debug
  end;
   
  // Pause to make sure that the DisplayLast procedure has exited
  StopTimeout(TIMEOUT);

  // Clean Up
  FreeObjects;

  // Call inherited
  inherited BeforeDestruction;

  WriteLog('***BeforeDestruction - End***'); //-=>Debug
end;

procedure TComponent.Stop;
begin
  bStopRequested := True;
  bAllowUserInput := False;
  bClientBusy := False  
end;

procedure TComponent.StopTimeout(Value: Longword);
var
lwStartTime, lwCurrentTime, lwInterval: Longword;

begin
  // Initialization
  lwInterval := 0;
  lwStartTime := GetTickCount;

  while lwInterval < Value
  do begin
     lwCurrentTime := GetTickCount;
     lwInterval := lwCurrentTime - lwStartTime;
     WriteLog('***StopTimeout - Interval valid and bCurrentlyUpdating=True');
     Application.ProcessMessages;

     // bCurrentlyUpdating is set to false at the end of the DisplayLast procedure
     if not bCurrentlyUpdating then
     begin
        WriteLog('***StopTimeout - bCurrentlyUpdating=false***');
        Break;
     end;
  end;

end;


Software Engineer, Advisory
CERTIFIED EXPERT
Top Expert 2005
Commented:
This one is on us!
(Get your first solution completely free - no credit card required)
UNLOCK SOLUTION

Author

Commented:
Thank you for your help.  
bClient1Busy was a typeo in my question, they are the same inthe code.



Russell LibbySoftware Engineer, Advisory
CERTIFIED EXPERT
Top Expert 2005

Commented:
You didn't have to close this before reaching a resolution, btw. If you need further help on this please post here. If you want, I can also provide a demo that async downloads a bunch of images to the Ax window for display. Might give you some ideas to go from.

Russell
Russell LibbySoftware Engineer, Advisory
CERTIFIED EXPERT
Top Expert 2005

Commented:
Example code:

http://users.adelphia.net/~rllibby/downloads/axdownload.zip

require url downloaded component (already gave you link)


Russell
Unlock the solution to this question.
Join our community and discover your potential

Experts Exchange is the only place where you can interact directly with leading experts in the technology field. Become a member today and access the collective knowledge of thousands of technology experts.

*This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

OR

Please enter a first name

Please enter a last name

8+ characters (letters, numbers, and a symbol)

By clicking, you agree to the Terms of Use and Privacy Policy.