Interaktion mit anderen Programmen

Ein anderes Programm aus der eigenen Anwendung starten

Sowohl in der Win16-API, als auch in der Win32-API existiert für den Aufruf anderer Programme die Funktion ShellExecute. Die Win16-API stellt dafür außerdem die Funktion WinExec bereit, deren Aufruf etwas einfacher ist, die aber nur ausführbare Programme starten kann und keine verknüpften Dokumente:


winexec('C:\Program.exe', SW_SHOWNORMAL);
Wenn man eine nicht ausführbare Datei mit WinExec öffnen möchte, muß man zuerst mit der Funktion "FindExecutable" die damit verknüpfte Anwendung ermitteln. Der folgende Funktion übergibt man den Namen einer Datei und erhält als Funktionsergebnis den Rückgabewert von WinExec:

function OpenFile(FileName:string):integer;
var FName,
    ExeName  : PChar;
begin
  ExeName:=StrAlloc(255);
  FName:=StrAlloc(255);
  StrPCopy(FName, FileName);
  if FindExecutable(FName, nil, ExeName)<32 then begin
    FileName:=StrPas(ExeName)+' '+StrPas(FName);
    StrPCopy(ExeName, FileName);
    Result:=WinExec(ExeName, SW_ShowNormal);
  end
  else
    Result:=0;
  StrDispose(FName);
  StrDispose(ExeName);
end;
WinExec existiert in der Win32-API zwar noch, sollte in 32Bit-Programmen aber nicht mehr benutzt werden, denn sie kann schon in der API der nächsten Windowsversion nicht mehr enthalten sein. Die flexiblere Alternative zu WinExec heißt ShellExecute, mit der man auch direkt mit einer Anwendung verknüpfte Dokumente öffnen..

ShellExecute(MainForm.Handle, 'open', <Programmdatei>,
             <Aufrufparameter>, '', SW_SHOWNORMAL);
..oder drucken kann:

ShellExecute(MainForm.Handle, 'print', <Dokumentdatei>, '', '', 
             SW_SHOWNORMAL);

- Mail und Internet-Client unter Win 95 aufrufen

Mail : Einfach als Programmaufruf "mailto:[email protected]" angeben, also:


ShellExecute(MainForm.Handle,
             'open',
             'mailto:[email protected]',
             nil,
             nil,
             SW_SHOWNORMAL);

{oder mit Betreff und Mailtext:}
ShellExecute(MainForm.Handle,
             'open',
             'mailto:[email protected]'+'?subject=Gruesse&body=Hallo Welt!',
             nil,
             nil,
             SW_SHOWNORMAL);
Wie man eine Mail mit Anhang verschickt, steht im Tips & Tricks-Abschnitt der FAQ.

Internet-Client:


var URL:string;
[...]
  URL:='http://www.pics-software.de';
  ShellExecute(MainForm.Handle,
               'open',
               PChar(URL),
               nil,
               nil,
               SW_SHOWNORMAL);
Die Funktion ShellExecute ist in der Unit "ShellAPI" deklariert. Diese muß also immer in die uses-Klausel der Unit eingebunden werden, in der die Funktion benutzt wird. Entgegen den Informationen in der WinAPI-Hilfe gibt ShellExecute(..) kein gültiges Handle der gestarteten Instanz zurück. Hier ein Artikel dazu aus dem MSDN:

ShellExecute returns a value greater than 32 if successful, or an error value that is less than or equal to 32 otherwise. The return value is cast as an HINSTANCE for backward compatibility with 16-bit Microsoft Windows applications. It is not a true HINSTANCE, however. The only thing that can be done with the returned HINSTANCE is to cast it to an integer and compare it with the value 32 or one of the error codes below. [MSDN April 1999]

- Start eines Programms und Warten auf dessen Ende

Die Funktion ExecAndWait funktioniert sowohl in Delphi1, als auch in allen 32Bit-Delphi-Versionen. Hier wird im 16Bit-Teil WinExec und GetModuleUsage und im 32Bit-Teil CreateProcess und WaitForSingleObject verwendet:


uses
  WinTypes, WinProcs, SysUtils;

{ WindowState is one of the SW_xxx constants.  
  Look up ShowWindow in the API help for a list.}
function ExecAndWait(const Filename, Params: string; 
                     WindowState: word): boolean;
{$IFDEF WIN32}
var
  SUInfo: TStartupInfo;
  ProcInfo: TProcessInformation;
  CmdLine: string;
begin
  { Enclose filename in quotes to take care of 
    long filenames with spaces. }
  CmdLine := '"' + Filename + '" ' + Params;
  FillChar(SUInfo, SizeOf(SUInfo), #0);
  with SUInfo do begin
    cb := SizeOf(SUInfo);
    dwFlags := STARTF_USESHOWWINDOW;
    wShowWindow := WindowState;
  end;
  Result := CreateProcess(NIL, PChar(CmdLine), NIL, NIL, FALSE, 
                          CREATE_NEW_CONSOLE or 
                          NORMAL_PRIORITY_CLASS, NIL, 
                          PChar(ExtractFilePath(Filename)), 
                          SUInfo, ProcInfo);
  { Wait for it to finish. }
  if Result then
    WaitForSingleObject(ProcInfo.hProcess, INFINITE);

{$ELSE}
var
  InstanceID : THandle;
  Buff: array[0..255] of char;
begin
  StrPCopy(Buff, Filename + ' ' + Params);
  InstanceID := WinExec(Buff, WindowState);
  if InstanceID < 32 then 
  { a value less than 32 indicates an Exec error }
    Result := FALSE
  else begin
    Result := TRUE;
    repeat
      Application.ProcessMessages;
    until Application.Terminated or 
          (GetModuleUsage(InstanceID) = 0);
  end;
{$ENDIF}
end;

Ein anderes Programm aus der eigenen Anwendung beenden

Ein Beispiel, wie man eine andere Anwendung aus dem eigenen Programm heraus beendet, findet man in der Unit CloseApp. Man braucht dazu das Instanzen-Handle der zu beendenden Anwendung.

Konsolen-Ausgaben aus DOS-Fenstern in eigenen Programmen einlesen

Mein Programm soll z.B. tasm32 aufrufen. Jetzt kann es sein, daß tasm32 Fehler in der Console anzeigt. Wie komme ich an die ran? Die Ausgabe in eine Datei umleiten wäre gut, funktioniert aber nicht, wenn ich das Umleitungszeichen mit als Parameter übergebe. Aber wie gehts ?

Bevor Du tasm32 startest, erzeuge einfach die Datei(en), in die die Ausgabe umgelenkt werden soll. (Das kann übrigens auch eine (Named)Pipe sein.) Diese(s) Handle(s) sollte(n) vererbbar sein.

Dann gibst Du in der StartupInfo Struktur beim CreateProcess diese(s) Handle an. Alle Handles, die nicht umgelenkt werden sollen, werden mit GetStdHandle() belegt. Dann noch in Flags USE_STD_HANDLES setzen und CreateProcess() aufrufen. Und ab geht die Post...

Informationen zur Benutzung von CreateProcess findet man unter dem Thema Ein anderes Programm aus der eigenen Anwendung starten.

Den Namen der Programmdatei (*.exe) einer Applikation aus einem Fensterhandle ermitteln

Kann mir vielleicht jemand sagen, wie ich zu einem Fensterhandle (oder dem entsprechenem Prozeß) den Dateinamen der ausgeführten EXE-Datei bekomme?

Leider gehen Windows 9x/2000 hier einen völlig anderen Weg als Windows NT 4.0. Erstgenannte verwenden dafür den "TProcessEntry32"-Typen aus der Unit "TlHelp32". Die Funktionen aus dieser Unit sind aber nur in der Kernel32-DLL von Windows 9x/2000 definiert. Hier ein Beispiel von Christian Kästner:


uses TlHelp32, ShellApi;

var
  ProcID    : DWord;
  SSHandle  : THandle;
  Continue  : boolean;
  ProcEntry : TProcessEntry32;
begin
  GetWindowThreadProcessID(FindWindow('Fenstertitel',nil), @ProcID);
  SSHandle := CreateToolhelp32Snapshot(TH32CS_SnapProcess, 0);
  ProcEntry.dwSize := Sizeof(ProcEntry);
  FileList.Items.Clear;
  // gehe alle Prozesse durch (m.H. der TlHelp32 - Unit)
  Continue := Process32First(SSHandle, ProcEntry);
  while Continue do begin
    // gesuchter Prozess (ProcID) in der Liste aller Prozesse gefunden?
    if ProcEntry.th32ProcessID = ProcID then
      FileList.Items.Add(ProcEntry.szExeFile);
    Continue := Process32Next(SSHandle, ProcEntry);
  end;
  CloseHandle(SSHandle);
end;
Unter Windos NT 4.0 verwendet man statt dessen die Funktion "GetModuleBaseName" aus der Library PSAPI.DLL. Diese Funktion ist wiederum unter Win9x/2000 nicht definiert, also funktioniert folgendes Beispiel von Christo Crause nur unter Windows NT 4.0:

interface

type
  TPIDlist = array[0..1000] of DWORD;

function EnumProcesses (pidList : PInteger; cb : Integer; var cbNeeded :
 Integer): boolean; stdcall;

function GetModuleBaseName (hProcess : THandle; module : HInst; BaseName :
 Pchar; size : Integer) : Integer; stdcall;

implementation

const psapidll = 'psapi.dll';

var PID : TPIDlist;

function EnumProcesses; external psapidll;
function GetModuleBaseName; external psapidll name 'GetModuleBaseNameA';

function GetProcessList(var PIDlist : TPIDlist): integer;
var cb, cbNeeded : integer;
begin
  cbNeeded := 0;
  cb := SizeOf(PIDlist);
  FillChar(PIDlist, cb, 0);
  if not EnumProcesses(@PIDlist, cb, cbNeeded) then 
    cbNeeded := 0
  else 
    cbNeeded := cbNeeded div SizeOf(DWord);
  Result := cbNeeded;
end;

procedure GetProcessNames;
var numProcesses : integer;
    ProcHandle   : THandle;
    ExeName      : string;
begin
  numProcesses := GetProcessList(PIDlist);
  for i := 0 to numProcesses-1 do begin
  ProcHandle := OpenProcess(PROCESS_QUERY_INFORMATION, False, PIDlist[i]);
  if ProcHandle <> 0 then begin
    try
      if GetModuleBaseName(ProcHandle, 0, @szName, sizeof (szName)) > 0 then
        ExeName := szName
      else
        ExeName := 'System';
    finally
      CloseHandle(ProcHandle)
    end;
  end
  else 
    if PIDlist[i] = 0 then 
      ExeName := 'System idle';
end;

Wie bekommt man Zugriff auf alle aktuell geöffneten Fenster?

Diese Unit demonstriert die API-Funktionen

Mit Hilfe dieser Funktionen wird eine Liste aller aktuell geöffneten Fenster und deren Handles, Prozess-IDs, Klassen, Titel, etc. erstellt. Auch die Funktion zur Ermittlung des mit dem Fenster verbundenen EXE-Namens wird demonstriert. Die API-Funktionen die Process32First und Process32Next sind nur unter W95 definiert, daher funktioniert die Unit so nicht unter Windows NT.

Sie können auch ein komplettes Beispielprojekt (4 kB) mit dieser Unit vom Server laden. Dafür habe ich lange in den Tiefen der API graben müssen und bin mächtig stolz auf das Ergebnis. Also bitte bei Weitergabe des Codes die Autoren angeben!

Den Anzeige-Zustand und die Position eines Fensters ermittelt man mit der API-Funktion GetWindowPlacement. Die folgende Funktion GetWindowState gibt den Anzeigezustand des Fensters mit dem Handle Wnd zurück:


function GetWindowState(Wnd:HWnd):integer;
var WPlacement : PWINDOWPLACEMENT;
begin
  GetMem(WPlacement,SizeOf(TWINDOWPLACEMENT));
  WPlacement^.Length:=SizeOf(TWINDOWPLACEMENT);
  if GetWindowPlacement(Wnd,WPlacement) then
    Result:=WPlacement^.showCmd
  else
    Result:=-1;
  FreeMem(WPlacement);
end;
mögliche Rückgabewerte: SW_HIDE, SW_MINIMIZE, SW_RESTORE, SW_SHOW, SW_SHOWMAXIMIZED, SW_SHOWMINIMIZED, SW_SHOWMINNOACTIVE, SW_SHOWNA, SW_SHOWNOACTIVATE oder SW_SHOWNORMAL (siehe auch in der API-Hilfe zur Funktion GetWindowPlacement).

Wie erhält man das Handle des Controls, das den Eingabefokus hat?

Für Fenster in einem Prozess des eigenen Programms hilft ein einfacher Aufruf der API-Funktion "GetFocus". Für Fenster in fremden Prozessen muß man zuerst eine Beziehung zwischen dem eigenen Prozess und dem Prozess, der das aktive Control enthält, herstellen. Das erledigt diese Funktion der man das Handle des aktiven Top-Level-Fensters übergeben muß. Das Handle des aktiven Top-Level-Fensters erhält man z.B. mit "GetForegroundWindow".


function GetFocussedWindow(ParentWnd:HWnd):HWnd;
var OtherThreadID,Buffer : DWord;
begin
  OtherThreadID:=GetWindowThreadProcessID(ParentWnd, @Buffer);
  if AttachThreadInput(GetCurrentThreadID, OtherThreadID, true) then begin
    Result:=GetFocus;
    AttachThreadInput(GetCurrentThreadID, OtherThreadID, false);
  end
  else
    Result:=0;
end;

Wie kann man einen Tastendruck an ein anderes Fenster schicken?

Dazu benötigt man das Handle des Fensters und schickt an dieses dann zwei Nachrichten: WM_KeyDown und WM_KeyUp. Im folgenden Beispiel wird im Fenster "Form1" das Menü mit der Taste F10 aktiviert:


var  W : HWnd;

begin
   W := FindWindow(NIL,'Form1'); //"Form1" heißt hier das Fenster mit dem Menü
   if W <> 0 then
   begin
     PostMessage(W, wm_KeyDown, vk_F10,0);   //Simuliert F10
     PostMessage(W, wm_KeyUp, vk_F10,0);
   end;
 ...
end;
Eine andere Alternative ist die systemweite Versendung von Tastaturereignissen mit Keybd_Event. Mit dieser Funktion kann man auch Tastenkombinationen verschicken. Im folgenden Beispiel von Ralf Imhäuser ist es die Tastenkombination [Shift-Tab]:

  Keybd_Event(vk_Shift,0,0,0);
  Keybd_Event(vk_Tab,0,0,0);
  Keybd_Event(vk_Tab,0,KEYEVENTF_KEYUP,0);
  Keybd_Event(vk_Shift,0,KEYEVENTF_KEYUP,0);
Wie man an die Handles aller Fenster kommt, erfährt man im Thema Wie bekommt man Zugriff auf alle aktuell geöffneten Fenster?.