Seite 1 von 2

Nachricht an ein Windows Programm schicken. [erledigt]

Verfasst: Di, 02. Sep 2014 15:50
von satmax
Ich versuche mich gearde an der WIndows API und möchte dazu eine Nachricht an ein anderes Windows Programm schicken.

Ob das Programm läuft und das ProzessHandle habe ich (aus Forumsbeispielen):

Code: Alles auswählen

Function GetProzessHandle(cExe, nPidId)
Local aProcesses  := space(10240)
Local aModules    := space(4*10)
Local cbNeeded    := 0
Local cbNeeded2   := 0
LOcal cname       := space(64)
Local nPid
Local nProcHandle
Local i,nMax
Local xRet 

EnumProcesses( @aProcesses, LEN(aProcesses) , @cbNeeded )
nMax := len(trim(aProcesses))/4

for i:= 1 to nMax
	nPid := Bin2U(substr(aProcesses,1+((i-1)*4),4))
		if nPidId==NIL .or. (nPIdId==nPid)
			nProcHandle  := OpenProcess( PROCESS_QUERY_INFORMATION + PROCESS_VM_READ ,0, nPid )
				if nProcHandle > 0
					if  EnumProcessModules( nProcHandle, @aModules,len(aModules)/4, @cbNeeded2) != 0
						cName := space(128)
						GetModuleBaseNameA( nProcHandle,bin2u(substr(aModules,1,4)),@cName,len(cName))
						cName := strtran(cname,chr(0),"")
						
						if upper(trim(cName)) == upper(cExe)
							RETURN ( nProcHandle )
						ELSE
							CloseHandle(nProcHandle)	
						endif
						
					endif
				endif
		endif
next
RETURN NIL
wie schicke ich nun eine Nachricht an dieses Programm?

Re: Nachricht an ein Windows Programm schicken.

Verfasst: Di, 02. Sep 2014 16:20
von brandelh
Ich denke die API funktion SendMessage() ist dafür vorgesehen.

Re: Nachricht an ein Windows Programm schicken.

Verfasst: Di, 02. Sep 2014 17:20
von AUGE_OHR
satmax hat geschrieben:Ich versuche mich gearde an der WIndows API und möchte dazu eine Nachricht an ein anderes Windows Programm schicken.
um ein "Fenster" zu finden würde ich "FindWindowA" benutzten.
wenn es sich um ein Control "innerhalb" eines Fenster ist dann "FindWindowExA"

die Frage ist "was" du den anderen Windows Programm schicken willst ?

Re: Nachricht an ein Windows Programm schicken.

Verfasst: Di, 02. Sep 2014 17:36
von satmax
Ich möchte ein 2. Programm (C++) auffordern eine bestimmte Aktion auszuführen, in etwa so

sendMessage(hWin,WM_USER_4096, cString)

WM_USER_4096 bekomme ich vom Programmierer des anderen Programmes, cString beinhaltet einen String mit meinen Daten < 20 Zeichen.


Bei mir haperst bei der ganzen Windows API Geschichte.... GetProzessHandle() funktioniert mal. Einen eindeutigen programmname.exe habe ich, der Fenstertitel ist immer unterschiedlich.


Nun hätte ich versucht, mit

DLLFUNCTION SendMessage(nProcHandle,nUInt , lWparam, nlParam ) USING STDCALL FROM USER32.DLL

SendMessage() einzubinden. das passt aber so nicht.

------------------------------------------------------------------------------
oError:args :
-> VALTYPE: N VALUE: 1993539584
-> VALTYPE: N VALUE: 32
-> VALTYPE: C VALUE: SendMessage
-> VALTYPE: N VALUE: 1624
-> VALTYPE: N VALUE: 1
-> VALTYPE: L VALUE: .T.
-> VALTYPE: N VALUE: 50
oError:canDefault : J
oError:canRetry : N
oError:canSubstitute: N
oError:cargo : NIL
oError:description : Unbekannte Funktion
oError:filename :
oError:genCode : 21
oError:operation : dllPrepareCall
oError:osCode : 0
oError:severity : 2
oError:subCode : 2002
oError:subSystem : BASE

Re: Nachricht an ein Windows Programm schicken.

Verfasst: Di, 02. Sep 2014 19:32
von brandelh
Vermutlich wieder die ANSI / UNICODE Sache, also SendMessageA() ...

Re: Nachricht an ein Windows Programm schicken.

Verfasst: Di, 02. Sep 2014 19:45
von satmax
Super, danke Hubert. Bin ich wieder ein Stück weiter und weiß jetzt auch warum es so viele Funktionen mit Suffix 'A' gibt. :)

Stimmt das so im Groben:

Code: Alles auswählen

nProcHandle:=GetProzessHandle("satmax5.exe", nPidId)
nUInt   := 63333
wParam := 0
lParam := 0
@user32:SendMessageA(nProcHandle,nUInt , wParam, lParam )
nUint ist mir noch halbwegs klar, aber wParam und lParam? Wie kann ich da einen String übergeben?

Re: Nachricht an ein Windows Programm schicken.

Verfasst: Di, 02. Sep 2014 19:59
von satmax
ich denke:

wParam sollte ein Word sein,
lParam ein long.

In meinem Fall will ich z.B: "9999999.9999" übergeben. Da könnte ich doch

Code: Alles auswählen

wParam := 9999       // maximal 65.535	
lParam := 9999999    // maximal 4.294.967.295
daraus machen. Am anderen Ende setzt sich der Programmierer das wieder zusammen?

Re: Nachricht an ein Windows Programm schicken.

Verfasst: Di, 02. Sep 2014 20:24
von satmax
Offensichtlich habe ich die Prozess ID brauche aber das Fenster Handle der anderen Applikation. Kann auch über die Prozess ID auf ein Fenster Handle kommen? Oder komme ich über den Filenamen auf ein Fenster Handle? Fenstertitel scheidet aus, der wechselt.

Re: Nachricht an ein Windows Programm schicken.

Verfasst: Di, 02. Sep 2014 20:39
von Rudolf
Hallo, ich verwende dazu OT4XB, anbei ein Beispiel wie ich mit verschiedenen Fenstern kommuniziere, vielleicht hilft das.
Grüße
Rudolf

Code: Alles auswählen

#define MY_WINDOW_NAME "FormCommander"

function SendToServer( nCmd , cText )
******************************************************************
local cds := Array(3)
local result
static hWnd := 0

DEFAULT cText to "Test"
DEFAULT nCmd  to 999

if !@user32:["__bo__hw"]:IsWindow( hWnd )
   hWnd := 0
end
if Empty(hWnd)
   hWnd := @user32:FindWindowExA(-3,0,"_OT4XB_GENERIC_WINDOW_",MY_WINDOW_NAME)
end
if Empty(hWnd)
   return NIL
end

cds[1] := nCmd
cds[2] := Len(cText)
cds[3] := _xgrab(cText)
result := @user32:SendMessageA(hWnd,WM_COPYDATA ,0,cds)
_xfree(cds[3])
return result

function ReceiverThread()
******************************************************************
local pMsg
pMsg  := _xgrab(32)
hWndReceiver := @user32:CreateWindowExA( 0,"_OT4XB_GENERIC_WINDOW_",MY_GUID,0,0,0,0,0,-3,0,AppInstance(),0)
ot4xb_subclasswindow( hWndReceiver , ReceiverHandler() )
while @user32:GetMessageA(pMsg,0,0,0) != 0
   @user32:DispatchMessageA(pMsg)
end
_xfree( pMsg)
return NIL


CLASS ReceiverHandler
******************************************************************
EXPORTED:
INLINE CLASS METHOD wndproc(hWnd,nMsg,wp,lp)
local cds,cStr,cStatus := ""
if nMsg == WM_COPYDATA
     //altd()
   cds  := PeekDWord(lp,,3)
   cStr := PeekStr(cds[3],,cds[2])
   @user32:ReplyMessage(1) // free the sender
   if oFCSys:lStatDebug
     dcqdebug "Command:" , cds[1]
     dcqdebug "String:"  , cStr
   endif
   do case
     case "!SHOW" $ var2char(cStr)
          formcommander()
     case "!COMSTOP" $ var2char(cStr)
          STOP_COMM
     case "!COMSTART" $ var2char(cStr)
          RESTART_COMM
     case "!STATUS" $ var2char(cStr)
          bu2_action()
     case "!READ" $ var2char(cStr)
          oFCSys:cAction := "R"
     case "!RECONFIG" $ var2char(cStr)
          reconfig()
     case "!CHECK" $ var2char(cStr)
          toastmsg("Interprocess communication ok")
     case "!QUIT" $ var2char(cStr)
          cStatus := ""
          for x := 1 to 1000
                if !oFCSysThread:lIsRunning
                    cStatus := " terminated ok"
                    exit
                endif
                sleep(10)
          next x
          prot("quit from SysMon at " + timestampc() ,"system.log")
          if empty(cStatus)
               prot("   timeout waiting end reading ! ","system.log")
          endif
          _endapp()
   endcase
   return 1
elseif nMsg == WM_CLOSE
   @user32:DestroyWindow(hWnd)
   return 0
elseif nMsg == WM_NCDESTROY
   @user32:PostQuitMessage(0)
   return NIL
end
return NIL
// ---------------------------------------------------------------------------
ENDCLASS


Re: Nachricht an ein Windows Programm schicken.

Verfasst: Di, 02. Sep 2014 20:59
von AUGE_OHR
satmax hat geschrieben:Offensichtlich habe ich die Prozess ID brauche aber das Fenster Handle der anderen Applikation. Kann auch über die Prozess ID auf ein Fenster Handle kommen? Oder komme ich über den Filenamen auf ein Fenster Handle? Fenstertitel scheidet aus, der wechselt.
vermutlich hast du jetzt ein "Fenster" und musst an ein "inneres" Control ran.
siehe dir mal den Thread mit den CALC.EXE Rechner an http://www.xbaseforum.de/viewtopic.php?f=27&t=4422

man will ja in dem Fall das Control was das Ergebnis anzeigt. Das findest du mit WinID http://www.xbaseforum.de/viewtopic.php? ... ilit=fritz
Damit erhält du nun den Class Name des Controls welches du mit "FindWindowExA" suchen musst um per SendMessageA() was zu machen.

Re: Nachricht an ein Windows Programm schicken.

Verfasst: Mi, 03. Sep 2014 16:57
von satmax
Danke für dein Beispiel Rudolf, aber irgendwie ist das alles nicht das was ich suche...

Jimmy, nein, ich habe da kein Fenster, das ist das Process Handle, bis hierher klappt es auch. Nur, wie komme ich vom Profess Handle zu einem Windows Handle?

Alle Beispiele die ich bisher gefunden habe suchen das Fensterhandle über die Titelbar, das geht aber in meinem Fall nicht. Der Titel beginnt zwar immer gleich, endet aber immer verschieden.
Oder kann ich auch nach den linken x Zeichen im Fenstertitel suchen?

Zusammengefasst: den Prozess finde ich über den Filenamen. Nun habe ich das Processhandle, wie komme ich nun auf das Fensterhandle?

Re: Nachricht an ein Windows Programm schicken.

Verfasst: Mi, 03. Sep 2014 18:05
von DelUser01
Hallo Markus

Zusatz am Rande:
wenn Du nur den Programmnamen der zu steuernden EXE hast müsstest Du auch dafür sorgen, dass die EXE nur 1x gestartet ist.
Sonst bräuchtest Du z.B. noch die Startzeit oder so als weiteres Unterscheidungskriterium.
Oder sollen alle entsprechenden EXEs das Signal bekommen?

Damit solltest Du eigentlich den benötigten Handle für die ProcessID bekommen:

Code: Alles auswählen

DLLFUNCTION OpenProcess( dwDesiredAccess , bInheritHandle  , dwProcessId ) ; 
Using STDCALL From KERNEL32.DLL

Function GsApiProcGetHwndFromPid( nPID )
Local nHWND := OpenProcess( PROCESS_QUERY_INFORMATION + PROCESS_VM_READ , .F. , nPID )
Return( nHWND )
Gruß
Roland

Re: Nachricht an ein Windows Programm schicken.

Verfasst: Mi, 03. Sep 2014 18:30
von satmax
Hallo Roland,

Code: Alles auswählen

OpenProcess( PROCESS_QUERY_INFORMATION + PROCESS_VM_READ , .F. , nPID )
verwende ich schon (siehe erstes Post). Laut MSDN:

>> If the function succeeds, the return value is an open handle to the specified process.

Und das ist eben kein Fensterhandle, oder?

Ich kann dieses Handle zB. verwenden um den Prozess zu beenden:

Code: Alles auswählen

TerminateProcess(nProcHandle,1)
Mir ist es aber nicht gelungen über dieses Handle eine Nachricht an den Prozess zu senden:

Code: Alles auswählen

	nProcHandle:=GetProzessHandle(cFile, nPidId)  // Liefert Handle oder 0
	IF nProcHandle == 0 
		MsgBox(" Programm nicht gestartet")
        ELSE
	        BringWindowToTop(nProcHandle)
        	SetForegroundWindow(nProcHandle)
   
	ENDIF


Das Programm wird dabei nicht in den Vordergrund gebracht.

Re: Nachricht an ein Windows Programm schicken.

Verfasst: Mi, 03. Sep 2014 22:04
von AUGE_OHR
satmax hat geschrieben:

Code: Alles auswählen

Function GetProzessHandle(cExe, nPidId)
...
   nProcHandle  := OpenProcess( PROCESS_QUERY_INFORMATION + PROCESS_VM_READ ,0, nPid )
ist der 2nd Parameter nicht logisch (.F. / .T. ) ?
http://msdn.microsoft.com/en-us/library ... 85%29.aspx

Re: Nachricht an ein Windows Programm schicken.

Verfasst: Do, 04. Sep 2014 11:32
von satmax
Danke an alle die mir geholfen haben, die Lösung:

Man braucht ot4xb & XppCbk (ohne Pablo geht nichts... :wink: )
Quelle: http://www.xbwin.com/

Dann noch das Beispiel WildFindWindow()
Quelle: http://news.xbwin.com/newsgroups.php?ar ... ticle_id=5
Beispiel enthalten.


Zuletz, SendMessageA() wollte nicht, dafür klappt es mit PostMessageA()

Code: Alles auswählen

local aWnd := WildFindWindow( "Mein Programm mit dem Titel..." )
...
PostMessageA(aWnd[1][1],1090,10,10000)
...

Schon kommen beim anderen Programm als user message 1090 die Werte 10 und 10000 an.

Re: Nachricht an ein Windows Programm schicken. [erledigt]

Verfasst: Fr, 05. Sep 2014 15:40
von brandelh
und wo verwendest du XppCbk ?

PS: ich kenne dies bei einer Funktion, die direkte Windows Events im Xbase++ Programm empfangen soll.

Re: Nachricht an ein Windows Programm schicken. [erledigt]

Verfasst: Fr, 05. Sep 2014 21:10
von satmax
xppcb ist ein kleiner Scriptkompiler der aus einem simplen Skript eine Callbackfunktion, ein Objekt File, erstellt.

Einfach das Beispiel ansehen. :wink:

Re: Nachricht an ein Windows Programm schicken. [erledigt]

Verfasst: Fr, 05. Sep 2014 23:43
von DelUser01
Hallo Markus

trotz [erledigt]-Status muss ich auch noch was dazu beitragen.
Habe das mit dem Abfragen nach dem Programmnamen (outlook.exe) und/oder Fenster-Namen ("Microsoft Outlook") auch mal gelöst und zwar so:

Code: Alles auswählen

//----------
DLLFUNCTION EnumWindows( cAddrOfUserFunc , uUserVar  ) ;
            Using STDCALL ;
            From USER32.DLL
DLLFUNCTION GetWindowTextA( nWindowHandle , @cTitel , nTitelMax ) ;
            Using STDCALL ;
            From USER32.DLL
DLLFUNCTION GetWindowThreadProcessId( nWindowHandle , @lpdwProcessId ) ;
            Using STDCALL ;
            From USER32.DLL
DLLFUNCTION OpenProcess( dwDesiredAccess , bInheritHandle  , dwProcessId     ) ;
            Using STDCALL ;
            From KERNEL32.DLL
//----------
Function GsApiFindProcTitle( cTitleRef )
Local nHWND := GsApiFindProcess( NIL , NIL , cTitleRef )
Return( nHWND )
//----------
Function GsApiFindProcExe( cExeRef )
Local nHWND := GsApiFindProcess( NIL , cExeRef , NIL )
Return( nHWND )
//----------
Function GsApiFindProcess( bCheck , cExeRef , cTitleRef )
Privat bFind
Privat nHWND := 0
If bCheck == NIL
   bCheck := { | nHWND , cWinTitle | GsApiFindProcSub2( nHWND , cWinTitle , cExeRef , cTitleRef ) }
EndIf
MEMVAR->bFind := bCheck
EnumWindows( BaCallBack( "GsApiFindProcSub1" , BA_CB_GENERIC2 ) , 0 )
Return( MEMVAR->nHWND )
//----------
Function GsApiFindProcSub1( nHandle , uPar )
Local nBuf
Local nMax := 100
Local cBuf := Space( nMax )   // Replicate( Chr( 32 ) , nMax )
Local nRet := 1
nBuf := GetWindowTextA( nHandle , @cBuf , nMax )
If nBuf > 0
   If EVal( MEMVAR->bFind , nHandle , Left( cBuf , nBuf ) )
      nRet := 0
      MEMVAR->nHWND := nHandle
   EndIf
EndIf
Return( nRet )
//----------
Function GsApiFindProcSub2( nHWND , cWinTitle , cExeRef , cTitleRef )
Local nPID   := 0
Local lMatch := .F.
If cTitleRef <> NIL .And. ! Empty( cTitleRef )
   If Upper( AllTrim( cTitleRef ) ) == Upper( AllTrim( cWinTitle ) ) 
      lMatch := .T.
   EndIf
ElseIf cExeRef <> NIL .And. ! Empty( cExeRef )
   GetWindowThreadProcessId( nHWND , @nPID )
   cProcName := GsApiProcGetProcName( nPID , .F. )
   If Upper( AllTrim( cProcName ) ) == Upper( AllTrim( cExeRef ) )
      lMatch := .T.
   EndIf
EndIf
Return( lMatch )
//----------
Function GsApiProcGetProcName( nPID , lFullPath )
Local nHWND     := 0
Local nHMod     := 0
Local nCb       := 0
Local nProc     := 100
Local cProc     := Space( nProc )
Local cProcName := ""
If lFullPath == NIL
   lFullPath := .F.
EndIf
nHWND := GsApiProcGetHwndFromPid( nPID )
nRet  := DllCall( "PSAPI.DLL" , DLL_STDCALL , "EnumProcessModules" , nHWND , @nHMod , 4 , @nCb )
If lFullPath
   nRet := DllCall( "PSAPI.DLL" , DLL_STDCALL , "GetModuleFileNameExA" , nHWND , nHMod , @cProc , nProc )   // kompletter Pfadname z.B. "c:\programme\sfirm\sfmain.exe"
Else
   nRet := DllCall( "PSAPI.DLL" , DLL_STDCALL , "GetModuleBaseNameA"   , nHWND , nHMod , @cProc , nProc )   // Filename + Erweiterung z.B. "sfmain.exe"
EndIf
cProcName := Left( cProc , nRet )
CloseHandle( nHWND )
Return( cProcName )
//----------
Function GsApiProcGetHwndFromPid( nPID )
Local nHWND := OpenProcess( PROCESS_QUERY_INFORMATION + PROCESS_VM_READ , .F. , nPID )
Return( nHWND )
//----------
BAP wird benötigt,
wenn es nicht geht habe ich bestimmt eine Function vergessen, dann einfach "hupen"... 8)
(vielleicht habe ich auch zu viel Code reinkopiert).

TESTPROGRAMM:

Code: Alles auswählen

Procedure main
Local nHWND
nHWND := GsApiFindProcTitle( "Microsoft Outlook" )
MsgBox( Var2Char( nHWND ) )
nHWND := GsApiFindProcExe( "outlook.exe" )
MsgBox( Var2Char( nHWND ) )
Return

Re: Nachricht an ein Windows Programm schicken. [erledigt]

Verfasst: Fr, 05. Sep 2014 23:49
von satmax
Hallo Roland,

da hast du aber einen exakten Titel, oder? Ich habe nur die linken 20 Zeichen, der restliche Teil variiert immer.

Re: Nachricht an ein Windows Programm schicken. [erledigt]

Verfasst: Fr, 05. Sep 2014 23:53
von DelUser01
Hallo Markus

ist doch ganz einfach:
aus der Zeile

Code: Alles auswählen

If Upper( AllTrim( cTitleRef ) ) == Upper( AllTrim( cWinTitle ) )
machst Du

Code: Alles auswählen

If Upper( AllTrim( cTitleRef ) ) == Left( Upper( AllTrim( cWinTitle ) ) , 20 )
Am einfachsten Du machst die Abfrage per Block dann ist das ganz flexibel.

Re: Nachricht an ein Windows Programm schicken. [erledigt]

Verfasst: Fr, 05. Sep 2014 23:57
von satmax
was ist BAP?

Re: Nachricht an ein Windows Programm schicken. [erledigt]

Verfasst: Sa, 06. Sep 2014 0:04
von DelUser01
Also statt dem Aufruf

Code: Alles auswählen

nHWND := GsApiFindProcTitle( cTitleRef )
nimmst Du

Code: Alles auswählen

nHWND := GsApiFindProcess( bCheck , cExeRef , cTitleRef )
und machst eine eigene Funktion:

Code: Alles auswählen

Function MartinMatch( nHWND , cWinTitle , cExeRef , cTitleRef )
Local nPID   := 0
Local lMatch := .F.
If cTitleRef <> NIL .And. ! Empty( cTitleRef )
   If Upper( AllTrim( cTitleRef ) ) == Left( Upper( AllTrim( cWinTitle ) ) , 20 )
      lMatch := .T.
   EndIf
EndIf
Return( lMatch )
TESTPROGRAMM:

Code: Alles auswählen

Procedure main
Local nHWND
Local bCheck
bCheck := { | nHWND , cWinTitle | MartinMatch( nHWND , cWinTitle , cExeRef , cTitleRef ) }
nHWND := GsApiFindProcess( bCheck , NIL , "Window-Titel 1234567" )
MsgBox( Var2Char( nHWND ) )
Return
Und das mit der Länge 20 lässt sich auch noch flexibel einbauen - usw. usw. usw.

Re: Nachricht an ein Windows Programm schicken. [erledigt]

Verfasst: Sa, 06. Sep 2014 0:05
von DelUser01
Hallo Martin

BAP => Binary Access Package

Ist zumindest in der Professional Subscription dabei

Re: Nachricht an ein Windows Programm schicken. [erledigt]

Verfasst: Sa, 06. Sep 2014 0:18
von satmax
Danke, hab's gefunden. Der Punkt ist eben die Callback Funktion, ohne der kein EnumWindows(), ohne der keine Teilstringsuche... Und mit pur Xbase++ gibt es. eben keine Callback Funktion. Unterm Strich sind Deine und meine Lösung ähnlich aufgebaut. Ist aber immer interessant was alles geht. :D

Re: Nachricht an ein Windows Programm schicken. [erledigt]

Verfasst: Sa, 06. Sep 2014 0:20
von DelUser01
Hallo Markus

Du hast schon recht - es führen eben viele Wege nach Rom.
Ich benütze eben nur die Microsoft APIs und nicht noch etwa anderes "undurchschaubares".