Given a COM DLL, extract all classes CLSID and corresponding interface name
Asked Answered
I

3

9

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
        }
    }
}
Icsh answered 10/1, 2011 at 21:46 Comment(0)
O
10

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.

Outleap answered 10/1, 2011 at 21:53 Comment(0)
R
6

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 (EDIT: or reference the DLL in the executable's manifest, as allowed by registration free COM).

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.

Retardation answered 11/1, 2011 at 17:18 Comment(6)
DllGetClassObject might have undesirable side effects, so I don't recommend this. Also, it's entirely possible to have a combination of public CLSIDs and private ones. Since the DLL is loaded by creation of a public type, there's no need to ever load the DLL outside the COM mechanisms.Outleap
Well, the ultimate forensics would involve disassembling the DLL and going through the code of DllGetClassObject(). In the typical implementation (read: the ATL one) there'd be a global data structure that neatly lists all the CLSIDs in one place. I can hardly imagine a COM DLL where the set of supported coclasses is conditional.Retardation
It depends on how sneaky the licensing scheme is. I definitely can envision a requirement to instantiate a license manager object and use it before any other classes can be instantiated, the question is whether the failure from attempting to create objects without a license is "I don't know that object" or "Tried to create that object but failed"Outleap
I believe there's a dedicated HRESULT for "class not licensed".Retardation
Probably. Doesn't mean every developer chooses to use it. There's also a dedicated reply for "TCP port closed", yet firewalls often don't use it.Outleap
For the record, it's CLASS_E_NOTLICENSED.Retardation
H
0

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

Hong answered 11/1, 2011 at 17:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.