5

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.

Guarita
  • 173
  • 1
  • 11
  • Have you checked this: http://support.microsoft.com/kb/895321/en-us – Simon Mourier Apr 05 '13 at 05:41
  • I'm not actually using the BSTR on my interface... In this specific case it is just a complex type (struct) with two Long variables and one Integer... This problem is happening only with Long vars. – Guarita Apr 05 '13 at 14:55
  • Is this VB6 specific: do you have trouble making calls from, say, C++ clients or Scripting clients? Also I suppose that some details about your COM component could be helpful. At least definition of the interface. – Ilya Kurnosov Apr 08 '13 at 20:19
  • Added some more interface information above. I haven't tried it from other clients. I'll try making this call from a C++ client... My server is written in Ada as I stated above and built using GNAT with GNATCOM (a COM library). – Guarita Apr 08 '13 at 21:57
  • "...why in the VB6 Object Browser there's no reference to the Variant type..." That's because `Variant` is default type in VB. "...definition here is using 'int' as the type of FirstLong... [and in] object explorer they are typed 'Long'..." That's OK if you use MIDL to compile your IDL. On 32-bit platforms `int` is synonymous with `long` for MIDL. If you don't compile with MIDL then we need more details. http://msdn.microsoft.com/en-us/library/windows/desktop/aa367090%28v=vs.85%29.aspx – Ilya Kurnosov Apr 09 '13 at 06:18
  • It would be nice to see IDL definition of `Parameter_Type` too. I also hope you didn't omit _attributes_ when posting IDL pieces. – Ilya Kurnosov Apr 09 '13 at 06:21
  • @Kurniliya: The IDL of the COM server has no attributes. All parameters are obtained through the put/get interface methods. I can't post Parameter_Type definition here because my enterprise information security policy does not allow me to. Parameter_Type is only an enumeration of the many variables supplied by the COM server. (in the end it's just an int). – Guarita Apr 09 '13 at 12:58
  • @Kurniliya: "That's OK if you use MIDL to compile your IDL. On 32-bit platforms int is synonymous with long for MIDL" Is this valid for WinXP SP3? I don't really understand what changed on this update but it interferred on the working of the COM server or client... It used to work before then. Could recompiling the server on the SP3 environment change anything on this? – Guarita Apr 09 '13 at 13:05
  • @Guarita Good question. I'm just saying that VB6 Object Browser shows info that matches IDL definitions just fine. Tagged question with [ada] in hope that someone knowledgable in GNATCOM stops by. – Ilya Kurnosov Apr 09 '13 at 13:20
  • @Guarita Can you show us the ADA declaration? – Mark Bertenshaw Apr 12 '13 at 12:19
  • @MarkBertenshaw: Actually I can't :-/. The ADA-written part is from a classified project. We use it as a component for simulation (my actual application). – Guarita Apr 16 '13 at 13:38
  • @Kurniliya: I have written the C++ client just now and found out that the structure (StructData) is 12 bytes long (using sizeof). It still returns the error described above. Tried to pad it to be 16 byte long and it still returns the same error. – Guarita Apr 16 '13 at 13:49
  • @HansPassant Bounty awarded for answer completeness although this hasn't still answered my original question. Did this to not waste the 50 reps invested and to award good explanation on the topic. If someone is able to solve this problem I'll open a bounty again (If I have the necessary rep by the time) and award him for justice. – Guarita Apr 16 '13 at 14:02

4 Answers4

8

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.

T.E.D.
  • 44,016
  • 10
  • 73
  • 134
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • "...Especially tricky in VB6 because you cannot directly control the guids that it uses..." Am I mistaken that this paragraph (generally correct, of course) should not be relevant for OP's particular situation? My impression was that he used VB6 in his _client_ only, not COM server. – Ilya Kurnosov Apr 10 '13 at 10:19
  • The client uses a proxy since it talks to the server. – Hans Passant Apr 10 '13 at 10:25
  • Yes. I used to believe that "selecting binary compatibility" is applicable to COM servers, not clients. – Ilya Kurnosov Apr 10 '13 at 10:39
  • 1
    That's correct. It is however very unclear in the OP's case where the original type library came from. With high odds that it came from a VB6 declaration since he's got a VB6 Type in his question, and used by the ADA server. – Hans Passant Apr 10 '13 at 10:48
  • The original type library comes from a IDL (hand-written) processed with make_idl to generate the ADA spec and the type library (.tlb). The ADA spec is added to our main project. In C++ we just #import the executable to load the namespace of the COM server (containing the COM server classes definitions - well, not exactly, I think it contains the stubs only.). – Guarita Apr 16 '13 at 13:56
0

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.

T.E.D.
  • 44,016
  • 10
  • 73
  • 134
  • COM is a binary standard, and is 32-bit only, so I would be surprised if the "bittishness" of the OS would make a difference. – Mark Bertenshaw Apr 12 '13 at 12:30
  • @MarkBertenshaw - It certianly makes a difference for the non-COM C code though. Many C types are difference sizes when compiled for 64-bit. – T.E.D. Apr 12 '13 at 13:36
  • Since the process is using COM, it all has to be 32-bit, including the Ada server, unless there is some clever code where the COM server communicates with a 64-bit process through a thunking layer. – Mark Bertenshaw Apr 12 '13 at 13:50
0

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)

Community
  • 1
  • 1
egilhh
  • 6,464
  • 1
  • 18
  • 19
  • Did this with a C++ client. Didn't work. Oh, and the struct is already padded when I query its size with sizeof() (12 bytes). Tried to pad it with an extra 'long' to complete 16 bytes and it still is returning the same error. – Guarita Apr 16 '13 at 14:05
0

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).

Mark Bertenshaw
  • 5,594
  • 2
  • 27
  • 40
  • "[...] so that the resulting type library struct [...]" - I didn't understand this one, you are saying I should change the type library for the definition of 'StructData' to be 12 bytes (multiple of four) long? – Guarita Apr 16 '13 at 14:17
  • No, you shouldn't change the type library, if it is being generated by the Ada compiler. You somehow need to force the compiler to use a layout that is suitable for use with VB. I can't find documentation for this compiler, so I am afraid the ball is in your court. Perhaps you could provide a link? – Mark Bertenshaw Apr 17 '13 at 16:28