It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 10 years ago.
Hi I have a pure C++ project (DLL) which I would like to convert to COM project, e.g. all public interfaces defined in headers, will now be exposed as COM interfaces (IDL etc...). And the final product should be COM Dll.
How do I start? How do I define Any high level guidelines? good articles?
There are at least two parts to this problem:
First, COM objects are created using CoCreateInstance. CoCreateInstance looks for COM registrations in memory (via CoRegisterClassObject), in the applications manifest as a zero reg COM object, and finally, in the registry.
For zero reg, create an assembly manifest describing your dll so that consumers of your object can add a dependantAssembly reference to their application manifest.
Then, a COM dll needs at least two entry points: DllGetClassObject and DllCanUnloadNow
You implement DllGetClassObject by creating an instance of a factory object - that supports IClassFactory - that can be used to make instances of your actual object.
So, to summarize - a kind of TDD driven approach to implementing a COM dll:
Create a DLL with 'DllGetClassObject' and 'DllCanUnloadNow' entry points.
Create a new GUID to represent your object, and create a assembly manifest describing the COM object your dll (will) contain.
Create a test application, that calls CoCreateInstance with that GUID.
Calling CoCreateInstance should now land up in your DllGetClassObject call. Implement the class factory object.
Implement the CreateInstance method to create a new instance of your c++ class. Ensure that all your interfaces derive from (at least) IUnknown. Call QueryInterface on your own newly created object to get and return the desired interface.
Assuming Visual Studio (Express is ok) is your build environment:
Create a test exe:
// main.cpp
#include <windows.h>
#include <objbase.h>
#include <initguid.h>
DEFINE_GUID(CLSID_RegFreeOcx,0x00000000,0x0000,0x0000,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00);
#if defined _MSC_VER
#if !defined _WINDLL && !defined (_CONSOLE)
#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
#endif
#endif
#pragma comment(linker, "/manifestDependency:\"name='acme.RegFreeOcx' processorArchitecture='*' version='1.0.0.0' type='win32' \"")
int main(){
CoInitialize(NULL);
IUnknown* pUnk;
CoCreateInstance(CLSID_RegFreeOcx,NULL,CLSCTX_ALL,IID_IUnknown,(void**)&pUnk);
if(pUnk)
pUnk->Release();
}
Create the manifest for reg free activation of the COM dll:
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity name="Acme.RegFreeOcx" processorArchitecture="x86" version="1.0.0.0" type="win32" />
<file name = "RegFreeOcx.dll">
<comClass clsid="{00000000-0000-0000-0000-000000000000}" threadingModel="Apartment" />
</file>
</assembly>
Create a dll project
// dllmain.cpp
#include <windows.h>
#pragma comment(linker,"/export:DllGetClassObject=_DllGetClassObject#12,PRIVATE")
#pragma comment(linker,"/export:DllCanUnloadNow=_DllCanUnloadNow#0,PRIVATE")
#include <objbase.h>
#include <initguid.h>
DEFINE_GUID(CLSID_RegFreeOcx,0x00000000,0x0000,0x0000,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00);
#include "MyClassFactory.hpp"
STDAPI DllGetClassObject(REFCLSID clsid, REFIID riid,void** ppvObj)
{
if(CLSID_RegFreeOcx == clsid){
MyClassFactory* pFactory = new MyClassFactory();
HRESULT result = pFactory->QueryInterface(riid,ppvObj);
pFactory->Release();
return result;
}
return E_FAIL;
}
STDAPI DllCanUnloadNow()
{
return E_FAIL;
}
//MyClassFactory.hpp
#include <MyClass.hpp>
class MyClassFactory : public IClassFactory {
volatile ULONG _cRef;
public:
MyClassFactory():_cRef(1){}
virtual ~MyClassFactory(){}
public: // IUnknown
STDMETHODIMP_(ULONG)AddRef(){
return InterlockedIncrement(&_cRef);
}
STDMETHODIMP_(ULONG)Release(){
ULONG result = InterlockedDecrement(&_cRef);
if(!result) delete this;
return result;
}
STDMETHODIMP QueryInterface(REFIID riid, void** ppvObj){
if(riid == IID_IUnknown || riid == IID_IClassFactory)
*ppvObj = (IClassFactory*)this;
else {
*ppvObj=0;
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}
public: // IClassFactory
STDMETHODIMP CreateInstance(IUnknown* pUnkOuter,REFIID riid,void** ppvObj){
if(pUnkOuter)
return E_INVALIDARG;
MyClass* pClass = new MyClass();
HRESULT result = pClass->QueryInterface(riid,ppvObj);
pClass->Release();
return result;
}
STDMETHODIMP LockServer(BOOL fLock){
return E_NOTIMPL;
}
};
And finally, define your own class. You could use IDL to do this but, for other cpp consumers, you can just define it in a header file.
// IMyClass.h
#include <objbase.h>
DEFINE_GUID(IID_MyInterface,0x00000000,0x0000,0x0000,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00);
interface IMyInterface : IUnknown {
STDMETHOD(MyMethod)(void)PURE;
};
Implement the class that implements IMyInterface in a cpp file in much the same way that the ClassFactory was implemented (in terms of the IUnknown methods) - swapping out references to IClassFactory with your own interface.
THe canonical gentle introduction is Inside COM by Dale Rogenson. For real depth, try Essential COM by Don Box
The three big areas to worry about are:
Threading
Memory management and memory ownership conventions]
Types passed across COM interface boundaries - particularly not using STL
THe Box book also covers some of the really nasty things people do with COM - like the free threaded marshaller - which you might be applicable if your library is already thread safe and you want to avoid marshalling penalties.
Related
I was given a VSTO Outlook add-in written in VBA, and have been tasked to reduce the start-up time. I'm rather new to the whole add-in and COM objects thing so I need some help.
The add-in takes anywhere from 0.2s to 2.0s to startup, and Outlook disables the plugin if the average startup time is >1000ms. Using the registry hack to force enable the add-in is unfortunately not an option. I've also tested it with an empty add-in, which also can take up to 1.8s to startup. I've searched SO and other such sites for a solution, and across one involving writing a "stub" in an un-managed language such as Delphi or C++, which does nothing but load the actual add-in. The interface that's supposed to do this is IManagedAddin.
My issue is with implementing this interface. I've created a simple add-in in VC++ The class that implements _IDTExtensibility2 is in Connect.h, shown below. However, I don't have a clue how to implement IManagedAddin::Load to load my add-in into Outlook, and there doesn't seem to be a lot of documentation on this. Any help would be much appreciated!
EDIT: Updated code below
// Connect.h : Declaration of the CConnect
#pragma once
#include "resource.h" // main symbols
#include "NativeAddin_i.h"
#include "IManagedAddin.h"
#include <Windows.h>
#include <iostream>
#if defined(_WIN32_WCE) && !defined(_CE_DCOM) && !defined(_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA)
#error "Single-threaded COM objects are not properly supported on Windows CE platform, such as the Windows Mobile platforms that do not include full DCOM support. Define _CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA to force ATL to support creating single-thread COM object's and allow use of it's single-threaded COM object implementations. The threading model in your rgs file was set to 'Free' as that is the only threading model supported in non DCOM Windows CE platforms."
#endif
using namespace ATL;
// CConnect
class ATL_NO_VTABLE CConnect :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CConnect, &CLSID_Connect>,
public IDispatchImpl<IConnect, &IID_IConnect, &LIBID_PixelLLib, /*wMajor =*/ 1, /*wMinor =*/ 0>,
public IDispatchImpl<_IDTExtensibility2, &__uuidof(_IDTExtensibility2), &LIBID_AddInDesignerObjects, /* wMajor = */ 1, /* wMinor = */ 0>
{
public:
static Outlook::_Application* outlookApp;
static ext_ConnectMode connectMode;
static LPDISPATCH addInInst;
static SAFEARRAY** customArr;
static UINT_PTR timerId;
CConnect()
{
}
DECLARE_REGISTRY_RESOURCEID(106)
BEGIN_COM_MAP(CConnect)
COM_INTERFACE_ENTRY(IConnect)
COM_INTERFACE_ENTRY2(IDispatch, _IDTExtensibility2)
COM_INTERFACE_ENTRY(_IDTExtensibility2)
END_COM_MAP()
DECLARE_PROTECT_FINAL_CONSTRUCT()
HRESULT FinalConstruct()
{
return S_OK;
}
void FinalRelease()
{
}
static VOID CALLBACK TimerCallback(HWND hWnd, UINT nMsg, UINT timerId, DWORD dwTime)
{
KillTimer(NULL, timerId);
HRESULT hr;
BSTR manifestUrl = SysAllocString(L"file://path/to/manifest.vsto");
// load add-in
IID clsid;
hr = IIDFromString(OLESTR("{99D651D7-5F7C-470E-8A3B-774D5D9536AC}"), &clsid); // VSTOAddinLoader CLSID
IID iid;
hr = IIDFromString(OLESTR("{B9CEAB65-331C-4713-8410-DDDAF8EC191A}"), &iid);
IManagedAddin* loader;
hr = CoCreateInstance(clsid, NULL, CLSCTX_ALL, iid, (void**)&loader);
hr = loader->Load(manifestUrl, outlookApp);
_IDTExtensibility2* ext;
hr = loader->QueryInterface(IID__IDTExtensibility2, (void**)&ext);
hr = ext->OnConnection(outlookApp, connectMode, addInInst, customArr);
MSO::IRibbonExtensibility* ribbon;
hr = (???)->QueryInterface(MSO::IID_IRibbonExtensibility, (void**)&ribbon);
}
STDMETHOD(OnConnection)(LPDISPATCH App, ext_ConnectMode ConnectMode, LPDISPATCH AddInInst, SAFEARRAY * * custom)
{
HRESULT hr;
UINT time = 200;
Outlook::_Application* app;
hr = App->QueryInterface(__uuidof(Outlook::_Application), (void**)&app);
// init static members
outlookApp = app;
connectMode = ConnectMode;
addInInst = AddInInst;
customArr = custom;
timerId = SetTimer(NULL, 0, time, (TIMERPROC)&TimerCallback);
return S_OK;
}
STDMETHOD(OnDisconnection)(ext_DisconnectMode RemoveMode, SAFEARRAY * * custom)
{
return S_OK;
}
STDMETHOD(OnAddInsUpdate)(SAFEARRAY * * custom)
{
return S_OK;
}
STDMETHOD(OnStartupComplete)(SAFEARRAY * * custom)
{
return S_OK;
}
STDMETHOD(OnBeginShutdown)(SAFEARRAY * * custom)
{
return S_OK;
}
};
OBJECT_ENTRY_AUTO(__uuidof(Connect), CConnect)
Outlook::_Application* CConnect::outlookApp = NULL;
ext_ConnectMode CConnect::connectMode = ext_cm_AfterStartup;
LPDISPATCH CConnect::addInInst = NULL;
SAFEARRAY** CConnect::customArr = NULL;
UINT_PTR CConnect::timerId = 0;
// IManagedAddin.h
#pragma once
#include "resource.h"
#include "NativeAddin_i.h"
struct __declspec(uuid("B9CEAB65-331C-4713-8410-DDDAF8EC191A"))
IManagedAddin : IUnknown
{
public:
virtual STDMETHOD(Load)(BSTR bstrManifestUrl, LPDISPATCH pdisApplication) = 0;
virtual STDMETHOD(Unload)() = 0;
};
// pch.h
#ifndef PCH_H
#define PCH_H
// add headers that you want to pre-compile here
#include "framework.h"
#import "libid:AC0714F2-3D04-11D1-AE7D-00A0C90F26F4" raw_interfaces_only, raw_native_types, named_guids, auto_search, no_namespace
#import "libid:2DF8D04C-5BFA-101B-BDE5-00AA0044DE52" raw_interfaces_only, raw_native_types, named_guids, auto_search, rename_namespace("MSO")
#import "libid:00062FFF-0000-0000-C000-000000000046" raw_interfaces_only, raw_native_types, named_guids, auto_search, rename_namespace("Outlook")
#endif //PCH_H
What I did was to wait until OnConnection callback fired and started a timer - you might be able to use a separate thread, but in my case the processing had to be done on the main thread due to some functionality that had thread affinity. OnConnection will give you Outlook.Application object.
In the timer callback (Outlook is not looking), create an instance of the IManagedAddin COM object using CoCreateInstance(CLSID_IManagedAddin, ...). Call IManagedAddin::Load. The path must be in the form file://c:/the/folder/myaddin.vsto.
QI the IManagedAddin object for IDTExtensibility2 interface and call IDTExtensibility2::OnConnection() using the parameters saved from the native OnConnection callback.
If Outlook has already called your C++ implementation of OnStartupComplete, call OnStartupComplete on the VSTO addin. If not, you can do it later.
QI for IRibbonExtensibility and call IRibbonExtensibility::GetCustomUI. Or you can hardcode the ribbon XML in the C++ addin. Note that if Outlook calls IRibbonExtensibility::GetCustomUI on your C++ addin, and you have to delegate the call to the VSTO addin before you had a chance to run your timer code, you have no choice but to call the above code immediately rather than in a timer callback since GetCustomUI cannot be postponed.
If you are using task panes (i.e. ICustomTaskPaneConsumer interface is implemented by your VSTO addin), your C++ addin must also implement it (besides the IDTExtensibility2 and ICustomTaskPaneConsumer interfaces). QI IManagedAddin for IServiceProvider interface and use it to call IServiceProvider::QueryService(GUID_NULL, IID_ICustomTaskPaneConsumer, ...). then call ICustomTaskPaneConsumer.CTPFactoryAvailable - note that ICustomTaskPaneConsumer does not come from your VSTO addin's IDTExtensibility2 interface but rather though the IServiceProvider object off the IManagedAddin interface.
in short:
Is it possible to create a C++-based COM component that I can reference and use from VBA, without needing to register the DLL (in windows registry)?
In long:
I'm a COM novice, so maybe what I'm tryong to do here structurally doesn't work or doesn't make sense, but:
I want to create a C++ COM library that is usable from VBA registration-free.
So I've created an C++ ATL project in VS, added a custom COM type (MyClass) and added a test method to it ("AddNumbers", taking 2 long parameters and returning 1).
When building this project, I'm getting an error "Failed to register output. Please try anebling Per-User Redirection or register the component from a comment prompt with elevated permissions", likely because I'm running Visual Studio without admin permissions. However, the DLL builds fine, I assume it's just not registered after building it.
Right, so when I reference this DLL in my VBA project (Tools -> Referewnces -> Browse... and add), intellisesne works, i.e. VBA knows the type "MyClass", knows it has a method "AddNumbers" and so on.
But, it can't instantiate it:
Sub test()
Dim q As MyClass
Set q = New MyClass ' This will fail: "Run-time error '429': ActiveX component can't create object"
End Sub
Why is that? Am I right in assuming it likely fails because this COM component isn't properly registered on the machine?
If so, can I somehow circumvent this? I've read some infos about registration-free .NET based COM components, but I have no idea if this also works - or is even needed?! - for C++ based components.
MyClass.h
// MyClass.h : Declaration of the CMyClass
#pragma once
#include "resource.h" // main symbols
#include "ATLProject4_i.h"
#if defined(_WIN32_WCE) && !defined(_CE_DCOM) && !defined(_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA)
#error "Single-threaded COM objects are not properly supported on Windows CE platform, such as the Windows Mobile platforms that do not include full DCOM support. Define _CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA to force ATL to support creating single-thread COM object's and allow use of it's single-threaded COM object implementations. The threading model in your rgs file was set to 'Free' as that is the only threading model supported in non DCOM Windows CE platforms."
#endif
using namespace ATL;
// CMyClass
class ATL_NO_VTABLE CMyClass :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CMyClass, &CLSID_MyClass>,
public IDispatchImpl<IMyClass, &IID_IMyClass, &LIBID_ATLProject4Lib, /*wMajor =*/ 0xFFFF, /*wMinor =*/ 0xFFFF>
{
public:
CMyClass()
{
}
DECLARE_REGISTRY_RESOURCEID(106)
BEGIN_COM_MAP(CMyClass)
COM_INTERFACE_ENTRY(IMyClass)
COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()
DECLARE_PROTECT_FINAL_CONSTRUCT()
HRESULT FinalConstruct()
{
return S_OK;
}
void FinalRelease()
{
}
public:
STDMETHOD(AddNumbers)(LONG Num1, LONG Num2, LONG* ReturnVal);
};
OBJECT_ENTRY_AUTO(__uuidof(MyClass), CMyClass)
MyClass.cpp
// MyClass.cpp : Implementation of CMyClass
#include "stdafx.h"
#include "MyClass.h"
// CMyClass
STDMETHODIMP CMyClass::AddNumbers(LONG Num1, LONG Num2, LONG* ReturnVal)
{
// TODO: Add your implementation code here
*ReturnVal = Num1 + Num2;
return S_OK;
}
After porting a project from visual studio to mingw. I am getting the following linker error
undefined reference to `g_Templates'
undefined reference to `g_cTemplates'
The code which it points to looks something like this
#include <tchar.h>
#endif // DEBUG
#include <strsafe.h>
#include <combase.h>
extern CFactoryTemplate g_Templates[];
extern int g_cTemplates;
HINSTANCE g_hInst;
DWORD g_amPlatform; // VER_PLATFORM_WIN32_WINDOWS etc... (from GetVersionEx)
OSVERSIONINFO g_osInfo;
//
// an instance of this is created by the DLLGetClassObject entrypoint
// it uses the CFactoryTemplate object it is given to support the
// IClassFactory interface
class CClassFactory : public IClassFactory, public CBaseObject
{
private:
const CFactoryTemplate *const m_pTemplate;
...
public:
CClassFactory(const CFactoryTemplate *);
// IUnknown
STDMETHODIMP QueryInterface(REFIID riid, __deref_out void ** ppv);
STDMETHODIMP_(ULONG)AddRef();
STDMETHODIMP_(ULONG)Release();
// IClassFactory
STDMETHODIMP CreateInstance(LPUNKNOWN pUnkOuter, REFIID riid, __deref_out void **pv);
STDMETHODIMP LockServer(BOOL fLock);
// allow DLLGetClassObject to know about global server lock status
static BOOL IsLocked() {
return (m_cLocked > 0);
};
};
// --- COM entrypoints -----------------------------------------
//
// Call any initialization routines
//
void DllInitClasses(BOOL bLoading)
{
// traverse the array of templates calling the init routine
// if they have one
for (i = 0; i < g_cTemplates; i++) //<---------Cannot recognize this symbol
{
const CFactoryTemplate * pT = &g_Templates[i];
if (pT->m_lpfnInit != NULL)
{
(*pT->m_lpfnInit)(bLoading, pT->m_ClsID);
}
}
}
....
....
I have been searching on this issue for a while and have not made any progress. It seems that that this symbol exists in strmbasd.lib (debug version) and is generated from DirectShow base classes. I generated strmbasd.lib using mingw64 however I am still getting this linker error. I wanted to know if there was any other approach I could try .
I have used Direct Show for Microsoft Visual C++. And found no such issue. Microsoft SDK provides the libraries and headers as well as the base classes. You may want to check the link. I haven't used MingW, so I don't know about the issue of MingW. You may try it in MSVC, MSDN provides some handful informations and references for Direct Show. Please check the previous link mentioned above.
Your including dllentry.cpp/dllsetup.cpp from DirectShow BaseClasses assumes that you develop a filter library and you are expected to define template symbols in your code (example) to satisfy linker.
If you don't see how your code is referencing factories, you can define fake array and g_cTemplates of zero to pass through, however eventually there is something that makes linker drag these symbols into output.
I am attempting to understand creating/using COM components without the help of MFC/ATL to know its inner workings.
I am using this codeguru article for reference.Following are the steps followed by me.
Created a Wind32 Dll,
Added a MIDL file and declared the interface IAdd and library name DemoMath; compiled the code using MIDL compiler.
Created CAddObj class deriving IAdd interface,provided implementation for IAdd and IUnknown interfaces.
Created class CAddFactory deriving from IClassFactory interface;provided implementation for IClassFactory methods.
Now creating DllGetClassObject to give client an option to invoke this function to get an instance of the class factory.
Following is the code:
#include "stdafx.h"
#include <objbase.h>
#include "AddObjFactory.h"
#include "IAdd_i.c"
STDAPI DllGetClassObject(const CLSID& clsid,
const IID& iid,
void** ppv)
{
//
//Check if the requested COM object is implemented in this DLL
//There can be more than 1 COM object implemented in a DLL
//
if (clsid == CLSID_AddObject)
{
//
//iid specifies the requested interface for the factory object
//The client can request for IUnknown, IClassFactory,
//IClassFactory2
//
CAddFactory *pAddFact = new CAddFactory;
if (pAddFact == NULL)
return E_OUTOFMEMORY;
else
{
return pAddFact->QueryInterface(iid , ppv);
}
}
//
//if control reaches here then that implies that the object
//specified by the user is not implemented in this DLL
//
return CLASS_E_CLASSNOTAVAILABLE;
}
Now where is CLSID_AddObject constant suppose to be defined
or Is it generated while compiling MIDL file(I didn't find it though)?
coclass IDL item will typically get you CLSID:
library Foo
{
//...
[
//...
]
coclass AddObject
{
//...
};
then on your "IAdd_i.c" you are already including:
MIDL_DEFINE_GUID(CLSID, CLSID_AddObject, ...);
this is what defines CLSID_AddObject.
I'm writing a C++ application which imports a COM DLL as following,
#import "MyLib.dll" no_namespace, raw_interfaces_only
There is a problem using method '_GetObject' which is declared in the idl file like this,
[
object,
uuid(f022c0e0-1234-5678-abcd-c17d63954f4b),
dual,
nonextensible,
helpstring("IStorageProxy Interface"),
pointer_default(unique)
]
interface IStorageProxy : IDispatch
{
[hidden, helpstring("method _GetObject")]
HRESULT _GetObject(
[in] BSTR entryId,
[in] REFCLSID rclsid,
[in] REFIID riid,
[out, iid_is(riid), retval] IUnknown** stgObject);
};
But the generated tlh file has changed the types of the second and third parameters.
struct __declspec(uuid("f022c0e0-1234-5678-abcd-c17d63954f4b"))
IStorageProxy : IDispatch
{
//
// Raw methods provided by interface
//
virtual HRESULT __stdcall _GetObject (
/*[in]*/ BSTR entryId,
/*[in]*/ GUID * rclsid,
/*[in]*/ GUID * riid,
/*[out,retval]*/ IUnknown * * stgObject ) = 0;
};
As I'm coding against the original function signature (defined in the idl), so now the C++ code can't compile. I'm not sure why the types changed to 'GUID *'. Is there any way to stop the compiler from doing this?
No, that's normal. Both REFGUID and REFIID are just a typedef for GUID*. Same kind of idea as a HWND and HDC, typedefs for HANDLE. These typedefs catch mistakes in C++ code, they are not really appropriate in a type library that supplies typeinfo to many languages.
You could technically keep these typedefs but those types will then have to be defined in the type library as well so that the COM client knows what they mean. The component author would have to include WTypes.idl. That's probably too late by now, you can't do anything about it in a COM client.