Allow wxAutomationObject::GetInstance() create new instance if needed.

When getting an instance of an OLE automation object, it is often useful to
connect to the existing instance if any or start a new one otherwise. Make
GetInstance() behave like this by default while still allowing to use the
wxAutomationInstance_UseExistingOnly flag to reestablish the old behaviour.

Also improve the error reporting in wxAutomationObject.

See #12489.

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@66262 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Vadim Zeitlin
2010-11-26 13:30:37 +00:00
parent 27d7687903
commit 6eefca4fb7
5 changed files with 152 additions and 82 deletions

View File

@@ -175,6 +175,11 @@ Changes in behaviour not resulting in compilation errors, please read this!
from the previous versions. Using wxNotebookEvent::GetSelection() instead of from the previous versions. Using wxNotebookEvent::GetSelection() instead of
querying the notebook selection avoids the problem and is recommended. querying the notebook selection avoids the problem and is recommended.
- wxMSW-specific wxAutomationObject::GetInstance() method now creates a new
instance if needed instead of failing if the application providing the
requested ProgID is not running. Pass wxAutomationInstance_UseExistingOnly
flag to it to revert to the old behaviour.
Changes in behaviour which may result in compilation errors Changes in behaviour which may result in compilation errors
----------------------------------------------------------- -----------------------------------------------------------
@@ -452,6 +457,7 @@ MSW:
- Allow using wxDC::DrawText() with multiline texts. - Allow using wxDC::DrawText() with multiline texts.
- Fix wxBitmapButton best size determination broken in 2.9.1. - Fix wxBitmapButton best size determination broken in 2.9.1.
- Center task dialog-based wxProgressDialog on the parent (John Roberts). - Center task dialog-based wxProgressDialog on the parent (John Roberts).
- wxAutomationObject::GetInstance() creates objects on demand (Kolya Kosenko).

View File

@@ -26,6 +26,16 @@ typedef unsigned short* WXBSTR;
#undef GetObject #undef GetObject
#endif #endif
// Flags used with wxAutomationObject::GetInstance()
enum wxAutomationInstanceFlags
{
// Only use the existing instance, never create a new one.
wxAutomationInstance_UseExistingOnly = 0,
// Create a new instance if there are no existing ones.
wxAutomationInstance_CreateIfNeeded = 1
};
/* /*
* wxAutomationObject * wxAutomationObject
* Wraps up an IDispatch pointer and invocation; does variant conversion. * Wraps up an IDispatch pointer and invocation; does variant conversion.
@@ -44,9 +54,10 @@ public:
// Get a dispatch pointer from the current object associated // Get a dispatch pointer from the current object associated
// with a ProgID, such as "Excel.Application" // with a ProgID, such as "Excel.Application"
bool GetInstance(const wxString& progId) const; bool GetInstance(const wxString& progId,
int flags = wxAutomationInstance_CreateIfNeeded) const;
// Get a dispatch pointer from a new instance of the the class // Get a dispatch pointer from a new instance of the class
bool CreateInstance(const wxString& progId) const; bool CreateInstance(const wxString& progId) const;
// Low-level invocation function. Pass either an array of variants, // Low-level invocation function. Pass either an array of variants,

View File

@@ -6,6 +6,35 @@
// Licence: wxWindows licence // Licence: wxWindows licence
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
/**
Automation object creation flags.
These flags can be used with wxAutomationObject::GetInstance().
@since 2.9.2
*/
enum wxAutomationInstanceFlags
{
/**
Only use the existing instance, never create a new one.
This flag can be used to forbid the creation of a new instance if none
is currently running.
*/
wxAutomationInstance_UseExistingOnly = 0,
/**
Create a new instance if there are no existing ones.
This flag corresponds to the default behaviour of
wxAutomationObject::GetInstance() and means that if getting an existing
instance failed, we should call wxAutomationObject::CreateInstance() to
create a new one.
*/
wxAutomationInstance_CreateIfNeeded = 1
};
/** /**
@class wxAutomationObject @class wxAutomationObject
@@ -103,14 +132,23 @@ public:
Retrieves the current object associated with the specified ProgID, and Retrieves the current object associated with the specified ProgID, and
attaches the IDispatch pointer to this object. attaches the IDispatch pointer to this object.
If attaching to an existing object failed and @a flags includes
wxAutomationInstance_CreateIfNeeded flag, a new object will be created.
Returns @true if a pointer was successfully retrieved, @false Returns @true if a pointer was successfully retrieved, @false
otherwise. otherwise.
Note that this cannot cope with two instances of a given OLE object being Note that this cannot cope with two instances of a given OLE object being
active simultaneously, active simultaneously,
such as two copies of Excel running. Which object is referenced cannot such as two copies of Excel running. Which object is referenced cannot
currently be specified. currently be specified.
@param progId COM ProgID, e.g. "Excel.Application"
@param flags The creation flags (this parameters was added in wxWidgets
2.9.2)
*/ */
bool GetInstance(const wxString& progId) const; bool GetInstance(const wxString& progId,
int flags = wxAutomationInstance_CreateIfNeeded) const;
/** /**
Retrieves a property from this object, assumed to be a dispatch pointer, and Retrieves a property from this object, assumed to be a dispatch pointer, and

View File

@@ -196,28 +196,26 @@ void MyFrame::OnAbout(wxCommandEvent& WXUNUSED(event))
*/ */
void MyFrame::OnTest(wxCommandEvent& WXUNUSED(event)) void MyFrame::OnTest(wxCommandEvent& WXUNUSED(event))
{ {
wxMessageBox(wxT("Please ensure Excel is running, then press OK.\nThe active cell should then say 'wxWidgets automation test!' in bold.")); wxMessageBox(wxT("Excel will be started if it is not running after you have pressed OK button.")
wxT("\nThe active cell should then say 'wxWidgets automation test!' in bold."),
wxT("Excel start"));
wxAutomationObject excelObject, rangeObject; wxAutomationObject excelObject;
if (!excelObject.GetInstance(wxT("Excel.Application"))) if ( !excelObject.GetInstance(wxT("Excel.Application")) )
{ {
// Start Excel if it is not running wxLogError(wxT("Could not create Excel object."));
if (!excelObject.CreateInstance(wxT("Excel.Application"))) return;
{
wxMessageBox(wxT("Could not create Excel object."));
return;
}
} }
// Ensure that Excel is visible // Ensure that Excel is visible
if (!excelObject.PutProperty(wxT("Visible"), true)) if (!excelObject.PutProperty(wxT("Visible"), true))
{ {
wxMessageBox(wxT("Could not make Excel object visible")); wxLogError(wxT("Could not make Excel object visible"));
} }
const wxVariant workbooksCountVariant = excelObject.GetProperty(wxT("Workbooks.Count")); const wxVariant workbooksCountVariant = excelObject.GetProperty(wxT("Workbooks.Count"));
if (workbooksCountVariant.IsNull()) if (workbooksCountVariant.IsNull())
{ {
wxMessageBox(wxT("Could not get workbooks count")); wxLogError(wxT("Could not get workbooks count"));
return; return;
} }
const long workbooksCount = workbooksCountVariant; const long workbooksCount = workbooksCountVariant;
@@ -226,19 +224,19 @@ void MyFrame::OnTest(wxCommandEvent& WXUNUSED(event))
const wxVariant workbook = excelObject.CallMethod(wxT("Workbooks.Add")); const wxVariant workbook = excelObject.CallMethod(wxT("Workbooks.Add"));
if (workbook.IsNull()) if (workbook.IsNull())
{ {
wxMessageBox(wxT("Could not create new Workbook")); wxLogError(wxT("Could not create new Workbook"));
return; return;
} }
} }
if (!excelObject.PutProperty(wxT("ActiveCell.Value"), wxT("wxWidgets automation test!"))) if (!excelObject.PutProperty(wxT("ActiveCell.Value"), wxT("wxWidgets automation test!")))
{ {
wxMessageBox(wxT("Could not set active cell value.")); wxLogError(wxT("Could not set active cell value."));
return; return;
} }
if (!excelObject.PutProperty(wxT("ActiveCell.Font.Bold"), wxVariant(true)) ) if (!excelObject.PutProperty(wxT("ActiveCell.Font.Bold"), wxVariant(true)) )
{ {
wxMessageBox(wxT("Could not put Bold property to active cell.")); wxLogError(wxT("Could not put Bold property to active cell."));
return; return;
} }
} }

View File

@@ -56,12 +56,12 @@
#if wxUSE_OLE_AUTOMATION #if wxUSE_OLE_AUTOMATION
// Report an OLE error to the user via wxLog. // Report an OLE error when calling the specified method to the user via wxLog.
static void static void
ShowException(const wxString& member, ShowException(const wxString& member,
HRESULT hr, HRESULT hr,
EXCEPINFO *pexcep, EXCEPINFO *pexcep = NULL,
unsigned int uiArgErr); unsigned int uiArgErr = 0);
// wxAutomationObject // wxAutomationObject
@@ -147,7 +147,7 @@ bool wxAutomationObject::Invoke(const wxString& member, int action,
1 + namedArgCount, LOCALE_SYSTEM_DEFAULT, dispIds); 1 + namedArgCount, LOCALE_SYSTEM_DEFAULT, dispIds);
if (FAILED(hr)) if (FAILED(hr))
{ {
ShowException(member, hr, NULL, 0); ShowException(member, hr);
delete[] argNames; delete[] argNames;
delete[] dispIds; delete[] dispIds;
return false; return false;
@@ -489,40 +489,78 @@ bool wxAutomationObject::GetObject(wxAutomationObject& obj, const wxString& prop
return false; return false;
} }
namespace
{
HRESULT wxCLSIDFromProgID(const wxString& progId, CLSID& clsId)
{
HRESULT hr = CLSIDFromProgID(wxBasicString(progId), &clsId);
if ( FAILED(hr) )
{
wxLogSysError(hr, _("Failed to find CLSID of \"%s\""), progId);
}
return hr;
}
void *DoCreateInstance(const wxString& progId, const CLSID& clsId)
{
// get the server IDispatch interface
//
// NB: using CLSCTX_INPROC_HANDLER results in failure when getting
// Automation interface for Microsoft Office applications so don't use
// CLSCTX_ALL which includes it
void *pDispatch = NULL;
HRESULT hr = CoCreateInstance(clsId, NULL, CLSCTX_SERVER,
IID_IDispatch, &pDispatch);
if (FAILED(hr))
{
wxLogSysError(hr, _("Failed to create an instance of \"%s\""), progId);
return NULL;
}
return pDispatch;
}
} // anonymous namespace
// Get a dispatch pointer from the current object associated // Get a dispatch pointer from the current object associated
// with a ProgID // with a ProgID
bool wxAutomationObject::GetInstance(const wxString& progId) const bool wxAutomationObject::GetInstance(const wxString& progId, int flags) const
{ {
if (m_dispatchPtr) if (m_dispatchPtr)
return false; return false;
HRESULT hr;
CLSID clsId; CLSID clsId;
IUnknown * pUnk = NULL; HRESULT hr = wxCLSIDFromProgID(progId, clsId);
wxBasicString unicodeName(progId);
hr = CLSIDFromProgID((BSTR) unicodeName, &clsId);
if (FAILED(hr)) if (FAILED(hr))
{
ShowException(progId, hr, NULL, 0);
wxLogWarning(wxT("Cannot obtain CLSID from ProgID"));
return false; return false;
}
IUnknown *pUnk = NULL;
hr = GetActiveObject(clsId, NULL, &pUnk); hr = GetActiveObject(clsId, NULL, &pUnk);
if (FAILED(hr)) if (FAILED(hr))
{ {
ShowException(progId, hr, NULL, 0); if ( flags & wxAutomationInstance_CreateIfNeeded )
wxLogWarning(wxT("Cannot find an active object")); {
const_cast<wxAutomationObject *>(this)->
m_dispatchPtr = DoCreateInstance(progId, clsId);
if ( m_dispatchPtr )
return true;
}
else
{
wxLogSysError(hr,
_("Cannot get an active instance of \"%s\""), progId);
}
return false; return false;
} }
hr = pUnk->QueryInterface(IID_IDispatch, (LPVOID*) &m_dispatchPtr); hr = pUnk->QueryInterface(IID_IDispatch, (LPVOID*) &m_dispatchPtr);
if (FAILED(hr)) if (FAILED(hr))
{ {
ShowException(progId, hr, NULL, 0); wxLogSysError(hr,
wxLogWarning(wxT("Cannot find IDispatch interface")); _("Failed to get OLE automation interface for \"%s\""),
progId);
return false; return false;
} }
@@ -536,34 +574,15 @@ bool wxAutomationObject::CreateInstance(const wxString& progId) const
if (m_dispatchPtr) if (m_dispatchPtr)
return false; return false;
HRESULT hr;
CLSID clsId; CLSID clsId;
HRESULT hr = wxCLSIDFromProgID(progId, clsId);
wxBasicString unicodeName(progId);
hr = CLSIDFromProgID((BSTR) unicodeName, &clsId);
if (FAILED(hr)) if (FAILED(hr))
{
ShowException(progId, hr, NULL, 0);
wxLogWarning(wxT("Cannot obtain CLSID from ProgID"));
return false; return false;
}
// get the server IDispatch interface const_cast<wxAutomationObject *>(this)->
// m_dispatchPtr = DoCreateInstance(progId, clsId);
// NB: using CLSCTX_INPROC_HANDLER results in failure when getting
// Automation interface for Microsoft Office applications so don't use
// CLSCTX_ALL which includes it
hr = CoCreateInstance(clsId, NULL, CLSCTX_SERVER, IID_IDispatch,
(void**)&m_dispatchPtr);
if (FAILED(hr))
{
ShowException(progId, hr, NULL, 0);
wxLogWarning(wxT("Could not start an instance of this class."));
return false;
}
return true; return m_dispatchPtr != NULL;
} }
static void static void
@@ -576,70 +595,68 @@ ShowException(const wxString& member,
switch (GetScode(hr)) switch (GetScode(hr))
{ {
case DISP_E_UNKNOWNNAME: case DISP_E_UNKNOWNNAME:
message = wxT("Unknown name or named argument."); message = _("Unknown name or named argument.");
break; break;
case DISP_E_BADPARAMCOUNT: case DISP_E_BADPARAMCOUNT:
message = wxT("Incorrect number of arguments."); message = _("Incorrect number of arguments.");
break; break;
case DISP_E_EXCEPTION: case DISP_E_EXCEPTION:
if ( pexcep )
{ {
message = wxT("Error Code ("); if ( pexcep->bstrDescription )
message << pexcep->wCode ;// unsigned short message << pexcep->bstrDescription << wxS(" ");
message += wxT(")"); message += wxString::Format(wxS("error code %u"), pexcep->wCode);
if (pexcep->bstrDescription != NULL) }
message += pexcep->bstrDescription; else
else {
message += wxT("<<No Description>>"); message = _("Unknown exception");
} }
break; break;
case DISP_E_MEMBERNOTFOUND: case DISP_E_MEMBERNOTFOUND:
message = wxT("Method or property not found."); message = _("Method or property not found.");
break; break;
case DISP_E_OVERFLOW: case DISP_E_OVERFLOW:
message = wxT("Overflow while coercing argument values."); message = _("Overflow while coercing argument values.");
break; break;
case DISP_E_NONAMEDARGS: case DISP_E_NONAMEDARGS:
message = wxT("Object implementation does not support named arguments."); message = _("Object implementation does not support named arguments.");
break; break;
case DISP_E_UNKNOWNLCID: case DISP_E_UNKNOWNLCID:
message = wxT("The locale ID is unknown."); message = _("The locale ID is unknown.");
break; break;
case DISP_E_PARAMNOTOPTIONAL: case DISP_E_PARAMNOTOPTIONAL:
message = wxT("Missing a required parameter."); message = _("Missing a required parameter.");
break; break;
case DISP_E_PARAMNOTFOUND: case DISP_E_PARAMNOTFOUND:
message = wxT("Argument not found, argument."); message.Printf(_("Argument %u not found."), uiArgErr);
message << uiArgErr;
break; break;
case DISP_E_TYPEMISMATCH: case DISP_E_TYPEMISMATCH:
message = wxT("Type mismatch, argument."); message.Printf(_("Type mismatch in argument %u."), uiArgErr);
message << uiArgErr;
break; break;
case ERROR_FILE_NOT_FOUND: case ERROR_FILE_NOT_FOUND:
message = wxT("The system cannot find the file specified."); message = _("The system cannot find the file specified.");
break; break;
case REGDB_E_CLASSNOTREG: case REGDB_E_CLASSNOTREG:
message = wxT("Class not registered."); message = _("Class not registered.");
break; break;
default: default:
message = wxT("Unknown error occurred. Return value is "); message.Printf(_("Unknown error %08x"), hr);
message << hr;
break; break;
} }
wxLogDebug("OLE Automation error in %s: %s", member, message); wxLogError(_("OLE Automation error in %s: %s"), member, message);
} }
#endif // wxUSE_OLE_AUTOMATION #endif // wxUSE_OLE_AUTOMATION