If you wish to implement multiple interfaces you will want to go with MFC or preferably ATL. However, the code for a single singleton interface in plain C++ is not too bad. I converted this from a plain C version I did a while ago. The C++ version is a lot neater.
Code:
#include <windows.h>
#include <assert.h>
#define ASSERT assert
#include "ihello.h" // The midl generated header file
class CHello : public IHello, public IClassFactory
{
private:
ITypeInfo * m_ptinfo;
public:
/* --IUnknown methods-- */
STDMETHODIMP QueryInterface(REFIID riid, void ** ppv)
{
if (ppv == NULL) return E_INVALIDARG;
if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IDispatch) || IsEqualIID(riid, IID_IHello))
{
*ppv = (IHello *) this;
}
else if (IsEqualIID(riid, IID_IClassFactory))
{
*ppv = (IClassFactory *) this;
}
else
{
*ppv = NULL;
return E_NOINTERFACE;
}
AddRef();
return NOERROR;
}
STDMETHODIMP_(ULONG) AddRef(void) { return 1; }
STDMETHODIMP_(ULONG) Release(void) { return 1; }
/* --IDispatch methods-- */
STDMETHODIMP GetTypeInfoCount(unsigned int FAR* pctinfo)
{
if (!pctinfo) return E_INVALIDARG;
*pctinfo = 1;
return NOERROR;
}
STDMETHODIMP GetTypeInfo(unsigned int iTInfo, LCID lcid, ITypeInfo FAR* FAR* ppTInfo)
{
if (!ppTInfo) return E_INVALIDARG;
*ppTInfo = NULL;
if(iTInfo != 0) return DISP_E_BADINDEX;
m_ptinfo->AddRef(); // AddRef and return pointer to cached
// typeinfo for this object.
*ppTInfo = m_ptinfo;
return NOERROR;
}
STDMETHODIMP GetIDsOfNames(REFIID riid, OLECHAR ** rgszNames, UINT cNames, LCID lcid, DISPID * rgdispid)
{
return DispGetIDsOfNames(m_ptinfo, rgszNames, cNames, rgdispid);
}
STDMETHODIMP Invoke(DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS * pdispparams,
VARIANT * pvarResult, EXCEPINFO * pexcepinfo, UINT * puArgErr)
{
return DispInvoke(this, m_ptinfo, dispidMember, wFlags, pdispparams, pvarResult, pexcepinfo, puArgErr);
}
/* --IClassFactory methods-- */
STDMETHODIMP CreateInstance(LPUNKNOWN punkOuter, REFIID riid, void **ppv)
{
if (!ppv) return E_INVALIDARG;
*ppv = NULL;
if (punkOuter != NULL) return CLASS_E_NOAGGREGATION;
return QueryInterface(riid, ppv);
}
STDMETHODIMP LockServer (BOOL fLock) { return NOERROR; }
/* --Constructor and destuctor-- */
CHello()
{
ITypeLib * ptl;
ASSERT(SUCCEEDED(LoadTypeLib(L"hello.tlb", &ptl)));
ASSERT(SUCCEEDED(ptl->GetTypeInfoOfGuid(IID_IHello, &m_ptinfo)));
ptl->Release();
}
~CHello()
{
m_ptinfo->Release();
}
/* --IHello methods: Your custom interface goes here-- */
int m_x; // Could be private but let's keep everything together
STDMETHODIMP put_x(/* [in] */ int Value) { m_x = Value; return NOERROR; }
STDMETHODIMP get_x(/* [retval][out] */ int *retval) { *retval = m_x; return NOERROR; }
STDMETHODIMP ShowMessage(/* [defaultvalue][in] */ BSTR msg = L"Hello World!")
{
if (!msg) return E_INVALIDARG;
MessageBoxW(NULL, msg, L"Message", 0);
return NOERROR;
}
};
CHello g_Hello;
int main(void)
{
HRESULT hr;
BOOL bRet;
DWORD dwClsFactoryCookie;
MSG msg;
OleInitialize(NULL);
hr = CoRegisterClassObject(CLSID_Hello, (IClassFactory *) &g_Hello,
CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &dwClsFactoryCookie);
if (FAILED(hr)) { MessageBox(NULL, "Failed to register object", NULL, 0); return hr; }
while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0 && bRet != -1)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
CoRevokeClassObject(dwClsFactoryCookie);
OleUninitialize();
return 0;
}
It may look complicated but the only change you need to make is to replace the highlighted code wth your interface's methods(and replace "Hello" with the name of your interface).
Before you get to the code however, you need to provide an idl version of your interface.
Code:
[
uuid(3C591B20-1F13-101B-B826-00DD01103DE1), // LIBID_Hello
helpstring("Hello 1.0 Type Library"),
lcid(0x09),
version(1.0)
]
library Hello
{
importlib("stdole2.tlb");
#define DISPID_NEWENUM -4
[
uuid(3C591B25-1F13-101B-B826-00DD01103DE1), // IID_IHello.
helpstring("Hello object."),
oleautomation, dual
]
interface IHello : IDispatch
{
[propget, helpstring("Returns and sets x coordinate.")]
HRESULT x([out, retval] int* retval);
[propput, helpstring("Returns and sets x coordinate.")]
HRESULT x([in] int Value);
[helpstring("Displays a message.")]
HRESULT ShowMessage([in, defaultvalue("Hello World!")] BSTR msg);
}
[
uuid(FEB8C280-FD2D-11ce-87C3-00403321BFAC), // CLSID_Hello
helpstring("Hello Class"),
appobject
]
coclass Hello
{
[default] interface IHello;
interface IDispatch;
}
}
Again, you only have to replace the highlighted section with the methods that make up your interface (and replace "Hello" and generate new uuids). This is compiled with midl.exe and provides a type library and a header file.
Code:
midl hello.idl /header ihello.h
Finally, some entries must be made in the registry to register your class. Typically, this would be done on application install, but for simplicity we can do it with a ".reg" file:
Code:
REGEDIT4
[HKEY_CLASSES_ROOT\HELLO.Application]
""="HELLO Application"
[HKEY_CLASSES_ROOT\HELLO.Application\CLSID]
""="{FEB8C280-FD2D-11ce-87C3-00403321BFAC}"
[HKEY_CLASSES_ROOT\CLSID\{FEB8C280-FD2D-11ce-87C3-00403321BFAC}]
""="HELLO Application"
[HKEY_CLASSES_ROOT\CLSID\{FEB8C280-FD2D-11ce-87C3-00403321BFAC}\LocalServer32]
""="C:\\Full\\Path\\To\\Object\\HELLO.EXE /Automation"
[HKEY_CLASSES_ROOT\CLSID\{FEB8C280-FD2D-11ce-87C3-00403321BFAC}\ProgId]
""="HELLO.Application"
To end with here is some test code(a vbscript, test.vbs):
Code:
Set o = CreateObject("Hello.Application")
o.ShowMessage
o.ShowMessage "Goodbye Cruel World!"
o.ShowMessage o.x
o.x = 5
o.ShowMessage o.x
Being a COM object it can be used from C, C++, VB, C#, Perl, JScript, Delphi, or any other language that supports COM. Any out-of-process COM object is ready for DCOM so it can be run from the local computer or six blocks away(pending security, etc).
EDIT: It should be pointed out that you need administrator access when you merge the ".reg" file and when you first run the program. This is because LoadTypLib() implicitly registers the type library the first time it is called. (This should be done on installation.)