A software I am working on recently, some of which need to be completed by calling other software, but that software only has executable files and no source code at all. Fortunately, what I want to do is not difficult, it only needs to be started after my program is started. , open the software, set some text on one of the text mines when needed, and click a button.
Speaking of this, I believe you have some preliminary ideas for this function. Yes, the basic idea is:
1) Call CreatePRocess() to open the target program.
2) Use FindWindow() to find the window handle of the target program.
3) Find the Handle of the text box and the MessageID of the button, set the text using the SendMessage() method, and trigger the event.
OK, this is indeed very simple, but when I implemented it, I found that the result of this was: when my program started and opened the target program, its Splash window and the main window will be displayed, even if When I use FindWindow() to find the main window Handle, and call SendMessage (WindowHandle, SW_HIDE) to hide the window, the main window will be displayed for a moment. This effect is really the most perfect thing I can't bear to see .
So how to solve this problem? First of all, of course I am looking for a method on CreateProcess(). Unfortunately, it only has one parameter to set the default display method of the window, but once the window resets the display method itself, it has no effect. . . . Continue to search for the document. At this time, I saw a parameter of CreateProcess(), TStartupInfo, which has a property like lpDesktop. According to MSDN, if the pointer is NULL, the newly created Process will be started on the current Desktop, and if it is assigned to it After a Desktop name, Process will be launched on the specified Desktop. Well, it seems good, so start with it:
1) First, create a virtual Desktop.
const
DesktopName = 'MYDESK';
FDesktop:=CreateDesktop(DesktopName,nil,nil,0,GENERIC_ALL,nil);
Multiple Desktops can be created in Windows, and SwitchDesktop() can be used to switch which Desktop is displayed. There have been programs that simulate Windows into Linux and can switch between multiple virtual Desktops. In fact, that kind of program is also used. Windows itself is implemented by virtual Desktop function. In addition, the Windows startup screen and screensaver screen are also implemented by virtual Desktop. Okay, I won’t introduce this aspect. If you are interested, you can check it in MSDN for more details. material:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dllproc/base/enumdesktops.asp
2) When CreateProcess, specify the program to run on my newly generated Desktop:
var
StartInfo:TStartupInfo;
FillChar(StartInfo, sizeof(StartInfo), 0);
StartInfo.cb:=sizeof(StartInfo);
StartInfo.lpDesktop:=PChar(DesktopName); //Specify the name of Desktop
StartInfo.wShowWindow:=SW_HIDE;
StartInfo.dwFlags:=STARTF_USESHOWWINDOW;
StartInfo.hStdError:=0;
StartInfo.hStdInput:=0;
StartInfo.hStdOutput:=0;
if not CreateProcess(PChar(FileName),nil,nil,nil,nil,true,CREATE_NEW_CONSOLE+HIGH_PRIORITY_CLASS,nil,PChar(ExtractFilePath(FilePath)),StartInfo,FProceInfo) then begin
MessageBox(application.Handle,'Error when init voice (5).',PChar(Application.Title),MB_ICONWARNING);
exit;
end;
3) Use FindWindow to find the main window of the program
At first I wrote down the code like this:
for I:=0 to 60 do begin //wait 30 seconds for open the main window
WindowHandle:=FindWindow(nil,'WindowCaption');
if WindowHandle<>0 then begin
break;
end;
Sleep(500);
end;
However, practice has proved that this way, the Window that is not in the current Desktop cannot be found. What should I do:
The answer is that you can use the SetThreadDesktop() function, which can set the Desktop where the Thread works, so I added another sentence before the above code:
if not SetThreadDesktop(FDesktop) then begin
exit;
end;
However, after the program is run, the function returns false, indicating that the method call failed. After looking at MSDN carefully, I found that there is a sentence:
The SetThreadDesktop function will fail if the calling thread has any windows or hooks on its current desktop (unless the hDesktop parameter is a handle to the current desktop).
Oh, it turns out that there is no UI thing in the thread that needs to switch Desktop, but I called this method in the main thread of the program, and of course it will fail. It will be easy to know this, I only need to use one Isn't it OK to bind the "clean" thread to the new Desktop, and then use the FindWindow() method to find the WindowHandle I'm looking for? So, this step requires the help of a thread. The code is as follows:
TFindWindowThread = class(TThread)
Private
FDesktop:THandle;
FWindowHandle:THandle;
protected
procedure Execute();override;
public
constructor Create(ACreateSuspended:Boolean;const ADesktop:THandle);reintroduce;
property WindowHandle:THandle read FWindowHandle;
end;
{ TFindWindowThread }
procedure TFindWindowThread.Execute();
var
I:Integer;
Begin
//make the current thread find window on the new desktop!
if not SetThreadDesktop(FDesktop) then begin
exit;
end;
for I:=0 to 60 do begin //wait 30 seconds for open the main window
FWindowHandle:=FindWindow(nil,PChar('WindowCaption'));
if FWindowHandle<>0 then begin
break;
end;
Sleep(500);
end;
end;
constructor TFindWindowThread.Create(ACreateSuspended:Boolean;const ADesktop:THandle);
Begin
inherited Create(ACreateSuspended);
FDesktop:=ADesktop;
end;
And the code in the main program becomes like this:
FindWindowThread:=TFindWindowThread.Create(false,FDesktop);
try
FindWindowThread.WaitFor;
FMainWindowHandle:=FindWindowThread.WindowHandle;
Finally
FindWindowThread.Free;
end;
if FMainWindowHandle=0 then begin
MessageBox(Application.Handle,'Error when init voice (6).',PChar(Application.Title),MB_ICONWARNING);
exit;
end;
Haha, it’s successful, so you can find the window Handle smoothly.
4) Finally, use this main window Handle to find the EditBox Handle inside, as follows:
FEditWindow:=FindWindowEx(FMainWindowHandle,0,PChar('Edit'),nil);
I have specified the ClassName of this text box here, and this name can be obtained with Spy++.
The initialization work ends here. If it goes well, the program will be run in the background. So the function call is the same as usual:
if (FMainWindowHandle=0) or (FEditWindow=0) then begin
exit;
end;
SendMessage(FEditWindow,WM_SETTEXT,0,LongInt(@AText[1]));
SendMessage(FMainWindowHandle,WM_COMMAND,$8012,$0);
The number $8012 is also the resource ID obtained by using Spy++.
Finally, don't forget to close the program and release the virtual Desktop:
if FProceInfo.hProcess<>0 then begin
TerminateProcess(FProceInfo.hProcess,0);
end;
if FDesktop<>0 then begin
CloseDesktop(FDesktop);
end;
OK, this almost perfectly implements the function of a background call program, which will be completely transparent to the end customer, and the customer simply doesn't feel that there is another program working in the background. Isn't it very good? We can use many other people's programs directly (of course, we have to follow the copyright).
If you have any suggestions for improvement, or exchange, you can mail to: tonyki[at]citiz.net