From ca289436cd4da42ed1be1480aebd01750eee41ec Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 9 May 2001 01:20:04 +0000 Subject: [PATCH] fixed wxExecute + DDE bug (merged from 2.2) git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@10084 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- docs/latex/wx/function.tex | 7 +- samples/exec/exec.cpp | 98 +++++++++++++++++++++------- src/msw/utilsexc.cpp | 128 ++++++++++++++++++++++++------------- 3 files changed, 166 insertions(+), 67 deletions(-) diff --git a/docs/latex/wx/function.tex b/docs/latex/wx/function.tex index b85ec925e5..952dcc160c 100644 --- a/docs/latex/wx/function.tex +++ b/docs/latex/wx/function.tex @@ -1466,7 +1466,12 @@ should ensure that this can cause no recursion, in the simplest case by calling \helpref{wxEnableTopLevelWindows(FALSE)}{wxenabletoplevelwindows}. For asynchronous execution, however, the return value is the process id and -zero value indicates that the command could not be executed. +zero value indicates that the command could not be executed. As an added +complication, the return value of $-1$ in this case indicattes that we didn't +launch a new process, but connected to the running one (this can only happen in +case of using DDE under Windows for command execution). In particular, in this, +and only this, case the calling code will not get the notification about +process termination. If callback isn't NULL and if execution is asynchronous (note that callback parameter can not be non-NULL for synchronous execution), diff --git a/samples/exec/exec.cpp b/samples/exec/exec.cpp index b540460720..80f4f4335d 100644 --- a/samples/exec/exec.cpp +++ b/samples/exec/exec.cpp @@ -93,8 +93,6 @@ public: void OnFileExec(wxCommandEvent& event); - void OnDDEExec(wxCommandEvent& event); - void OnAbout(wxCommandEvent& event); // polling output of async processes @@ -111,8 +109,21 @@ private: void DoAsyncExec(const wxString& cmd); + // last command we executed wxString m_cmdLast; +#ifdef __WINDOWS__ + void OnDDEExec(wxCommandEvent& event); + void OnDDERequest(wxCommandEvent& event); + + bool GetDDEServer(); + + // last params of a DDE transaction + wxString m_server, + m_topic, + m_cmdDde; +#endif // __WINDOWS__ + wxListBox *m_lbox; MyProcessesArray m_running; @@ -187,6 +198,7 @@ enum Exec_Shell, Exec_OpenFile, Exec_DDEExec, + Exec_DDERequest, Exec_Redirect, Exec_Pipe, Exec_About = 300 @@ -214,6 +226,7 @@ BEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_MENU(Exec_OpenFile, MyFrame::OnFileExec) EVT_MENU(Exec_DDEExec, MyFrame::OnDDEExec) + EVT_MENU(Exec_DDERequest, MyFrame::OnDDERequest) EVT_MENU(Exec_About, MyFrame::OnAbout) @@ -292,6 +305,7 @@ MyFrame::MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size) #ifdef __WINDOWS__ execMenu->AppendSeparator(); execMenu->Append(Exec_DDEExec, _T("Execute command via &DDE...\tCtrl-D")); + execMenu->Append(Exec_DDERequest, _T("Send DDE &request...\tCtrl-R")); #endif wxMenu *helpMenu = new wxMenu(_T(""), wxMENU_TEAROFF); @@ -529,48 +543,86 @@ void MyFrame::OnFileExec(wxCommandEvent& event) DoAsyncExec(cmd); } +#ifdef __WINDOWS__ + +bool MyFrame::GetDDEServer() +{ + wxString server = wxGetTextFromUser(_T("Server to connect to:"), + DIALOG_TITLE, m_server); + if ( !server ) + return FALSE; + + m_server = server; + + wxString topic = wxGetTextFromUser(_T("DDE topic:"), DIALOG_TITLE, m_topic); + if ( !topic ) + return FALSE; + + m_topic = topic; + + wxString cmd = wxGetTextFromUser(_T("DDE command:"), DIALOG_TITLE, m_cmdDde); + if ( !cmd ) + return FALSE; + + m_cmdDde = cmd; + + return TRUE; +} + void MyFrame::OnDDEExec(wxCommandEvent& WXUNUSED(event)) { -#ifdef __WINDOWS__ - wxString server = wxGetTextFromUser(_T("Server to connect to:"), - DIALOG_TITLE, _T("IExplore")); - if ( !server ) - return; - - wxString topic = wxGetTextFromUser(_T("DDE topic:"), - DIALOG_TITLE, _T("WWW_OpenURL")); - if ( !topic ) - return; - - wxString cmd = wxGetTextFromUser(_T("DDE command:"), - DIALOG_TITLE, - _T("\"file:F:\\wxWindows\\samples\\" - "image\\horse.gif\",,-1,,,,,")); - if ( !cmd ) + if ( !GetDDEServer() ) return; wxDDEClient client; - wxConnectionBase *conn = client.MakeConnection("", server, topic); + wxConnectionBase *conn = client.MakeConnection("", m_server, m_topic); if ( !conn ) { wxLogError(_T("Failed to connect to the DDE server '%s'."), - server.c_str()); + m_server.c_str()); } else { - if ( !conn->Execute(cmd) ) + if ( !conn->Execute(m_cmdDde) ) { wxLogError(_T("Failed to execute command '%s' via DDE."), - cmd.c_str()); + m_cmdDde.c_str()); } else { wxLogStatus(_T("Successfully executed DDE command")); } } -#endif // __WINDOWS__ } +void MyFrame::OnDDERequest(wxCommandEvent& WXUNUSED(event)) +{ + if ( !GetDDEServer() ) + return; + + wxDDEClient client; + wxConnectionBase *conn = client.MakeConnection("", m_server, m_topic); + if ( !conn ) + { + wxLogError(_T("Failed to connect to the DDE server '%s'."), + m_server.c_str()); + } + else + { + if ( !conn->Request(m_cmdDde) ) + { + wxLogError(_T("Failed to send request '%s' via DDE."), + m_cmdDde.c_str()); + } + else + { + wxLogStatus(_T("Successfully sent DDE request.")); + } + } +} + +#endif // __WINDOWS__ + // input polling void MyFrame::OnIdle(wxIdleEvent& event) { diff --git a/src/msw/utilsexc.cpp b/src/msw/utilsexc.cpp index 7bfdc8234a..6dfe980f2a 100644 --- a/src/msw/utilsexc.cpp +++ b/src/msw/utilsexc.cpp @@ -315,6 +315,49 @@ LRESULT APIENTRY _EXPORT wxExecuteWindowCbk(HWND hWnd, UINT message, } #endif // Win32 +#if wxUSE_IPC + +// connect to the given server via DDE and ask it to execute the command +static bool wxExecuteDDE(const wxString& ddeServer, + const wxString& ddeTopic, + const wxString& ddeCommand) +{ + bool ok; + + wxDDEClient client; + wxConnectionBase *conn = client.MakeConnection(_T(""), + ddeServer, + ddeTopic); + if ( !conn ) + { + ok = FALSE; + } + else // connected to DDE server + { + // the added complication here is that although most + // programs use XTYP_EXECUTE for their DDE API, some + // important ones - like IE and other MS stuff - use + // XTYP_REQUEST! + // + // so we try it first and then the other one if it + // failed + { + wxLogNull noErrors; + ok = conn->Request(ddeCommand) != NULL; + } + + if ( !ok ) + { + // now try execute - but show the errors + ok = conn->Execute(ddeCommand); + } + } + + return ok; +} + +#endif // wxUSE_IPC + long wxExecute(const wxString& cmd, bool sync, wxProcess *handler) { wxCHECK_MSG( !!cmd, 0, wxT("empty command in wxExecute") ); @@ -334,6 +377,11 @@ long wxExecute(const wxString& cmd, bool sync, wxProcess *handler) static const size_t lenDdePrefix = 7; // strlen("WX_DDE:") if ( cmd.Left(lenDdePrefix) == _T("WX_DDE#") ) { + // speed up the concatenations below + ddeServer.reserve(256); + ddeTopic.reserve(256); + ddeCommand.reserve(256); + const wxChar *p = cmd.c_str() + 7; while ( *p && *p != _T('#') ) { @@ -385,27 +433,21 @@ long wxExecute(const wxString& cmd, bool sync, wxProcess *handler) ddeCommand += *p++; } - // maybe we don't have to launch the DDE server at all - if it is - // already running, for example - wxDDEClient client; - wxLogNull nolog; - wxConnectionBase *conn = client.MakeConnection(_T(""), - ddeServer, - ddeTopic); - if ( conn ) + // if we want to just launch the program and not wait for its + // termination, try to execute DDE command right now, it can succeed if + // the process is already running - but as it fails if it's not + // running, suppress any errors it might generate + if ( !sync ) { - // FIXME we don't check the return code as for some strange reason - // it will sometimes be FALSE - it is probably a bug in our - // DDE code but I don't see anything wrong there - (void)conn->Execute(ddeCommand); - - // ok, the command executed - return value indicating success, - // making it up for async case as we really don't have any way to - // get the real PID of the DDE server here - return sync ? 0 : -1; + wxLogNull noErrors; + if ( wxExecuteDDE(ddeServer, ddeTopic, ddeCommand) ) + { + // a dummy PID - this is a hack, of course, but it's well worth + // it as we don't open a new server each time we're called + // which would be quite bad + return -1; + } } - //else: couldn't establish DDE conversation, now try launching the app - // and sending the DDE request again } else #endif // wxUSE_IPC @@ -637,36 +679,36 @@ long wxExecute(const wxString& cmd, bool sync, wxProcess *handler) #if wxUSE_IPC // second part of DDE hack: now establish the DDE conversation with the // just launched process - if ( !!ddeServer ) + if ( !ddeServer.empty() ) { - wxDDEClient client; - wxConnectionBase *conn; + bool ok; + // give the process the time to init itself + // + // we use a very big timeout hoping that WaitForInputIdle() will return + // much sooner, but not INFINITE just in case the process hangs + // completely - like this we will regain control sooner or later + switch ( ::WaitForInputIdle(pi.hProcess, 10000 /* 10 seconds */) ) { - // try doing it the first time without error messages - wxLogNull nolog; + default: + wxFAIL_MSG( _T("unexpected WaitForInputIdle() return code") ); + // fall through - conn = client.MakeConnection(_T(""), ddeServer, ddeTopic); + case -1: + wxLogLastError(_T("WaitForInputIdle() in wxExecute")); + + case WAIT_TIMEOUT: + wxLogDebug(_T("Timeout too small in WaitForInputIdle")); + + ok = FALSE; + break; + + case 0: + // ok, process ready to accept DDE requests + ok = wxExecuteDDE(ddeServer, ddeTopic, ddeCommand); } - if ( !conn ) - { - // give the app some time to initialize itself: in fact, a common - // reason for failure is that we tried to open DDE conversation too - // soon (before the app had time to setup its DDE server), so wait - // a bit and try again - ::Sleep(2000); - - wxConnectionBase *conn = client.MakeConnection(_T(""), - ddeServer, - ddeTopic); - if ( !conn ) - { - wxLogError(_("Couldn't launch DDE server '%s'."), command.c_str()); - } - } - - if ( conn ) + if ( !ok ) { // FIXME just as above we don't check Execute() return code wxLogNull nolog;