Solved

Spawn Application and then Send WMCopyMessage to the LAST spawned application

Posted on 2006-06-19
13
324 Views
Last Modified: 2010-04-05
Hi Experts,

First let me give some background.

I have a
1. main application and a
2. seperate application (download manager for main application).

The application is a web downloader.
In the main application the user adds all the URL's he wants to download.
When he clicks the download button, the main application ShellExecute the "Download Manager" and
sent the URLS to the Download Manager via SendMessage(), WM_COPYDATA.
To get a handle to the spawned Download Manager I use FindWindowEX().

But the problem that I get is.
When a Download Manager is already running and I spawn another one I need the FindWindowEX() function to return the handle of the VERY LAST Download Manager I spawned.

Is there a way to fix this?
Please advice.
I would also like to know how safe is this process?
0
Comment
Question by:Marius0188
  • 8
  • 5
13 Comments
 
LVL 17

Expert Comment

by:TheRealLoki
ID: 16937693
ShellExecute gives you the handle, so you can don't need the FindWIndowEx()
TheHandle:=ShellExecute(Application.Handle,'Open',PChar(SExec),PChar(Sparam),PChar(SDir),SW_SHOW);

You could also spawn the download manager with CreateProcess, instead of ShellExecute, which also gives you the handle.
0
 
LVL 17

Expert Comment

by:TheRealLoki
ID: 16937708
1 other possibility is that you could pass the URL as a parameter to your downloadmanager, so you would not need to send the wm_copydata at all.
e.g.
MyDownloadManager.exe "http://someserver/somefile.zip"
0
 

Author Comment

by:Marius0188
ID: 16937826
I must admit most of the time this approach is working 100%.
When spawning the download manager and using FindWindowEx() to get a handle to the download manager main form
it gets the last one.

But it happen once and only then I have been made aware of this problem which I believe
can be great problem in future.
0
 

Author Comment

by:Marius0188
ID: 16937999
I now remember why I did not use the handle returned by ShellExecute().
It do spawn the download manager but the SendMessage does nothing then.

The handle returned by ShellExecute() does not seem to change when spawning multiple DManagers, as well.

This is my code as is which is working except for the one possible problem

-------------------------------------------------------CODE BEGIN-----------------------------------------------------------
var
  aCopy :TCopyDataStruct;
  hTarget :HWND;
  sData :String;
begin
    sData := 'TESTING STRING TO SENT';
    ShellExecute(Handle, 'open', PChar(GetAppPath + Download Manager\download_manager.exe'), nil, nil, SW_SHOWNORMAL);

    Application.ProcessMessages;
    Sleep(200);

    With aCopy do
    begin
      dwData := 0;
      cbData := Length(PChar(sData)) + 1;
      lpData := PChar(sData);
    end;

    hTarget := FindWindowEx(0, 0, nil, 'Download Manager');
    If hTarget <> 0 then
      SendMessage(hTarget, WM_COPYDATA, LongInt(0), LongInt(@aCopy))
    Else MessageDLG('Unable to find Download Manager!', mtError, [mbOk], 0);
end;

--------------------------------------------------------CODE END------------------------------------------------------------


BUT When using this code based on your suggestion it do spawn the DManager like mentioned but the SendMessage() seem to do nothing then.

This is the code is used

--------------------------------------------------------CODE BEGIN----------------------------------------------------------
var
  aCopy :TCopyDataStruct;
  hTarget :HWND;
  sData :String;
begin
    sData := 'TESTING STRING TO SENT';
    hTarget := ShellExecute(Handle, 'open', PChar(GetAppPath + 'Download Manager\download_manager.exe'), nil, nil, SW_SHOWNORMAL);

    Application.ProcessMessages;
    Sleep(200);

    With aCopy do
    begin
      dwData := 0;
      cbData := Length(PChar(sData)) + 1;
      lpData := PChar(sData);
    end;

    If hTarget <> 0 then
      SendMessage(hTarget, WM_COPYDATA, LongInt(0), LongInt(@aCopy))
    Else MessageDLG('Unable to find Download Manager!', mtError, [mbOk], 0);
end;

--------------------------------------------------------CODE END-------------------------------------------------------------

Ok advice will be appreciated.

0
 
LVL 17

Expert Comment

by:TheRealLoki
ID: 16938333
I think your problem is that the form isn't created yet when you try to find it. You may need a delay after "spawning" your download manager, to wait until the new form has been created.
eg.
sleep(2000); instead of 200
try it out, see if it fixes it. if it does, then you can find a nicer way to do it (like putting the "wm_copydata in a thread that waits until the form is ready, or times out after 10 seconds - worst case)

If you like, I can show you how to use CreateProcess, the Process ID, and EnumWindows to determine the "download manager'" form, but I don't think you'll need it if it is just a timing thing (as above)
0
 

Author Comment

by:Marius0188
ID: 16938535
I did sleep(2000) still not working.

What is funny to is that the Handle returned by ShellExecute() is always = 42?

Is this correct?
Should this handle changes as new apps get spawned?
0
Maximize Your Threat Intelligence Reporting

Reporting is one of the most important and least talked about aspects of a world-class threat intelligence program. Here’s how to do it right.

 
LVL 17

Expert Comment

by:TheRealLoki
ID: 16938579
no, that's not the processid. I'm talking about using your original code, but just increasing the sleep() to see ifit solves the problem.
0
 
LVL 17

Accepted Solution

by:
TheRealLoki earned 250 total points
ID: 16938859
Here's an example I wrote for you on how to do this using CreateProcess, EnumWindows, and GetWindowThreadProcessId.
I also show how to send a record structure using the wm_copydata method.
I have a thread that tries (up to 5 seconds) to find the newly created process and form

This demo will spawn a copy of itself, and let you send edit.text to it

unit Unit1;

interface

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

const
  WM_FoundForm = WM_user + 100;

type
PCopyDataStruct = ^TCopyDataStruct;
 TCopyDataStruct = record
  dwData: LongInt;
  cbData: LongInt;
  lpData: Pointer;
 end;

type PMyRecord = ^TMyRecord;
     TMyRecord = record
                     data: string[128];
                 end;

type
  TWaitForWindowAndSendMessageThread = class(TThread)
  private
      procedure Execute; override;
  public
      ProcessID: THandle;
      FormHandle: HWnd;
      constructor Create(ProcessID_: THandle; OnComplete_: TNotifyEvent);
  end;

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    Edit1: TEdit;
    bCreateProcess: TButton;
    bSendURLToLastSpawned: TButton;
    bClose: TButton;
    bUseMyHandle: TButton;
    procedure bSendURLToLastSpawnedClick(Sender: TObject);
    procedure bCreateProcessClick(Sender: TObject);
    procedure bCloseClick(Sender: TObject);
    procedure bUseMyHandleClick(Sender: TObject);
  private
    { Private declarations }
    LastSpawnedProcessID: THandle;
    LastSpawnedWindow: HWnd;
  public
    { Public declarations }
    WorkerThread: TWaitForWindowAndSendMessageThread;
{This happens when the thread finds the form}
    Procedure MsgFoundForm(var Msg:TMessage); Message WM_FoundForm;
    procedure WMCOPYDATA( var Msg : TMessage ); message wm_copydata;
    procedure ThreadCompleted(Sender: TObject);
  end;

var
  Form1: TForm1;
  function ThreadEnumWindowsFunc(Handle: THandle; ProcessID: THandle): boolean; stdcall;

implementation

{$R *.dfm}

function ThreadEnumWindowsFunc(Handle, ProcessID: THandle): boolean;
    var
        Caption: array[0..256] of Char;
        wndPid: THandle;
    begin
        result := true;
        if GetWindowText(Handle, Caption, SizeOf(Caption)-1) <> 0 then
        begin
            if Caption = 'Form1' then
            begin // form's caption matches our expected caption
                GetWindowThreadProcessId(Handle, wndPid);
                if wndPid = ProcessID then
                begin // matches our process
                    PostMessage(Form1.Handle, WM_FoundForm, Integer(ProcessID), Integer(Handle));
                    Form1.LastSpawnedWindow := Handle;
                    result := false;
                end;
            end;
        end;
    end;

{ TForm1 }

procedure TForm1.MsgFoundForm(var Msg: TMessage);
    begin
        LastSpawnedProcessID := Msg.WParam;
        LastSpawnedWindow := Msg.LParam;
        memo1.Lines.Add('Last Spawned:  ProcessID = ' + IntToStr(LastSpawnedProcessID) +
          ' Window Handle = ' + IntToStr(LastSpawnedWindow));
        if assigned(WorkerThread) then WorkerThread.Terminate;
// Demo - Send it the text now
        bSendURLToLastSpawnedCLICK(nil);        
    end;

procedure TForm1.bSendURLToLastSpawnedClick(Sender: TObject);
    var
        aCopy :TCopyDataStruct;
        MyRecord: TMyRecord;
    begin
        MyRecord.data := Edit1.Text;
        With aCopy do
        begin
            dwData := 0;
            cbData := sizeof(MyRecord);
            lpData := @MyRecord;
        end;
        SendMessage(LastSpawnedWindow, WM_COPYDATA, self.Handle, LongInt(@aCopy));
    end;

procedure TForm1.bCreateProcessClick(Sender: TObject);
    var
        StartupInfo: TStartupInfo;
        ProcessInfo: TProcessInformation;
    begin
        FillChar(StartupInfo, SizeOf(TStartupInfo), 0);
        with StartupInfo do
        begin
            cb := SizeOf(TStartupInfo);
            dwFlags := STARTF_USESHOWWINDOW or STARTF_FORCEONFEEDBACK;
{ you could pass sw_show or sw_hide as parameter }
            wShowWindow := sw_show;
        end;
        if CreateProcess(pchar(Paramstr(0)),nil,nil, nil, False,
                  NORMAL_PRIORITY_CLASS, nil, nil,
                  StartupInfo, ProcessInfo) then
        begin
            WorkerThread := TWaitForWindowAndSendMessageThread.Create(ProcessInfo.dwProcessId, ThreadCompleted);
        end
        else
          Raise Exception.Create('CreateProcess error:'  + IntToStr(GetLastError))
    end;

procedure TForm1.bCloseClick(Sender: TObject);
    begin
        sendmessage(LastSpawnedWindow,wm_Close,0,0);
    end;

procedure TForm1.bUseMyHandleClick(Sender: TObject);
    begin
        memo1.lines.add(inttostr(self.handle));
        LastSpawnedWindow := self.handle;
    end;

procedure TForm1.ThreadCompleted(Sender: TObject);
    begin
        WorkerThread := nil;
    end;

procedure TForm1.WMCOPYDATA(var Msg: TMessage);
    var
        i: integer;
        pc: pchar;
    begin
      with PMyRecord(PCopyDataStruct(Msg.LParam)^.lpData)^ do
        memo1.Lines.add(Data);
    end;

{ TWaitForWindowAndSendMessageThread }

constructor TWaitForWindowAndSendMessageThread.Create(ProcessID_: THandle; OnComplete_: TNotifyEvent);
    begin
        inherited Create(True);
        ProcessID := ProcessID_;
        OnTerminate := OnComplete_;
        FreeOnTerminate := True;
        Resume;
    end;

procedure TWaitForWindowAndSendMessageThread.Execute;
    var
        iterations: integer;
    begin
        iterations := 0;
        while (not terminated) and (iterations < 100) do // 5 seconds (+ enum time)
        begin
            inc(iterations);
            EnumWindows(@ThreadEnumWindowsFunc, ProcessID);
            sleep(50);
        end;
    end;

end.
0
 
LVL 17

Expert Comment

by:TheRealLoki
ID: 16938873
i was going to put the actual sending (wm_copydata) inside the thread, but i ran out of time :-)
0
 

Author Comment

by:Marius0188
ID: 16953292
Thanks for the help so far.

In regards to my original code.
I have increased the sleep() as  suggested and it looks to work fine now.

Is the way I did it ok?
Can someone foresee any problem?
0
 
LVL 17

Expert Comment

by:TheRealLoki
ID: 16954002
as long as the system doesn't take an incredibly long time to spawn the application, it should be fine.
0
 

Author Comment

by:Marius0188
ID: 16954253
How can trap global exceptions / errors in the download manager.
Lets say the download manager fails for some reason.
I need to flag the URL's as "not downloaded yet".

What I did was to set a global variable:
ErrorOccurred :Boolean;
and on the TApplicationEvents.OnException() event I set the variable to True.
Then on the Main form's Destroy() event I check the variable and if True then
I mark the URL's as "not downloaded yet".

Does this sound fine to you?
Any better suggestions?
0
 
LVL 17

Expert Comment

by:TheRealLoki
ID: 16955029
if an exception is raised, you will have to manually close the form (unless you handle the exception)
butyes, that should work
0

Featured Post

Enabling OSINT in Activity Based Intelligence

Activity based intelligence (ABI) requires access to all available sources of data. Recorded Future allows analysts to observe structured data on the open, deep, and dark web.

Join & Write a Comment

Creating an auto free TStringList The TStringList is a basic and frequently used object in Delphi. On many occasions, you may want to create a temporary list, process some items in the list and be done with the list. In such cases, you have to…
Introduction I have seen many questions in this Delphi topic area where queries in threads are needed or suggested. I know bumped into a similar need. This article will address some of the concepts when dealing with a multithreaded delphi database…
Sending a Secure fax is easy with eFax Corporate (http://www.enterprise.efax.com). First, Just open a new email message.  In the To field, type your recipient's fax number @efaxsend.com. You can even send a secure international fax — just include t…
Here's a very brief overview of the methods PRTG Network Monitor (https://www.paessler.com/prtg) offers for monitoring bandwidth, to help you decide which methods you´d like to investigate in more detail.  The methods are covered in more detail in o…

708 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

16 Experts available now in Live!

Get 1:1 Help Now