GUID/ProgId/CLSID confusion - com

Trying to track down a COM problem, I'm debugging my code and seeming to see the same GUID represented different ways...
I have a line in our code:
class __declspec(uuid("{D4F83347-E58E-11d1-9D47-006008098294}"))
And various registry stuff in between, then a call to:
CLSID clsid;
::CLSIDFromProgID("myProgId",&clsid);
In the debugger, clsid is displayed as {000AFC9A-3347-D4F8-8EE5-D1119D470060}. To me this is too similar not to be right, but it's not something I can check automatically... we've got the D4F8 and 3347, 9D47, but E58E becomes 8EE5 etc.
Is there a way I can understand why this is happening, and a way I can get them to look the same for comparison?
EDIT
To clear up some side-tracking, I've checked and the CLSID in the Windows registry and our registration scripts is presented as {D4F83347-E58E-11d1-9D47-006008098294} too - so the issue over my uuid(...) is not relevant I think.

Using CLSIDFromProgID() when you already have the guid doesn't make much sense. The function looks in the registry to map the "ProgId" string to the CLSID {guid}. Which of course makes it important that the progid is registered properly. Sure sounds like it is not. When your class is already decorated with __declspec(uuid) then simply use the __uuidof() operator to retrieve the guid.
The similarity in the byte values suggests that your registration code is broken.

"__declspec(uuid" is only an association of the identifier to your class, nothing else. Using CLSIDFromProgID API you are resolving ProgID to CLSID using registration infromation in system registry. That is, the two don't have to match. They typically do match though if you do everything neatly and your COM class is registered with the same identifier as the one being attached in source code to the C++ class.

After some testing, I discovered that the issue was simply how the visual C++ debugger was displaying the value, nothing more. e.g the registry value is {D4F83347-E58E-11d1-9D47-006008098294}, calling ::StringToCLSID() on the result of CLSIDFromProgID() gives {D4F83347-E58E-11d1-9D47-006008098294} - but in the debugger MSVC++ displays the variable as {000AFC9A-3347-D4F8-8EE5-D1119D470060}.
Why it does that, is another question!

Related

Implements vs Binary Compatibility

I have one VB6 ActiveX DLL that exposes a class INewReport. I added some new methods to this class and I was able to rebuild it and keep binary compatibility.
I have a second DLL that exposes a class clsNewReport, which implements the first class using:
Implements RSInterfaces.INewReport
Since I added new methods to INewReport, I had to also add those new methods to clsNewReport.
However, when I try to compile the second DLL, I get the binary-compatibility error "...class implemented an interface in the version-compatible component, but not in the current project".
I'm not sure what is happening here. Since I'm only adding to the class, why can't I maintain binary compatibility with the second DLL? Is there any way around this?
I think this is a correct explanation of what is happening, and some potential workarounds.
I made up a test case which reproduced the problem in the description and then dumped the IDL using OLEView from the old & new DLL which contained the interface.
Here is a diff of the old (left) and new IDL from INewReport:
Important differences:
The UUID of interface _INewReport has changed
A typedef called INewReport___v0 has been added which refers to the original UUID of the interface
(I assume that this is also what is happening to the code referred to in the question.)
So now in the client project the bincomp DLL refers to the original interface UUID; but that UUID only matches against a different name (INewReport___v0 instead of INewReport) than it did originally. I think this is the reason VB6 thinks there is a bincomp mismatch.
How to fix this problem? I've not been able to do anything in VB6 that would allow you to use the updated interface DLL with the client code without having to break bincomp of the client code.
A (bad) option could be to just change the client DLL to use project compatibility... but that may or may not be acceptable in your circumstances. It could cause whatever uses the client DLL to break unless all the consumers were also recompiled. (And this could potentially cause a cascade of broken bincomp).
A better but more complex option would be to define the interface in IDL itself, use the MIDL compiler to generate a typelib (TLB file), and reference that directly. Then you would have full control over the interface naming, etc. You could use the IDL generated from OLEView as a starting point for doing this.
This second option assumes that the interface class is really truly an interface only and has no functional code in it.
Here's how I setup a case to reproduce this:
Step 1. Original interface definition - class called INewReport set to binary compatible:
Sub ProcA()
End Sub
Sub ProcB()
End Sub
Step 2. Create a test client DLL which implements INewReport, also set to binary compatible:
Implements INewReport
Sub INewReport_ProcA()
End Sub
Sub INewReport_ProcB()
End Sub
Step 3: Add ProcC to INewReport and recompile (which also registers the newly built DLL):
(above code, plus:)
Sub ProcC()
End Sub
Step 4: Try to run or compile the test client DLL - instantly get the OP's error. No need to change any references or anything at all.
I was able to recreate your problem, using something similar to DaveInCaz's code. I tried a number of things to fix it, probably repeating things you've already tried. I came up with a possible hypothesis as to why this is happening. It doesn't fix the problem, but it may throw some additional light on it.
Quoting from This doc page:
To ensure compatibility, Visual Basic places certain restrictions on changes you make to default interfaces. Visual Basic allows you to add new classes, and to enhance the default interface of any existing class by adding properties and methods. Removing classes, properties, or methods, or changing the arguments of existing properties or methods, will cause Visual Basic to issue incompatibility warnings.
Another quote:
The ActiveX rule you must follow to ensure compatibility with multiple interfaces is simple: once an interface is in use, it can never be changed. The interface ID of a standard interface is fixed by the type library that defines the interface.
So, here's a hypothesis. The first quote mentions the default interface, which suggests that it may not be possible to alter custom interfaces in any way. That's suggested by the second quote as well. You're able to alter the interface class, because you are essentially altering its default interface. However, when you attempt to alter the implementing class in kind, to reflect the changes in your interface, your implementation reference is pointing to the older version of the interface, which no longer exists. Of course, the error message doesn't hint at this at all, because it appears to be based on the idea that you didn't attempt to implement the interface.
I haven't been able to prove this, but looking at DaveInCaz's answer, the fact that the UUID has changed seems to bear this idea out.

Error 0x800706F7 "The stub received bad data" on Windows XP SP3

In my VB6 application I make several calls to a COM server my team created from a Ada project (using GNATCOM). There are basically 2 methods available on the COM server. Their prototypes in VB are:
Sub PutParam(Param As Parameter_Type, Value)
Function GetParam(Param As Parameter_Type)
where Parameter_Type is an enumerated type which distinguishes the many parameters I can put to/get from the COM server and 'Value' is a Variant type variable. PutParam() receives a variant and GetParam() returns a variant. (I don't really know why in the VB6 Object Browser there's no reference to the Variant type on the COM server interface...).
The product of this project has been used continuously this way for years without any problems in this interface on computers with Windows XP with SP2. On computers with WinXP SP3 we get the error 0x800706F7 "The stub received bad data" when trying to put parameters with the 'Long' type.
Does anybody have any clue on what could be causing this? The COM server is still being built in a system with SP2. Should make any difference building it on a system with SP3? (like when we build for X64 in X64 systems).
One of the calls that are causing the problem is the following (changed some var names):
Dim StructData As StructData_Type
StructData.FirstLong = 1234567
StructData.SecondLong = 8901234
StructData.Status = True
ComServer.PutParam(StructDataParamType, StructData)
Where the definition of StructData_Type is:
Type StructData_Type
FirstLong As Long
SecondLong As Long
Status As Boolean
End Type
(the following has been added after the question was first posted)
The definition of the primitive calls on the interface of the COM server in IDL are presented below:
// Service to receive data
HRESULT PutParam([in] Parameter_Type Param, [in] VARIANT *Value);
//Service to send requested data
HRESULT GetParam([in] Parameter_Type Param, [out, retval] VARIANT *Value);
The definition of the structure I'm trying to pass is:
struct StructData_Type
{
int FirstLong;
int SecondLong;
VARIANT_BOOL Status;
} StructData_Type;
I found it strange that this definition here is using 'int' as the type of FirstLong and SeconLong and when I check the VB6 object explorer they are typed 'Long'. Btw, when I do extract the IDL from the COM server (using a specific utility) those parameters are defined as Long.
Update:
I have tested the same code with a version of my COM server compiled for Windows 7 (different version of GNAT, same GNATCOM version) and it works! I don't really know what happened here. I'll keep trying to identify the problem on WinXP SP3 but It is good to know that it works on Win7. If you have a similar problem it may be good to try to migrate to Win7.
I'll focus on explaining what the error means, there are too few hints in the question to provide a simple answer.
A "stub" is used in COM when you make calls across an execution boundary. It wasn't stated explicitly in the question but your Ada program is probably an EXE and implements an out-of-process COM server. Crossing the boundary between processes in Windows is difficult due to their strong isolation. This is done in Windows by RPC, Remote Procedure Call, a protocol for making calls across such boundaries, a network being the typical case.
To make an RPC call, the arguments of a function must be serialized into a network packet. COM doesn't know how to do this because it doesn't know enough about the actual arguments to a function, it needs the help of a proxy. A piece of code that does know what the argument types are. On the receiving end is a very similar piece of code that does the exact opposite of what the proxy does. It deserializes the arguments and makes the internal call. This is the stub.
One way this can fail is when the stub receives a network packet and it contains more or less data than required for the function argument values. Clearly it won't know what to do with that packet, there is no sensible way to turn that into a StructData_Type value, and it will fail with "The stub received bad data" error.
So the very first explanation for this error to consider is a DLL Hell problem. A mismatch between the proxy and the stub. If this app has been stable for a long time then this is not a happy explanation.
There's another aspect about your code snippet that is likely to induce this problem. Structures are very troublesome beasts in software, their members are aligned to their natural storage boundary and the alignment rules are subject to interpretation by the respective compilers. This can certainly be the case for the structure you quoted. It needs 10 bytes to store the fields, 4 + 4 + 2 and they align naturally. But the structure is actually 12 bytes long. Two bytes are padded at the end to ensure that the ints still align when the structure is stored in an array. It also makes COM's job very difficult, since COM hides implementation detail and structure alignment is a massive detail. It needs help to copy a structure, the job of the IRecordInfo interface. The stub will also fail when it cannot find an implementation of that interface.
I'll talk a bit about the proxy, stub and IRecordInfo. There are two basic ways a proxy/stub pair are generated. One way is by describing the interfaces in a language called IDL, Interface Description Language, and compile that with MIDL. That compiler is capable of auto-generating the proxy/stub code, since it knows the function argument types. You'll get a DLL that needs to be registered on both the client and the server. Your server might be using that, I don't know.
The second way is what VB6 uses, it takes advantage of a universal proxy that's built into Windows. Called FactoryBuffer, its CLSID is {00000320-0000-0000-C000-000000000046}. It works by using a type library. A type library is a machine readable description of the functions in a COM server, good enough for FactoryBuffer to figure out how to serialize the function arguments. This type library is also the one that provides the info that IRecordInfo needs to figure out how the members of a structure are aligned. I don't know how it is done on the server side, never heard of GNATCOM before.
So a strong explanation for this problem is that you are having a problem with the type library. Especially tricky in VB6 because you cannot directly control the guids that it uses. It likes to generate new ones when you make trivial changes, the only way to avoid it is by selecting the binary compatibility option. Which uses an old copy of the type library and tries to keep the new one as compatible as possible. If you don't have that option turned on then do expect trouble, especially for the guid of the structure. Kaboom if it changed and the other end is still using the old guid.
Just some hints on where to start looking. Do not assume it is a problem caused by SP3, this COM infrastructure hasn't changed for a very long time. But certainly expect this kind of problem due to a new operating system version being installed and having to re-register everything. SysInternals' ProcMon is a good utility to see the programs use the registry to find the proxy, stub and type library. And you'd certainly get help from a COM Spy kind of utility, albeit that they are very hard to find these days.
If it suddenly stopped working happily on XP, the first culprit I'd look for is type mismatches. It is possible that "long" on such systems is now 64-bits, while your Ada COM code (and/or perhaps your C ints) are exepecting 32-bits. With a traditionally-compiled system this would have been checked for you by your compiler, but the extra indirection you have with COM makes that difficult.
The bit you wrote in there about "when we compile for 64-bit systems" makes me particularly leery. 64-bit compiles may change the size of many C types, you know.
This Related Post suggests you need padding in your struct, as marshalling code may expect more data than you actually send (which is a bug, of course). Your struct contains 9 bytes (assuming 4 bytes for each of the ints/longs and one for the boolean). Try to add padding so that your struct contains a multiple of 4 bytes (or, failing that, multiple of 8, as the post isn't clear on the expected size)
I am also suggesting that the problem is due to a padding issue in your structure. I don't know whether you can control this using a #pragma, but it might be worth looking at your documentation.
I think it would be a good idea to try and patch your struct so that the resulting type library struct is a multiple of four (or eight). Your Status member takes up 2 bytes, so maybe you should insert a dummy value of the same type either before or after Status - which should bring it up to 12 bytes (if packing to eight bytes, this would have to be three dummy variables).

How to get Imported type libraries from an OCX or TLB file?

I was convinced that there is no way to find COM dependencies of an ActiveX but to my surprise OLEVIEW shows some comments Like:
// TLib : // TLib : OLE Automation : {00020430-0000-0000-C000-000000000046}
importlib("stdole2.tlb");
// TLib : Visual Basic runtime objects and procedures : {EA544A21-C82D-11D1-A3E4-00A0C90AEA82}
importlib("3");
I tried to extract the same information using TypeLibInfoFromFile but based on what I find in MSDN, there is no Api that provides this information. Are you aware of a method to extract this information from OCX or it's Tlb file? Knowing that all my ocxes are compiled with vb6 can I trust this informaion for Imported(Explicitly not in code) interfaces?
Well, I've found the answer to this question. I'll write it here just in case someone would search for the same question. It's possible to find some of dependencies but you can never be sure if you have found them all. Basically you must enumerate every type and interface, and every member of each type to find all types in the module and for every type you find you should check to see if it's in an external TypeLib. in the end you have a List of Typelibs referenced.
The problem with this method lays in the fact you find only the types that are used in the public interface (fields, return values and parameters) and you miss every local object or dynamically created ones. That said you can check this link for an implementation or better yet this one.

How do I know whether an application support OLE2 and which methods & attributes are exposed?

I want to call an ActiveX DLL or OLE2 object from ABAP.
I already know the syntax of how to instantiate the object & execute the methods:
data: my_object type ole2_object.
create object my_object <ole2object>.
call method of my_object <objectmethod>.
But given a particular application, how do I know if this is supported, what the values/names of ole2object and objectmethod is?
Transaction SOLE supplies a table of OLE Applications, among this is Excel.Application which I know can be instantiated as an OLE object, so it looks like you have to add the OLE2 app to this table first, but once again where can I read the data like CLSID & LibType from - is it published as part of the application?
Objects are coming from the table TOLE. Their methods and propeties are stored into table OLELOAD.
I found some time ago somes documents (this one and this one) that contained indications of how you can find the properties and methods.
Indication on the CLSID are succint, but it seems to be the value in the register with HKEY_CLASS_ROOt/CLSID that goes with the application indicated (ie VISIO.APPLICATION for exemple). If you search this application value with regedit, you'll find the corresponding CLSID.
Hope it's helping
Guillaume
In this case, you're simply using OLE2 to access a COM interface. If you're into accessing Office applications, you might want to take a look at KB222101. For other applications and libraries, you'll need the API documentation, some suitable examples (not necessarily in ABAP, VB will do just fine). a pointed stick to poke the developer with or all three of them...
Oh, and there's a tool called OLE/COM Object Explorer by MiTeC that can be downloaded for free from their website. Haven't used it myself, but it looks like it might be helpful.

CLSIDFromProgID is successful but CreateInstace fails! Why?

I am trying to create an instance of a COM object. I have the class name that implements the interface and I get a CLSID by using CLSIDFromProgID(). So since I am getting a CLSID I thought everything should be fine from now on. However when I do a call to CreateInstance and pass in the CLSID, I get an error saying "Class not registered". Also I get this error only in some computers. It runs error free on several computers. I don't understand where the problem could be. Is my registry dirty? Does anyone know what is going on here? Thanks for your help!
I just want to add that this is a .NET COM class. The appropriate entries are in the registry and the DLL is in the GAC.
CLSIDFromProgId is simply looking up the ProgId's name in the registry and translating it to a CLSID, it doesn't have to look at anything beyond the registry or even check that something is actually implementing that CLSID.
When you call CreateInstance on the CLSID, Windows will look up in the registry to find out how the object should be instantiated (usually a exe or dll). It will then try to load the dll (or start up the exe) and create the object from it.
There is a lot of documentation in MSDN on the processes involved, for example see "COM Class Objects and CLSIDs", and if you do a lot of COM work it is worthwhile learning the process from first principals since it can save a lot of time and hassle when debugging this type of issue.
It's a two step process in the registry. You used the ProgID to get the CLSID. Then, when you call CreateInstance, COM then uses the CLSID to find the path to the dll. You can used regedit yourself to lookup the CLSID and see what that entry looks like.
Thanks for your answers. The .Net assemblies were registered properly and were present in the GAC. One application that absolutely confirmed this was Process Explorer. You can view the dlls that are loaded by each application. So from here I was able to see if the application that was instantiating the COM objects was actually able to load the DLLs or not. I found out that this was indeed happening. The problem was due to different Regional settings. We found that the application threw an exception when the region was not set to US. This issue was fixed. The error message "Class not registered" was not very helpful. Thankfully it was a quick fix.
Using shell32 as an example, you can create a new instance like so;
var shl = (Shell) Activator.CreateInstance(Type.GetTypeFromProgID("Shell.Application"));
This will aquire a refernce to an existing component;
var shl2 = (Shell) Marshal.GetActiveObject("Shell.Application");
Here's a reference to how to do the same in IronPython.
** Note, this used the progid, clsid would be nearly identical, just use Type.GetTypeFromCLSID({GUID}).