Test COM DLL without exporting methods explicitly - c++

I created an ATL COM DLL. I now have to unit test the methods defined in the COM interface of the DLL. I created a console application "tester.exe" and wrote some google tests, which would talk to the COM DLL and its method. Problem is, the tester.exe only compiles if I explicitly export the COM method like:
__declspec(dllexport) STDMETHOD(Add)(DOUBLE Input1, DOUBLE Input2, DOUBLE* pOutput);
I want to test the COM interface directly and not rely on a DLL export. Is there any way my tester/com client can compile and run the dll without needing the dllexport, such that the method definition stays:
STDMETHOD(Add)(DOUBLE Input1, DOUBLE Input2, DOUBLE* pOutput);
Am I missing something very obvious? I tried looking at many resources and tutorials but none were very helpful.

I was able to resolve this issue by directly setting the CLSID and IDD in the test class:
const CLSID CLSID_CTest = { 0x809a77g7,0x2624,0x453e,{0xb3,0xc6,0x2c,0x68,0x15,0xff,0xe3,0x23} };
const IID IID_ICTest = { 0x0c832b41,0xbfe9,0x4a16,{0xb9,0x34,0xff,0x7f,0x8b,0x77,0x30,0x44} };
and then using these to create a CoCreateInstance
hr = CoCreateInstance(CLSID_CTest , NULL, CLSCTX_INPROC_SERVER, IID_ICTest , (LPVOID*)&ptrICTest);
With this, I neither need the lib of the COM server when compiling, nor any header.

Related

Calling C# code from Windows Flutter desktop app

I am trying to call a function from a c# dll in a flutter desktop app,
In the C# dll I have,
using System;
using System.Runtime.InteropServices;
namespace MathLibrary
{
// NOTE: I have set set [assembly: ComVisible(true)] in AssemblyInfo.cs
[ComVisible(true)]
[Guid("66DE2FB9-7A3B-4C33-AF26-9AD5EDD4C71F")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IMathLibrary
{
[DispId(1)]
string multiply(int a, int b);
};
[ComVisible(true)]
[Guid("021E950E-3612-4FAD-9F15-F61632A95BD8")]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("MathLibrary.MathCalc")]
public class MathCalc : IMathLibrary
{
public string multiply(int a, int b)
{
return "Product is " + (a * b).ToString();
}
}
}
I used this repo as the base flutter app.
In the app I have used platform channel to communicate between dart and c++ code. In c++ code I am trying to call the c# function (in file windows/runner/custom_channel.cpp).
After some googling, I came up with the following
First I added an import to the tlb file (had to add import to generated tlh file for IntelliSense to work)
#import "bin/MathLibrary.tlb"
using namespace MathLibrary;
And the following function is supposed to call the c# function
CoInitialize(NULL);
MathLibrary::IMathLibraryPtr IcalcPtr;
HRESULT hr = ::CoCreateInstance(__uuidof(MathLibrary::MathCalc), NULL,
CLSCTX_INPROC_SERVER,
__uuidof(MathLibrary::IMathLibrary),
(void**)&IcalcPtr);
_bstr_t calcVal;
if (FAILED(hr) || IcalcPtr == nullptr) {
// CoCreateInstance failed
// THIS CONDITION IS MET
(*resPointer)->Error("Cannot Create COM Object");
return;
}
//IcalcPtr->multiply(a, b, &calcVal);
calcVal = IcalcPtr->multiply(a, b);
// not sure how to convert bstr to std::string
const char* calcStr((const char*) calcVal.GetBSTR());
c.assign(calcStr);
CoUninitialize();
The CoCreateInstance fails.
Since I have no experience with c++, I am confused,
what is IMathLibraryPtr(I didn't define in c#)
Intellisense showed that a and b in IcalcPtr->multiply(a, b) are long but I thought it would be int
When I make a release build do I need to include the tlb or dll
what is tlh file, it got generated during build and I got Intellisense support only if I add an import to that file
I would like to understand in general how to interact with c# com-interface from c++ and also how to make it work in my case. Sample code and document links would be helpful
The problem was with the way the c# dll was built and not with the way it was called from c++. I had built the dll for "Any CPU" once I built it for x64 it worked.

How to load registered COM DLL in C++

I have a C++ COM dll and I have register it with regsvr32. I want tho use the functions and class of the dll Inside my code. Unfortunatly I dont possess any .h and it doesnt come with a .tlb file. I have the documentation how to use the functions and class but there is no information about how to link the dll to my project so I could use it. I am new with using external COM interface so i'm not quite sur where I could find this information.
I have tried #import "example.dll" (dll inserted in the project folder but it looks like it doesn't work I have an unable to load dll error. My program is mixed CLR / unmanaged C++.
Any suggestions?
Thanks in advance
If enough information is provided, you can define the interfaces in a header file yourself. I would recommend using #import to import an existing COM type library and investigate the generated .tlh file for ideas. For a simple interface with functions, for example, the code looks something like this:
struct __declspec(uuid("Interface-Guid-with-Dashes")) IInterfaceName : IUnknown
{
virtual HRESULT __stdcall get_Value (/*[out,retval]*/ long * result) = 0;
virtual HRESULT __stdcall Execute (/*[in]*/ int value) = 0;
};

c++ application cannot find com dll because compiler generating .tlh file with incorrect guids

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.

C# COM dll has REGDB_E_CLASSNOTREG error in C++ project

I have a C# dll that I properly have registered for COM Interop, and made COM visible. Using cppbuilder, I imported the type library, which generated the wrapper classes, and I am now attempting to use to create an instance of my C# class. However, I'm getting a REGDB_E_CLASSNOTREG error in my C++ code. I verified the dll is in the registry, and even re-registered it with regasm. No change. What could I be missing?
Here is my C++ code:
_MyClassPtr obj;
HRESULT hr = obj.CreateInstance(__uuidof(MyClass));
//now hr equals REGDB_E_CLASSNOTREG
I've also tried it as such:
IMyClass* obj;
HRESULT hr = CoCreateInstance(__uuidof(MyClass), NULL, CLSCTX_INPROC_SERVER, __uuidof(IMyClass), (void**) &obj);
//same result, hr equals REGDB_E_CLASSNOTREG
I do have one additional dependency in the C# app. I registered it for COM as well with no difference, but did not import it's type library into the C++ project.
UPDATE: based on the comments below, I discovered that CreateInstance is looking up the class guid in the following places in the registry:
HKCU\Software\Classes\Wow6432Node\CLSID\{guid}
HKCR\Wow6432Node\CLSID\{guid}
HKCU\Software\Classes\CLSID\{guid}
HKCR\CLSID\{guid}
But, going through the registry, the only entry under any of the CLSID nodes that is related to my assembly is the guid for the assembly itself, which is, of course, different than the guid for the class, or the interface.
I've manually run regasm under both x86 and x64 mode to try to acheive different results. No differences.
Well, I found out what would work.
IMyClassPtr obj;
HRESULT hr = obj.CreateInstance(CLSID_MyClass);
CLSID_MyCLass was a guid constant in the generated MyClass_TLB.cpp file. Using it instead of __uuidof(...) for the class types enabled everything to start working correctly.

CoCreateInstance: Create a COM instance defined in a .NET library?

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.