HOWTO: How do I call COM objects from an XLL?
Reference: Q0043
Article last modified on 30-Sep-2007
The information in this article applies to:
- XLL+ for Visual Studio .NET - 3, 4.1, 4.2, 4.3.1, 5.0
- XLL+ for Visual Studio 6 - 3, 4.1, 4.2, 4.3.1, 5.0
- XLL+ for Visual Studio 2005 - 5.0
How do I call COM objects from an XLL?
Issue
I want to use ADO (COM) objects from an XLL. How do I do it?
Summary
-
Use
#importstatements to include the libraries (or type libraries) that you need. -
Declare any library-level COM objects (e.g. a database connection) in your
application class. Initialize and terminate them in
OnXllOpenEx()andOnXllClose() -
Use
_variant_tandbstr_ttypes to wrap up arguments passed to COM methods. -
For details, see the sample project in q0043_sample.zip.
AdoAddin.slnis a Visual Studio .NET 2003 solution.AdoAddin8.slnis a Visual Studio 2005 solution. - For more information about using ADO from C++, see Microsoft's web-site.
#import
To include ADO in your library, add the following line near the top of your application header file:
#import "C:\Program Files\Common Files\System\ado\msado15.dll"
Unfortunately, this leads to a compiler error, because of a conflict with stdio.h,
so you must also add three more lines:
#ifdef EOF #undef EOF #endif #import "C:\Program Files\Common Files\System\ado\msado15.dll"
Application-level objects
Within your application class, add the declaration of a COM pointer.
// Names
public:
static LPCSTR m_pszDefName;
// Data
ADODB::_ConnectionPtr m_conn;
bool m_connectionIsOpen;
// Overrides
In your application class constructor, initialize the data members.
CAdoAddinApp::CAdoAddinApp()
: m_connectionIsOpen(false),
m_conn("ADODB.Connection")
{
...
}
In your class's override of OnXllOpenEx(), start using any data
members that will need to be available to add-in functions, eg:.
BOOL CAdoAddinApp::OnXllOpenEx()
{
...
// Prepare arguments for ADODB::_Connection::Open
bstr_t connectionString((LPCSTR)strConnection),
userID(L"Admin"),
password(L"");
long options = 0;
try
{
m_conn->Open(connectionString, userID, password, options);
m_connectionIsOpen = true;
}
catch(_com_error e)
{
XlMessageBox(CString("Failed to open connection: ") + e.ErrorMessage(), XlMessageBoxTypeExclamation);
return FALSE;
}
return TRUE;
}
Note the error-trapping using try...catch, and also note that we report the error and return FALSE if the procedure fails.
Dispose of any application-level objects in OnXllClose(), e.g.:
void CAdoAddinApp::OnXllClose()
{
if (m_connectionIsOpen)
{
m_conn->Close();
m_connectionIsOpen = false;
}
}
Add-in functions
In your add-in function, make sure that you wrap any IDispatch interfaces using
_variant_t, and any strings using bstr_t, e.g.:
//{{XLP_SRC(GetNames)
// NOTE - the FunctionWizard will add and remove mapping code here.
// DO NOT EDIT what you see in these blocks of generated code!
IMPLEMENT_XLLFN2(GetNames, "RC", "GetNames", "Filter", "Database"
, "Get names from table", "Filter applied to selection\000",
"\0appscope=1\0", 1)
extern "C" __declspec( dllexport )
LPXLOPER GetNames(const char* Filter)
{
XLL_FIX_STATE;
CXlOper xloResult;
//}}XLP_SRC
// Build query
CString query = "SELECT Name From Table1";
if (Filter[0] != 0)
query += CString(" WHERE Name LIKE '") + Filter + CString("'");
// Run query
ADODB::_RecordsetPtr rs("ADODB.Recordset");
_variant_t source((LPCTSTR)query);
_variant_t conn((IDispatch*)(XllGetTypedApp()->m_conn), true);
try
{
// Open recordset
rs->Open(source, conn, ADODB::adOpenForwardOnly, ADODB::adLockOptimistic, 0);
std::vector<CString> names;
// Read each record
while (!rs->EOF)
{
names.push_back((char*)(_bstr_t)rs->Fields->Item["Name"]->Value);
rs->MoveNext();
}
rs->Close();
xloResult = names;
}
catch(_com_error e)
{
xloResult.Format("#ERROR: %s", e.ErrorMessage());
}
return xloResult.Ret();
}
Sample
The sample project discussed above can be downloaded here.
