Solved

Spawn Application and then Send WMCopyMessage to the LAST spawned application

Posted on 2006-06-19
13
329 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
Live: Real-Time Solutions, Start Here

Receive instant 1:1 support from technology experts, using our real-time conversation and whiteboard interface. Your first 5 minutes are always free.

 

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
 
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

Gigs: Get Your Project Delivered by an Expert

Select from freelancers specializing in everything from database administration to programming, who have proven themselves as experts in their field. Hire the best, collaborate easily, pay securely and get projects done right.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

Title # Comments Views Activity
Virtuailstring tree compare node issue 14 116
Create a path if not exists 7 77
firemonkey keyboard covers the controls 1 25
Tvertscrollbox like a whatsapp layout 5 27
This article explains how to create forms/units independent of other forms/units object names in a delphi project. Have you ever created a form for user input in a Delphi project and then had the need to have that same form in a other Delphi proj…
Hello everybody This Article will show you how to validate number with TEdit control, What's the TEdit control? TEdit is a standard Windows edit control on a form, it allows to user to write, read and copy/paste single line of text. Usua…
Email security requires an ever evolving service that stays up to date with counter-evolving threats. The Email Laundry perform Research and Development to ensure their email security service evolves faster than cyber criminals. We apply our Threat…
Nobody understands Phishing better than an anti-spam company. That’s why we are providing Phishing Awareness Training to our customers. According to a report by Verizon, only 3% of targeted users report malicious emails to management. With compan…

776 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