I am working on a project that involves the creation of a dll that honours a certain interface in order to plug into some software in order to add functionality to it. This is done by a dll that calls my dll (I do not have the source code for the dll that does the calling). Originally I was given an interface and a C# implementation that created a COM visible dll. However after using this for a while I found I wanted to make use of some large C++ libraries and as creating wrappers would take a long time I thought about creating a C++ ATL COM dll instead. I did this and the methods of my class appear to be called correctly (I register my dll, run the program and the methods appear to be called in the correct order), however I have found some of the behavior to be different.
I am not sure how to go about explaining this as my code relates to a closed source API but perhaps if I describe an example someone might have some ideas as to where I might want look.
For instance, in the C# dll I attempt to open a file by doing this:
FMANFileControl fileControl = new FMANFileControl();
FMANFile wFile = null;
const string filePath = #"C:\Data\April 4\Data_IDA.wiff";
wFile = fileControl.GetFileObject(filePath, 1);
long numSamples = wFile.GetNumberOfSamples();
I get the correct number of samples.
In my C++ dll I have this (with some of the HRESULT checks removed in order to keep the code shorter):
std::string filePath = "C:\\Data\\April 4\\Data_IDA.wiff";
_bstr_t fileName(filePath.c_str());
IFMANFilePtr ipFMANFile;
IFMANFileControlPtr ipFMANFileControl;
hr = ipFMANFileControl.CreateInstance(__uuidof(FMANFileControl));
hr = ipFMANFile.CreateInstance(__uuidof(FMANFile));
ipFMANFile = ipFMANFileControl->GetFileObject(fileName, 1);
long numSamples = ipFMANFile->GetNumberOfSamples();
but the files does not open correctly, resulting is zero samples.
Using oleview I looked at the typelib and it says this for the function:
[id(0x00000001), helpstring("method GetWiffFileObject")]
IFMANWiffFile* GetWiffFileObject( [in] BSTR WiffFileName, [in] long sample);
The file I get information from is one that is being written to during an experiment and just before it obtains more data it calls my method and I should be able to obtain the newest file. In the C# dll this is possible, but in the C++ dll this is not. While I realize the specifics of this is hidden, I am wondering is anyone has any idea why a C++ COM dll and a C#, comvisible dll that make use of the same interface would exhibit different behavour when being called by the same dll.
I am pretty stumped at this moment so any ideas at all would be appreciated, even if they turn out to be way off base. I can share my source code if anyone thinks they might be able to help.
EDIT:
I tried the solution to answer 1, however I could not compile my code. When reading about this I found this post:
Differences between [in, out] and [out, retval] in COM IDL definitions
that seems to suggest that since the FMANFile pointer is marked [out, retval] that the method becomes:
IFMANFilePtr ExploreData::IFMANFileControl(BSTR filename, long sample);
or am I misinterpreting that article?
EDIT 2:
Got it working though I am not really sure why.
Originally I had the variables declared in the header as private member variables of the class, like this:
class ATL_NO_VTABLE CUserIDA :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CUserIDA, &CLSID_UserIDAObject>,
public IUserIDA
{
.
.
.
public:
STDMETHOD(GetSwitchCriteria)(DOUBLE* intensity, DOUBLE* minMass, DOUBLE* maxMass, VARIANT_BOOL *selectIntensity, LONG* numOfDepCycles);
.
.
.
private:
ExploreDataObjects::IFMANWiffFilePtr ipFMANWiffFile;
ExploreDataObjects::IFMANWiffFile2Ptr ipFMANWiffFile2;
};
Just to try it I moved them to the top of the class delcaration like this:
class ATL_NO_VTABLE CUserIDA :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CUserIDA, &CLSID_UserIDAObject>,
public IUserIDA
{
ExploreDataObjects::IFMANWiffFilePtr ipFMANWiffFile;
ExploreDataObjects::IFMANWiffFile2Ptr ipFMANWiffFile2;
I thought that by default these would also be private members and the same as before so I am at a loss to explain why this seemed to work. Can someone explain this?
Your C++ code is correct, except for the following line:
hr = ipFMANFile.CreateInstance(__uuidof(FMANFile));
It doesn't make any sens, because ipFMANFile is initialized once again in the next statement.
Unfortunately, this IDL declaration:
IFMANWiffFile* GetWiffFileObject([in] BSTR WiffFileName, [in] long sample);
is limited for debugging purposes since it doesn't support the native COM mechanisme for the exception reporting via HRESULT. The COM compliant declaration would be:
HRESULT GetWiffFileObject([in] BSTR WiffFileName, [in] long sample, [out, retval] IFMANWiffFile** fileInstance);
I believe that you are unable to change the library's code so I suggest you to try some external debugging tools like 'procmon.exe' and 'dbgview.exe' to inspect the application events when you run the CPP test case. Look for all failed actions.
I hope this will help you somehow
Related
First of all happy new year to everyone, hope you're doing well!
I'm working on a C++ project in which I need to call a C# DLL I created following the first answer of this post. Once I have the DLL, I need to call it from Qt, so by using dumpcpp and the .tlb file generated by regasm, I managed to get the .cpp and .h files to use my classes. Just as a reference, the namespace of the classes is Wrapper, and the main class is Device with guid {DD4A4896-C105-4C60-839B-B18C99C8FE15}.
Once I have the generated files to use the DLL, if I try to create a Wrapper:: Device instance on Qt, I get the following error:
QAxBase::setControl: requested control {dd4a4896-c105-4c60-839b-b18c99c8fe15} could not be instantiated
QAxBase::qt_metacall: Object is not initialized, or initialization failed
It doesn't give any more information, so I tried to check if the guid was stored on the system registry (I used the regasm command explained on the previously quoted post, and It said that it was successful, but you never know). Opening Registry editor and searching for the Guid revealed that it's present at: Computer\HKEY_CLASSES_ROOT\WOW6432Node\CLSID\{DD4A4896-C105-4C60-839B-B18C99C8FE15}, which, as far as I know, is the right route for these guids, and it points to the right DLL.
I though It may be due to some kind ActiveQt problem, and as the previously quoted post explained how to use that DLL from VS C++, I decided to give it a try, using this as an another reference. I've finished with this code, which is supposed to create an instance of my Device object
#include <iostream>
#include <atlstr.h>
#import "C:\Users\javie\Documents\Wrapper\Wrapper\bin\x86\Release\netstandard2.0\Wrapper.tlb" named_guids raw_interfaces_only
inline void TESTHR(HRESULT x) { if FAILED(x) _com_issue_error(x); };
int main()
{
try
{
TESTHR(CoInitialize(0));
Wrapper::IDevicePtr devPtr = nullptr;
TESTHR(devPtr.CreateInstance("{DD4A4896-C105-4c60-839B-B18C99C8FE15}"));
}
catch (const _com_error& e)
{
CStringW out;
out.Format(L"Exception occurred. HR = %lx, error = %s", e.Error(), e.ErrorMessage());
MessageBoxW(NULL, out, L"Error", MB_OK);
}
CoUninitialize();// Uninitialize COM
std::cout << "Hello World!\n";
}
However, this doesn't work either, the createInstance method throws an exception of Class not registered and HR=80040154. Again, according to Registry editor, the class is registered, so I don't understand the error. I've also tried with devPtr.CreateInstance("Wrapper.Device"), devPtr.CreateInstance("Wrapper::Device") or `devPtr.CreateInstance("Wrapper::CLSID_Device") as the links I posted suggest, but in those cases I get another exception with HR=800401f3 and message Invalid class string.
It doesn't matter whether VS or Qt Creator are opened as administrator or not, I get the exact same error.
I have run out of ideas, and I really need to be able to use that DLL from Qt using the files generated by dumpcpp.
Does any one know what could be happening? It feels quite strange to me.
If your C++ application is 64-bit, that's the answer right there, because your C# component is 32-bit (or MSIL but registered to the 32-bit hive). In situations like these, a simple test using VBScript is always useful.
Write a simple VB Script (test.vbs)
Dim obj
Set obj = CreateObject("Wrapper.Device") ' or whatever your ProgID is
MsgBox TypeName(obj)
Now, run this macro 2 ways: with 32-bit and 64-bit versions of VBScript:
32-bit > c:\windows\SysWow64\cscript.exe test.vbs
64-bit > c:\windows\system32\cscript.exe test.vbs
This is assuming your C# component is dispatch compatible. If it's not, then it will still give you differing results that you can use to debug.
Assuming automation/IDispatch compatible, one will work and one won't if you have registered your component correctly.
Have you registered correctly? When I use regasm, I always use the the switches /tlb /codebase when registering the C# component for COM.
Ok, in case someone find the same error, I'll explain the solution I found.
The problem was that in my case, the C# class I developed depended on another 32 bits dll which was not registered on my PC. Once I registered the other dll, everything worked fine.
I don't know why VS kept telling me that the class was not registered when my class itselft was registered, it was one of its dependencies that wasn't registered.
Anyway, I discovered this thanks to Joseph's comments, so thanks a lot for your help.
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;
};
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.
My question is similar to Getting CLSID for a DLL file?, I think.
I have a directory with some DLLs, each one implementing one or more COM interfaces. I would like to get:
1) Each interface name
2) The CLSID of the class implementing the interface
For each DLL. It's important that everything can be done programatically (So I can't use some sort of COM browser and manually look up for that information).
Later I will lookup the CLSID given the interface name and call some methods using IDispatch.
One alternative seems to be scanning the registry trying to match the type, interface and class GUID and the .dll filename. But that seems to be slow and not robust.
Does someone has a clear solution to this problem?
EDIT:
With the response of Ben Voigt, I came with the following code which suit my needs:
ITypeLib *typelib;
ITypeInfo *typeinfo;
LoadTypeLibEx(_T("c:\\mydir\\mycom1"), REGKIND_NONE, &typelib);
for (UINT i = 0;i < typelib->GetTypeInfoCount();++i) {
TYPEKIND typekind;
typelib->GetTypeInfoType(i, &typekind);
if (typekind == TKIND_COCLASS) {
// class!
CComBSTR className;
TYPEATTR *typeattr;
typelib->GetTypeInfo(i, &typeinfo);
typeinfo->GetDocumentation(MEMBERID_NIL, &className, NULL, NULL, NULL);
typeinfo->GetTypeAttr(&typeattr);
GUID classGUID = typeattr->guid;
for (UINT j = 0;j < typeattr->cImplTypes;++j) {
// interface!
CComBSTR interfaceName;
HREFTYPE hreftype;
ITypeInfo *classtypeinfo;
typeinfo->GetRefTypeOfImplType(j, &hreftype);
typeinfo->GetRefTypeInfo(hreftype, &classtypeinfo);
classtypeinfo->GetDocumentation(MEMBERID_NIL, &interfaceName, NULL, NULL, NULL);
// associate interfaceName with classGUID here
}
}
}
You can't get that from the COM DLL, but you can get it from the typelib. I'm pretty sure the MIDL compiler has a switch to decompile a typelib, but parsing IDL wouldn't be as easy as using the TypeLib API.
To complicate matters, the typelib is often stored as a resource inside the DLL. So you'd extract the resource, and open it with the TypeLib API.
Start with LoadTypeLibEx which will return you an ITypeLib* interface pointer (you knew you were going to need COM in order to get information about COM libraries, right?). This will actually do the resource extraction step for you.
Then, call ITypeLib::GetTypeInfoCount to find out how many types there are. Call ITypeLib::GetTypeInfoType for each one to find the interfaces and coclasses. And call ITypeLib::GetTypeInfo followed by ITypeInfo::GetDocumentation to get the name.
It looks like you have all of this so far. Next you need the GUID of the type, which is gotten with ITypeInfo::GetTypeAttr (not ITypeLib::GetLibAttr). That gives you a TYPEATTR structure, which has a guid field.
From the same TYPEATTR structure, you'll need the cImplTypes field. That together with ITypeInfo::GetRefTypeOfImplType will let you match up each coclass to the interfaces it implements.
Note that there's not guaranteed to be a 1:1 relationship between interfaces and implementation coclasses. And the interface can be in a different library from the coclass.
Few caveats to Ben Voigt's answer: not every COM DLL has a typelib. In your case, it seems, it does; but that's not a requirement. The only rock-solid requirement is that the DLL exports the function DllGetClassObject(), where you pass a CLSID and get back an object factory.
You could load the library and call DllGetClassObject for every registered CLSID on the system (scan the registry under HKCR\CLSID for the list of those). The ones where you get a valid object back are the ones that the DLL supports. Now, in theory, it's not even a requirement that the CLSIDs the DLL supports are registered; I can envision a DLL that implements private object classes that only the intended client knows about and no one else should. But this is a very, very exotic scenario; for one thing, the regular COM mechanism of looking up the DLL path by the CLSID will break for those. The client would have to load the DLL directly.
You could also scan the registry for the CLSID's where the DLL under consideration is registered as InprocServer32; this, again, will break in case of private classes. You can do a registry search in regedit, search in data. Also, you'd have to deal with filename case issues, short vs. long names, hardlinks, environment variable substitution and so on. So I would not recommend it.
EDIT: just thought of another way. Download Regmon, run it, and call regsvr32 on the DLL. Then watch what CLSID's are touched.
You might want to look at the source code in WiX's heat utility, which does the same thing:
http://wix.sourceforge.net/manual-wix3/heat.htm
I'm learning how to write a scriptable ActiveX control. My goal is to have a tiny control that can check to see if something is installed on the system. What I've done so far is:
Create a MFC ActiveX control project in VS2008
Add some 'safe for scripting' bits that I found here.
Extend the IDL to provide my "IsInstalled" method, which for now returns TRUE unconditionally (but will later read some keys from the registry.)
Build the control and run regsvr32 on it. I verified that it does show up in HKEY_CLASSES_ROOT, and when I instantiate the object, the IE Developer Tools "Locals" pane shows that the object is of type _D[my plugin name]. Not only that, but my IsInstalled() method shows up underneath that object.
However, when I call IsInstalled(), I just can't get it to work:
JScript Debugger - Breaking on JScript runtime error -(n http://img138.imageshack.us/img138/1586/whycomwhy.png
I'm at a loss. I've also tried making IsInstalled a property instead of a method, using VARIANT_BOOL instead of boolean instead of BOOL in the IDL, you name it.
Here's the relevant excerpts of code.
The header:
afx_msg VARIANT_BOOL IsInstalled();
The implementation:
afx_msg VARIANT_BOOL
CMyAXCtrl::IsInstalled()
{
return TRUE;
}
The dispatch map:
BEGIN_DISPATCH_MAP(CMyAXCtrl, COleControl)
DISP_FUNCTION_ID(CMyAXCtrl, "IsInstalled", dispidIsInstalled, IsInstalled, VT_BOOL, VTS_NONE)
END_DISPATCH_MAP()
The dispatch part of the IDL:
[ uuid(6B662202-CF13-4144-AA33-C3FEE9C2C962),
helpstring("Dispatch interface for My Control")]
dispinterface _Daxplugin
{
properties:
methods:
[id(1)] VARIANT_BOOL IsInstalled();
};
If there's any other relevant bits of code I should provide, let me know. But I'm stumped here. Thank you in advance!
You almost certainly have the wrong prototype for a scriptable function. OLE Automation for scripting languages tends to rely on returning a HRESULT then using an out parameter for the actual return code.
So change it to
[id(1)] HRESULT IsInstalled(VARIANT_BOOL* p);
Also TRUE != VARIANT_TRUE, you must return VARIANT_TRUE which is equal to -1 instead of 1.
Hope some of that actually helps, but without the actual error I might be mistaken :)
You could mark your control as save for scripting by implementing IObjectSafety or by marking the Object as save while registering it (as supposed by the link you provided).
Have you run regsvr32 after adding the code to mark it save for scripting?
You can check the registry if your control has the safe for scripting bits set.
If the bits are set you will find the two keys {7DD95802-9882-11CF-9FA9-00AA006C42C4} (Safe for Initialization)
{7DD95801-9882-11CF-9FA9-00AA006C42C4}(Safe For Scripting) as subkeys of ImplementedCategories in your object.
I would suggest to implement IObjectSafety since it doesn't depend on your class to register itself.