I'm trying to create out-of-proc com server in some exe file and a client, which will access to functions through proxy\stub mechanism.
I have my .idl file:
[
object,
uuid(eaa27f4f-ad6b-4a52-90f3-6028507751a1),
dual,
nonextensible,
helpstring("IConfig Interface"),
pointer_default(unique)
]
interface IInterractionInterface : IDispatch
{
[id(1), helpstring("Testing function")] HRESULT Test([in] long param);
};
[
uuid(6fde5037-3034-4ae1-8aa7-2ad45e5716e4),
version(1.0),
helpstring("Some lib")
]
library SomeLib
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");
[
uuid(86feabe4-a0a7-45b5-bcd4-f4f7085d6b1f),
helpstring("Some lib")
]
coclass Interraction
{
[default] interface IInterractionInterface;
};
}
I am generated using midl compiler _p.c, _i.c files, created proxy\stub dll using .def:
LIBRARY proxy_stub.dll
DESCRIPTION 'generic proxy/stub DLL'
EXPORTS DllGetClassObject #1 PRIVATE
DllCanUnloadNow #2 PRIVATE
DllRegisterServer #4 PRIVATE
DllUnregisterServer #5 PRIVATE
Then i am registered this dll using regsrv32, and in win registry i have this:
In my server i am created factory:
CoRegisterClassObject(CLSID_InterractionInterfaceFactory, (IClassFactory*) &factory, CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, ®ID);
And it waits for client calls.
In client i am calling my factory using CreateInstance:
result = CoGetClassObject(CLSID_InterractionInterfaceFactory, CLSCTX_LOCAL_SERVER, NULL, IID_IClassFactory, (void**)&factory);
if (S_OK == result)
{
IInterractionInterface* iface = NULL;
result = factory->CreateInstance(NULL, IID_InterractionInterface, (void**)&iface);
if (S_OK == result)
{
}
}
And client receives null iface and result is E_UNEXPECTED, but in factory it creates successfuly and i am returning S_OK from Factory::CreateInstance().
I can't understand is PS mechanism using my .dll or not? Maybe i forgot some steps? Why my object can't pass through process borders?
Edit:
I've tried to replace the client code and now it is:
result = CoCreateInstance(CLSID_InterractionInterfaceFactory, NULL, CLSCTX_LOCAL_SERVER, IID_InterractionInterface, (void**)&iface);
iface->Test(1);
And when i am trying to call Test(1) it throws an error, that this is pure virtual function. And in factory in CreateInstance i am receiving requirement of Unkonown interface.
If you want to use proxy/stub DLL, define the interface outside of the library block. Only stuff defined outside of library goes into the code generated for proxy/stub DLL. Stuff defined, or referenced, inside library block goes into the generated type library. A typical IDL file defines interfaces outside of library, then mentions them inside, in coclass blocks; this way interface definitions end up in both proxy/stub and TLB, for maximum flexibility.
Your interface is automation compatible (well, almost; change the parameter type from int to long). In this case, you may prefer to use so called "universal" marshalling, based on a type library. Simply register the MIDL-generated TLB file with regtlib tool or programmatically with LoadTypeLibEx, and you would have marshaling support in place.
Related
I am quite inexperienced with COM objects, following is what I did:
In C++, I created a class which extend IUnknow and implements QueryInterface, AddRef and Release.
Extend IShellIconOverlayIdentifier and implements the 3 methods inside.
Export the class with __declspec(dllexport)
The DLL is compiled with MSVC2015 64 bits. (working on a windows7 64bits).
Now comes the difficult part:
How to make windows to link/use the DLL:
https://msdn.microsoft.com/en-us/library/windows/desktop/hh127455(v=vs.85).aspx Explain how to register the Overlay handler, but not how to register the COM object. I made a few tries, but after windows restart, nothing happens.
How to register and how to check if registered a COM dll?
Is there compilation restrictions like compiler/architecture?
EDITED Simplified:
It seem that I had a very incomplete vision of COM implementation, and in order to register the COM object, 2 directions are possible:
Register manually the object in the register, which is a nightmare.
Implement DllRegisterServer and DllUnregisterServer: which is a nightmare also.
To simplify the problem, I removed everything else up to having only those 2 functions:
STDAPI DllRegisterServer(void)
{
return NOERROR;
}
STDAPI DllUnregisterServer(void)
{
return NOERROR;
}
Unfortunately, this is not exported to the DLL, (as no __declspec(dllexport) is specified). As soon as I try to add this declaration, the compiler complain and no DLL is generated.
SOLUTION STEP 1:
Olectl.h is very very bad and avoid DllRegisterServer to be exported.
In order to create a COM object manually (without wizard), following steps are required:
Implement DllGetClassObject & DllCanUnloadNow
Implement Factory extending IClassFactory
Implement the Target class, extending IUknown
Register the COM in the registry
[optional] implement DllRegisterServer & DllUnregisterServer to make registry easier.
Implements DllGetClassObject & DllCanUnloadNow
Those are the only exported to the DLL functions:
STDAPI DllGetClassObject(_In_ REFCLSID rclsid, _In_ REFIID riid, _Outptr_ LPVOID * ppObject)
STDAPI DllCanUnloadNow(void)
DllGetClassObject, if the provided rclsid is the TargetClassCLSID and riid is the `FactoryCLSID, it creates and return a Factory object.
DllCanUnloadNow return if the DLL is in use (S_OK) or not, in which case the user/client may unload the DLL (S_FALSE).
The problem is, the definition do not allows to add __declspec(dllexport) due probably to some Microsoft monopol directive to make VS "mandatory".
To export them, just add before
#pragma comment( linker, "/export:DllGetClassObject,PRIVATE" )
#pragma comment( linker, "/export:DllCanUnloadNow,PRIVATE" )
//TODO: make this cross-compiler compatible?
Implementing the Factory
Implements AddRef and Release: they maintain how many client/users are currently using this Factory object. AddRef increment it by 1, Release decrements it by 1. As soon as the number is 0, Releasemay auto delete itself.
Implements QueryInterface which return if riidis the FactoryCLSID.
Implements CreateInstance, which return an instance of the TargetObject if riid match TargetClassCLSID
Implementing the Target Class
Again, we have to implements AddRef, Release and QueryInterface, but for the final class this time.
In my case, this Target class extends IShellIconOverlayIdentifier.
Register the COM object into the OS registry
After generating the dll, several registry need to be set. Some of them are linked together, so adding one may automatically add another.
HKEY_CLASS_ROOT::CLSID::<Add Here>
HKEY_CLASS_ROOT::Wow6432Node::CLSID::<Add Here>
HKEY_LOCAL_MACHINE::SOFTWARE::Classes::CLSID::<Add Here>
HKEY_LOCAL_MACHINE::SOFTWARE::Classes::Wow6432Node::CLSID::<Add Here>
HKEY_LOCAL_MACHINE::SOFTWARE::Wow6432Node::Classes::CLSID::<Add Here>
Content to add is:
Create a key: name={TargetClassCLSID}, value=<Any app name>
Create a sub-key: name=InProcServer32, value=<link to your dll>
Create a sub-string of InProcServer32: name=ThreadingModel, value=Apartment
//TODO: probably there are other alternatives, to better understand what that mean.
In my case, I had to register some more registers for the IconHandler:
HKEY_LOCAL_MACHINE::SOFTWARE::Microsoft::Windows::CurrentVersion::Explorer::ShellIconOverlayIdentifier::<Overlay here>
HKEY_LOCAL_MACHINE::SOFTWARE::Microsoft::Windows::CurrentVersion::ShellExtensions::Aproved::<new entry here>
Where the overlay entry is a key: name=<Any app name>, value=<TargetClassCLSID>
The new entry is a string value which name=<TargetClassCLSID>and value=Any app name
Those values need also to be in:
HKEY_LOCAL_MACHINE::SOFTWARE::Wow6432Node::Microsoft::Windows....
I have a multhithreaded application which works sitting in MTA. It performs calls of outproc object (let's name it OUTPROC_OBJ_A) and everything works fine:
each thread calls CoInitializeEx(NULL, COINIT_MULTITHREADED).
But now, I have yet another couple of com objects sitting in a dll - I need to use it in my application. Lets name one of it INPROC_OBJ_B. Their threading model is apartment threading. Also, it shares interfaces from OUTPROC_OBJ_A (it uses some of data as arguments of methods importing it from .tlb file)
When I do something like this (in one thread):
idl 1:
interface IData : IDispatch
{
}
coclass ConcreteData : IData;
idl 2: (uses idl 1):
interface IOBJ_B : IDispatch
{
void initData([in, out] IData* data);
}
coclass INPROC_OBJ_B : IOBJ_B;
main:
{
CoInitializeEx(NULL, COINIT_MULTITHREADED);
...
{
// 1.) Create instance of INPROC_OBJ_B.
CComPtr<IOBJ_B> pOBJ_B = CreateInstance(INPROC_OBJ_B);
// 2.) Pass to some method of INPROC_OBJ_B some data (For example, pointer to another com object created here.)
CComPtr<IData> pData = CreateInstance(ConcreteData);
pOBJ_B->initData(pData);
// 3.) Here it crashes. Probably on release of pData.
// initData now is empty and does nothing.
}
CoUninitialize();
}
When I do this, it can work fine sometimes. But sometimes, it crashes the entire application with access violation. Sometimes it throws object_is_not_connected_exception.
When I replace COINIT_MULTITHREADED with COINIT_APARTMENTTHREADED in CoInitializeEx, it works fine. But I can't do this in my application - The entire infrastructure is in MTA.
[UPDATE] : more pseudo code in example.
What am I doing wrong?
I have code running within a COM exe process. Specifically, I have an addin that operates within any Office product.
My code has an IDispatch* that is explicitly given to it. Specifically, I am given an IDispatch* through my implementation of _IDTExtensibility2::OnConnection.
The IDispatch* I receive is an interface to a specific coclass that is not the coclass which is marked with the [appobject] IDL attribute. Specifically, the interface is to a the coclass called Application (same name in all Office products), but the coclass marked [appobject] within any Office product typelib is called Global.
From within this process, is there a way to get the singleton instance of the the [appobject] coclass? Here's what I have tried so far:
STDMETHODIMP Addin::OnConnection(LPDISPATCH application, ext_ConnectMode connectMode, LPDISPATCH addInInst, SAFEARRAY** custom)
{
// get type info of Application object
ITypeInfoPtr typeInfoPtr;
application->GetTypeInfo(0, LOCALE_USER_DEFAULT, &typeInfoPtr);
// get type lib of Office product
ITypeLibPtr typeLibPtr;
UINT typeInfoIndex;
typeInfoPtr->GetContainingTypeLib(&typeLibPtr, &typeInfoIndex);
...
}
So I can get the ITypeInfo* and ITypeLib* for the Application class and its containing type library. I know I can use ITypeComp* to bind to global functions/variables by using the ITypeComp* returned from ITypeLib::GetTypeComp. Something like this:
// get type comp for the typelib
ITypeCompPtr typeCompPtr;
typeLibPtr->GetTypeComp(&typeCompPtr);
// bind to global property-get function "Application"
ITypeInfoPtr boundTypeInfoPtr;
DESCKIND descKind;
BINDPTR bindPtr;
ULONG hash = LHashValOfNameSys(SYS_WIN32, LOCALE_USER_DEFAULT, L"Application");
typeCompPtr->Bind(L"Application", hash, INVOKE_PROPERTYGET, &boundTypeInfoPtr, &descKind, &bindPtr);
...
However, this is for late-binding these function/variable calls. I actually want to perform early binding -- I have the necessary DISPIDs handy. What I don't have is a IDispatch* to the singleton instance of the [appobject] class.
Alternatively, I can scan the type lib for the coclass that has the [appobject] attribute. However, once I get the ITypeInfo* for that coclass, I don't know what to do with it. If I call CreateInstance on it, a new process is started as per usual COM activation rules.
How can I get an IDispatch* to this [appobject] instance?
I have a com dll that gets used in a c++ project. I have not registered the dll but its in the same solution. My application continuously breaks and I found that it breaks here
hr = CoCreateInstance(rclsid, pOuter, dwClsContext, __uuidof(IUnknown), reinterpret_cast<void**>(&pIUnknown));
because I don't have a com class with that guid (rclsid). I did more digging and I found that it gets the guid from a .tlh file in a temp directory but the guid differs from my code.
.tlh file
struct __declspec(uuid("b10a18c8-7109-3f48-94d9-e48b526fc928"))
DocMapEntry;
// [ default ] interface _DocMapEntry
// interface _Object
// interface IDocMapEntry
c# code (com visable dll)
[ComVisible(true)]
[Guid("E98676B9-1965-4248-A9B6-74EC5EE5385A")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IDocMapEntry
{
int Offset { get; set; }
int PageOffset { get; set; }
int PageNumber { get; set; }
}
I tried changing the value to the correct value but it continuously gets changed and visual studio asks me to reload.
When I look at the class using com explorer I get a different clsid, and when I try to unregister it, it completes successfully but the item is still there and the file location is mscoree.
my c++ application uses the guid in the .tlh file but doesn't find the file.
public class DocMapEntry : IDocMapEntry
{
...
}
My questions are.
Do I need to register my com dll for this to work?
how can I set the guid for the class?
Why would it create a temp .tlh file with the wrong guid? (optional)
why can't I change this file? (optional)
Thank you.
public interface IDocMapEntry
COM strongly distinguishes between a CLSID and a IID. The IID, the interface identifier, identifies an interface implemented by a COM object. We can see that one, you specified it with the [Guid] attribute. You'd pass it as the 4th argument of CoCreateInstance(), the one where you now pass the IID of IUnknown.
What you need is the CLSID, the class identifier. We can't see it in your snippet, it is the one for whatever class implements your IDocMapEntry. With some odds that it is named DocMapEntry. And additional odds that you didn't give it a [Guid] so the CLR will auto-generate one for you. Note that it will change when you modify the class, one possible reason for CoCreateObject() to fail.
Forgetting to register the .NET assembly with Regasm.exe /codebase is another reason, you stated as much in your question. This is required or COM will not be able to find the DLL and fails the call with error code 0x80040154, "Class not registered". Having it in the same solution is not sufficient, COM search rules are not anything like those used by .NET.
Also note that you are exposing the internals of the class, avoid that by applying the [ClassInterface(ClassInterfaceType.None)] attribute on the class.
No you do not have to register the COM DLL but you do need to register an object responsible for creating the COM objects. Use do this in your application by calling CoRegisterClassObject after the COM subsystem has been initialized (by calling CoInitialize or CoInitializeEx).
IClassFactory *factory = new ObjectFactory();
// Register the factory responsible for creating our COM objects
DWORD classToken;
HRESULT hr = CoRegisterClassObject(
Object_CLSIID,
factory,
CLSCTX_LOCAL_SERVER,
REGCLS_MULTIPLEUSE,
&classToken);
// Now we can create the objects
Object *obj = nullptr;
hr = CoCreateInstance(
Object_CLSIID,
nullptr,
CLSCTX_LOCAL_SERVER,
Object_IID,
reinterpret_cast<void**>(&obj));
Not sure why it's creating a temporary .tlh but I suspect it's something in your build settings - possibly in the build steps.
Is your COM object in a C# dll ? If yes, did you run regasm previously in your system, which could have registered the COM dll.
Guid("E98676B9-1965-4248-A9B6-74EC5EE5385A"): This is your interface GUID, not your component guid.
You should have a GUID declared for COM object in the implementation class, which should be b10a18c8-7109-3f48-94d9-e48b526fc928.
Why would it create a temp .tlh file with the wrong guid?
Are you sure you have not imported the COM dll using #import keyword.
I've created a COM library defined in an IDL, it has an "entrypoint" called IFoo:
[
object,
uuid(789b4d46-4028-4196-8412-4c5c8ef86caa),
nonextensible,
pointer_default(unique)
]
interface IFoo: IUnknown
{
HRESULT HelloWorld();
};
I've implemented this in my C# library like so:
[ComVisible(true)]
[Guid("45b50f1e-d551-4be0-b52a-7ec075840114")]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(IFoo))]
class Foo: IFoo
{
public void HelloWorld()
{
Console.WriteLine("Hello world");
}
}
I compiled this, and registered it with:
regasm.exe foo.dll
Everything looks good thus far.
Now, how do I create an instance of this in my C++ program? I think the right function is ::CoCreateInstance:
::CoInitializeEx(NULL, COINIT_MULTITHREADED);
IUnknown *pUnk = 0;
HRESULT hr = ::CoCreateInstance(???,
NULL,
CLSCTX_INPROC_SERVER,
__uuidof(IFoo),
(void**)&pUnk);
Is this the right way to create an instance of this class? If so, what goes in "???" above?
I think the class's UUID ("45b50f1e-d551-4be0-b52a-7ec075840114" above) should go there, but I can't figure out how to manually create an IID from this GUID string.
It turns out, I was right, the class's UUID goes there, but getting it was something I didn't expect:
struct __declspec(uuid("45b50f1e-d551-4be0-b52a-7ec075840114")) Cls;
Then:
HRESULT hr = ::CoCreateInstance(__uuidof(Cls),
NULL,
CLSCTX_INPROC_SERVER,
__uuidof(IFoo),
(void**)&pUnk);
This fixed the problem and worked correctly.
Just use MIDL to compile your IDL file into set of C++ files and include them into your C++ program. Have in mind that interface is not the "entry point" and you are going to need the class object in your IDL too.
Another way to create a COM client in C++ is Microsoft specific #import directive, but I am not sure if it is compatible with .NET-based components. If you can create a .tlb file for your .NET component, you can just add the needed stuff like this:
#import "MyLibrary.tlb"
This directive has lots of options. Check this MSDN article for details.