|
|||||||
|
|
|
![]() |
|
|
Strumenti |
|
|
#1 |
|
Member
Iscritto dal: Jun 2006
Messaggi: 117
|
[c#/c++] Eseguire un'applicazione esterna dentro una form
Salve,
una premessa: non sto chiedendo come si esegue un programma esterno. Questo lo so già fare. Ho già guardato un sacco di post qui sul forum e tutti putroppo si riferiscono alla semplice esecuzione di programmi esterni. Dunque io ho la necessità di eseguire un'applicazione esterna all'interno di una mia form, per la precisione dentro un suo pannello. In altre parole vorrei emulare una sorta di applicazione MDI in cui però le finestre child della principale (la mia applicazione) non sono form da me create, ma applicazioni esterne eterogenee, sul cui codice non ho alcun controllo quindi! Ho guardato un sacco di forum sul web e la cosa è fattibile, ma putroppo tutti gli esempi funzionanti eseguono applicazioni di windows come notepad o la calcolatrice. Queste applicazioni funzionano anche a me nella maniera voluta e questo fa ben sperare. Putroppo però non sempre le cose funzionano. Ad esempio con eseguibili come iexplore.exe (Internet Explorer) o write.exe (Wordpad) succede che l'applicazione esterna viene lanciata ma non viene "inglobata" nella mia form. Tecnicamente quello che sul web indicano di fare è di usare alcune funzioni win-api dichiarandole esterne dentro il proprio codice "managed" in C# o C++ (o VB.NET). Ecco un esempio del mio codice FUNZIONANTE (notate che eseguo appunto notepad.exe) Codice:
// dichiarazione delle funzioni esterne win-api
public:
[DllImport("USER32.DLL")]
static int SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
static bool MoveWindow(IntPtr hwnd, int x, int y, int cx, int cy, bool repaint);
// setting del processo da lanciare
System::Diagnostics::ProcessStartInfo^ psi = gcnew System::Diagnostics::ProcessStartInfo();
psi->WindowStyle = System::Diagnostics::ProcessWindowStyle::Normal;
psi->FileName = "notepad.exe";
psi->Arguments = "";
psi->UseShellExecute = false;
System::Diagnostics::Process^ proc = System::Diagnostics::Process::Start(psi);
// il thread della mia applicazione attende INDEFINITAMENTE fino a quando l'applicazione
// esterna è stata inizializzata correttamente
bool wait = proc->WaitForInputIdle();
// imposto l'handle della finestra della nuova applicazione lanciata come child di
// un pannello della mia form
IntPtr gpsHandle = proc->MainWindowHandle;
SetParent(gpsHandle, panel3->Handle);
MoveWindow(gpsHandle, 0, 0, panel3->Width, panel3->Height, true);
this->panel3->ResumeLayout();
this->panel3->PerformLayout();
this->ResumeLayout();
this->PerformLayout();
Ma ecco un trucco strano. Basta sostituire la chiamata alla WaitForInputIdle() con una stupida sleep e tutto funziona. Il problema però è che io non posso sapere a priori quanto sia necessario attendere per una generica applicazione esterna da eseguire. Ho fatto delle prove e con una sleep di 300ms per ora funzionano tutte le applicazioni che ho provato. Con 100ms solo alcune... insomma è un casino! In definitiva sembra quasi che il processo con la nuova applicazione esterna venga lanciato. Poi però il chiamante (la mia form) non rimane bloccato sulla WaitForInputIdle per un lasso di tempo sufficiente affinché l'handle della finestra associata all'applicazione esterna sia stato correttamente inizializzato. Questo sembra essere erroneo però, perchè la WaitForInputIdle, in mancanza di parametri espliciti, attende il processo chiamato per un tempo indefinito. Invece nel mio caso ritorna sempre immediatamente con valore true, come se avesse rilevato che la finestra dell'applicazione esterna sia stata inizializzata correttamente... anche se poi l'handle rimane a 0! Ovviamente ho anche provato a chiamare WaitForInputIdle passando come parametro il tempo di attesa, ma anche con valori molto alti la funzione ritorna immediatamente, fregandosene altamente insomma! Potete aiutarmi? Qualcuno sa perchè questa procedura non fa il suo lavoro in alcune circostanze? Perchè con la sleep() le cose funzionano? Grazie |
|
|
|
|
|
#2 |
|
Member
Iscritto dal: Jun 2006
Messaggi: 117
|
Nessuno sa aiutarmi? Nessuno ha mai provato ad inglobare una finestra di un programma esterno in una propria form?
|
|
|
|
|
|
#3 | |
|
Senior Member
Iscritto dal: May 2004
Città: Londra (Torino)
Messaggi: 3692
|
Quote:
Ma in maniera ortodossa, usando gli oggetti COM e i controlli relativi. Per il tuo problema prova a passare delle API, ovvero da OpenProcess. Ti basta sapere il PID e l'handle dovresti prenderlo. Proseguo. Poi dato il pid passi ad EnumWindows(), La tua e' una delle finestre... qual e' la principale? Proverei a passare ogni finestra della enumwindows a GetWindowThreadProcessId(), e quando trovo il pid che coincide con quello di lancio, allora dovresti essere davanti alla main.
__________________
Se pensi che il tuo codice sia troppo complesso da capire senza commenti, e' segno che molto probabilmente il tuo codice e' semplicemente mal scritto. E se pensi di avere bisogno di un nuovo commento, significa che ti manca almeno un test. Ultima modifica di gugoXX : 16-12-2008 alle 01:18. |
|
|
|
|
|
|
#4 | ||
|
Member
Iscritto dal: Jun 2006
Messaggi: 117
|
Quote:
Quote:
Grazie mille |
||
|
|
|
|
|
#5 | |
|
Senior Member
Iscritto dal: Oct 2007
Città: Padova
Messaggi: 4131
|
Secondo me dovresti seguire i consigli di GugoXX.
Io posto solo per "toglierti il prurito" riguardo la faccenda della WaitForInputIdle che torna subito. E' bastata una ricerca di 5min. con Google per trovare questo: LINK estratto: Quote:
__________________
As long as you are basically literate in programming, you should be able to express any logical relationship you understand. If you don’t understand a logical relationship, you can use the attempt to program it as a means to learn about it. (Chris Crawford) |
|
|
|
|
|
|
#6 | |
|
Senior Member
Iscritto dal: Dec 2004
Messaggi: 3210
|
Quote:
Ho fatto una prova veloce ( VB 2008 ) e sono riuscito ad aprire nella mia Form ( che non è manco MDI, come invece consigliato da molti suggerimenti online ) tutti gli applicativi Office, notepad, wordpad, Internet Explorer, ecc... In soldoni : Codice:
Public Class Form1
Private Declare Auto Function SetParent Lib "user32" (ByVal hWndChild As IntPtr, ByVal hWndNewParent As IntPtr) As IntPtr
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim PSI As New ProcessStartInfo("iexplore.exe")
Dim P As Process = Process.Start(PSI)
Dim i As Integer = 0
For i = 0 To 1000
System.Threading.Thread.Sleep(10)
P.Refresh()
If P.MainWindowHandle <> IntPtr.Zero Then Exit For
Next i
SetParent(P.MainWindowHandle, Me.Handle)
End Sub
End Class
|
|
|
|
|
|
|
#7 |
|
Senior Member
Iscritto dal: Dec 2004
Messaggi: 3210
|
... E tradotto in C# :
Codice:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
[System.Runtime.InteropServices.DllImport("user32.dll")]
static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
private void button1_Click(object sender, EventArgs e)
{
ProcessStartInfo PSI = new ProcessStartInfo("iexplore.exe");
Process P = Process.Start (PSI);
for(int i = 0;i<1000;i++)
{
System.Threading.Thread.Sleep(10);
P.Refresh();
if(P.MainWindowHandle != IntPtr.Zero)
{
break;
}
}
SetParent(P.MainWindowHandle, this.Handle);
}
}
|
|
|
|
|
|
#8 |
|
Senior Member
Iscritto dal: Oct 2007
Città: Padova
Messaggi: 4131
|
__________________
As long as you are basically literate in programming, you should be able to express any logical relationship you understand. If you don’t understand a logical relationship, you can use the attempt to program it as a means to learn about it. (Chris Crawford) |
|
|
|
|
|
#9 | |||
|
Member
Iscritto dal: Jun 2006
Messaggi: 117
|
Quote:
Allora... Quote:
ProcessStartInfo PSI = new ProcessStartInfo("myApp.exe"); Process P = Process.Start (PSI); P->WaitForInputIdle() No?! Quote:
Perchè quindi se chiamo sta benedetta WaitForInputIdle() sul nuovo processo creato per l'applicazione esterna, il chiamante, cioè la form principale, non sta lì fermo e buono ad aspettare? |
|||
|
|
|
|
|
#10 |
|
Senior Member
Iscritto dal: May 2004
Città: Londra (Torino)
Messaggi: 3692
|
Ogni processo potrebbe avere piu' di una finestra.
Il problema e' che per qualche applicazione il processo principale (spesso l'unico) coincide con una finestra, che coincide anche con la finestra principale (Notepad). Per altre applicazione il processo principale, anche se unico, non coincide con la finestra principale. Per I.E. ho idea che il processo principale si tratti di un Loader, che si occupa di istanziare e lanciare i thread delle finestre, una delle quali sara' la principale per l'applicazione, ma verra' istanziata, aperta e sara' pronta solo in un secondo tempo. La WaitForInputIdle, cosi' come la MainWindowHandle del C# fanno parecchie cose sotto la coperta, richiamando parecchie API, in un modo simile a quello che ho provato ad esporre sopra. Sta di fatto che quando richiarmi WaitForInputIdle il processo principale non ha ancora avuto tempo di creare le finestre (almeno la principale), e quindi non si tratta ancora di un processo con una finestra principale. Per questo motivo ricade ancora nei "processi senza finestra", e ritorna subito. D'altronde il C# non puo' sapere che prima o poi quel processo avra' una finestra principale (non lo sa nemmeno quel processo stesso...) e quindi non stara' ad aspettare senza sapere se mai dovra' ritornare. Se invece aspetti un po' di tempo, il processo avra' avuto modo di creare tutto il necessario, e C# si trovera' aggiornate variabili come MainWindowHandle (che appunto sotto la coperta fa parecchie cose). E anche WaitForInputHandle funzionera' come atteso, dato che i 2 campi sono fra loro in relazione.
__________________
Se pensi che il tuo codice sia troppo complesso da capire senza commenti, e' segno che molto probabilmente il tuo codice e' semplicemente mal scritto. E se pensi di avere bisogno di un nuovo commento, significa che ti manca almeno un test. |
|
|
|
|
|
#11 |
|
Senior Member
Iscritto dal: Oct 2007
Città: Padova
Messaggi: 4131
|
Grande GugoXX, grazie della spiegazione!
Ragazzi, siete dei pozzi di scienza infusa
__________________
As long as you are basically literate in programming, you should be able to express any logical relationship you understand. If you don’t understand a logical relationship, you can use the attempt to program it as a means to learn about it. (Chris Crawford) |
|
|
|
|
|
#12 |
|
Member
Iscritto dal: Jun 2006
Messaggi: 117
|
Grazie mille GugoXX della spiegazione. Quindi a questo punto non c'è modo di essere sicuri che un'applicazione abbia creato la finestra principale. Dunque sembra che l'unico approccio certo è l'utilizzo di una sleep() prima della lettura dell'handle MainWindowHandle, magari ottimizzato come ha suggerito MarcoGG.
Concordate? Ah però in effetti c'era anche il metodo sempre di GugoXX che suggeriva di rintracciare il processo lanciato con il suo PID e la EnumWindows()... però se non vado errato, anche in questo caso dovrei comunque attendere un po' prima di provare a rintracciare il processo, in modo da dargli tempo di costruire la finestra principale. Giusto? |
|
|
|
|
|
#13 | |
|
Senior Member
Iscritto dal: May 2004
Città: Londra (Torino)
Messaggi: 3692
|
Quote:
Prova ancora a verificare che per le applicazioni che vorresti usare non esista un componente COM gia' pronto. Se stai usando C#, prova ad aggiungere un riferimento di tipo COM alla tua applicazione. Dovresti vedere un lungo, lunghissimo elenco di tutti componenti COM disponibili sulla tua macchina. Se le applicazioni di tuo interesse espongono un controllo COM dovresti vederlo li', e dovresti poterlo aggiungere al tuo progetto. A quel punto ti ritroveresti con il controllo disponibile sulla toolbar, pronto per essere usato sulla tua Form come un qualsiasi altro controllo (quasi, un po' piu' rognoso). Questo e' il modo migliore. Certo, se non hanno pensato di fare un controllo COM non puoi percorrere questa strada. Come dicevo appunto InternetExplorer e' un Controllo Com utilizzato poi da una finestra (se non sbaglio il controllo di InternetExplorer si chiama axBrowser qualcosa) E cosi' anche i vari applicativi Office. Ma anche altri, es: VLC. In C#, per lo stesso motivo, quando si progettano applicazioni con parti GUI, e' bene separare la parte grafica dalla parte logica, mettendo la parte grafica dentro un controllo in un progetto di tipo WindowsFormControlLibrary, la parte logica in un altro progetto di tipo Library a se stante (addirittura di tipo Windows Workflow se si vuole). In questo modo sara' possibile utilizzare la WindowsFormControlLibrary allo stesso modo che i vecchi COM, dentro nuove applicazioni. Il progetto principale risulterebbe essere solo un collante, la glue logic che mette insieme i vari pezzi. Pezzi che possono essere addirittura cambiati in seguito con nuove versioni (retrocompatibili).
__________________
Se pensi che il tuo codice sia troppo complesso da capire senza commenti, e' segno che molto probabilmente il tuo codice e' semplicemente mal scritto. E se pensi di avere bisogno di un nuovo commento, significa che ti manca almeno un test. |
|
|
|
|
|
| Strumenti | |
|
|
Tutti gli orari sono GMT +1. Ora sono le: 23:20.




















