I have developed a COM dll in c++. Now I have a MFC client which uses some of the interfaces exposed by the COM dll. When calling CoCreateInstance from this application, it fails with the following error:80020008 DISP_E_BADVARTYPE. I have added a new interface and a new method in my idl file. I wish to pass structure for which I have defined the structure(with UUID) in the idl file. Now I am passing it as a SAFEARRAY in my method.Please help me understand this error and when does it occur.
DISP_E_BADVARTYPE is a COM interop marshalling error. (Is your client compiled with CLR support?) It means that a type can't be marshalled because it's not supported - it's not CLR-compliant, and perhaps not Automation-compatible either.
One cause of this problem is mismatched integer types, such as passing a CLR long integer to a COM dual interface (64-bit integers are not Automation-compatible.)
Another possible cause might be your structure type. SafeArrays of user-defined types are Automation-compatible (VT_ARRAY|VT_RECORD) and so I expect it's possible to do what you want to do. But maybe there's something incompatible in your UDT. Or maybe you've specified it incorrectly, like maybe VT_SAFEARRAY or VT_USERDEFINED.
Can you post your IDL?
Related
I have a number of functions that look basically like…
Public Function Keys() As Dictionary(Of Integer, myData).KeyCollection
I'm trying to expose this class to COM so I can use it in Excel, but VS complains that…
Warning: Type library exporter encountered a generic type instance in a signature. Generic code may not be exported to COM.
I think this means I have to copy this to a type that can be exported to COM, I suspect a SAFEARRAY of strings?
Anyone have an example code of this?
You cannot expose generic types to COM, it has no support for it whatsoever. The reason that Dictionary doesn't have the [ComVisible(true)] attribute. Somewhat intuitively obvious, languages like C++, scripting languages like VBScript or Javascript, VB6 and VBA, etc, don't know beans about .NET generics. Only a .NET program can ever use it, you don't need COM interop for that.
If you want to expose a dictionary then you'll need to create your own or simply fall back to the olden Hashtable. Which is [ComVisible], your type library will automatically include mscorlib.tlb
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).
I am building an automation interface for an existing application. After implementing a DLL server and an EXE server (mainly for getting familiar with the basics of COM) I am now at the point where I generate a type library from an IDL file and can, for example, basically automate my application from VBScript:
Set oApp = CreateObject("MyApp.1")
oApp.ShowAboutBox()
This simple call to a function that takes no parameters works. The next step I want to take is call a function that takes a parameter.
The signature of the function in the IDL file is
HRESULT CreateSomeChildWindow([out, retval] MyChildWindow** ppChildWindow);
and in VBScript I assume it would be
Dim oWnd As MyChildWindow
oWnd = oApp.CreateSomeChildWindow()
This call already works in C++ although MyChildWindow is not currently registered as a COM object in the registry. The reason MyChildWindow doesn't need to be registered is that CreateSomeChildWindow simply returns the interface pointer to the created MyChildWindow object in a parameter. And the reason it isn't registered is that I want to avoid redundancy, and also I don't want MyChildWindow to be instantiated directly (e.g. by calling CreateObject in VBScript).
Question:
Now I'm trying to find out whether it will be necessary to register MyChildWindow after all. Is my assumption correct that in order to call CreateSomeChildWindow in VBScript
I need to write Dim oWnd As MyChildWindow
For this to work, MyChildWindow must be registered
And if the answer to that is yes, hopefully clients still can't MyChildWindow directly, since I don't implement a class object for it? Or will I have to implement a class object?
Your out/retval is not an object (on the scripting side), it is an interface pointer. And since the method CreateSomeChildWindow is on IDL, in type library, in registered type library - scripting/automation is aware of interface definition, such as methods etc, because the whole type library is already registered. You are already well set, no additional registration required.
When caller receives an interface pointer, it does not care what object the pointer belongs to. Interface pointer alone is good enough, and scripting/automation environment known how to deal with it.
On the callee side however, you need to return an interface pointer, and you are dealing with objects. So you need some class which implements this interface and you return this object's interface.
In the documentation for com it says that it works literally with every language. Do you need to have a specific API for that language so it can interface with com, or can any language literally just use it out of box? Also do you need a special compiler? Sorry if this is a stupid question but I have never used it before, and I have been trying to find this answer. When I look at demos of com examples it all seems to access the objects in a c style syntax, are their bindings and apis for other languages (literally all)?
The key thing about COM is that it is a "binary standard": which is to say that it doesn't care what the language used is, so long as the bits and bytes in memory end up in the right place.
COM basically specifies that all COM objects must have a specific layout in memory: the interface pointer points to a pointer that in turn points to a table of function pointers, which has at least three members, the first three of which are pointers to the IUnknown functions (AddRef, Release, QueryInterface), and the remainder are pointers to the other functions in the interface. COM also specifies how arguments are passed to these functions - so that the caller and callee agree on how the stack is used, and who pops off the values.
This requirement happily matches how C++ just happens to work on Windows; so most C++ classes that implement IUnknown will just happen to ends up as being valid COM classes: this is because Microsoft's implementation of C++ happens to use an object layout that matches what COM requires: the C++ object vtable pointer is the same as the COM pointer-to-table-of-function-pointers, the C++ table of function pointers is exactly what COM requires for its table-of-function-pointers, and so on. (This isn't entirely just a happy coincidence: COM was likely designed to take advantage of the most common way that C++ objects are implemented in memory which is the technique that MS's compiler uses. Note that C++ the language specification doesn't actually specify any particular object layout - so you could have a 3rd party C++ compiler that implemented C++ in a way that gave you classes that are not usable by COM. But no compiler vendor in their right mind would do that, since they would appear to be broken compared to the others!)
In plain C, you can create a COM object by creating suitable structs-containing-pointers manually. This works because C essentially allows you to specify binary-level memory layout for structs manually; you can create structs that you know will have the appropriate layout that COM is expecting.
In other languages, especially those that don't allow the user to specify memory layout explicitly, you need support from the language to allow for COM support. All the .Net languages - C#, VB.Net, and so on - use support in the .Net runtime that understands what COM expects, and produces the appropriate wrappers as needed to allow the interop to work.
So, long story short, it's not the case that any language under the sun will automatically work with COM; it's really the case that a couple of languages - namely C and C++ - are already aligned with COM's requirements; and most other languages will need some compiler support to make it work.
I'm using an external native COM component in my C# .NET application.
This COM DLL doesn't have a type library, so I had to write the interop code myself, and having include/idl files I did it like TlbImp does.
But the worst thing is that the object creation fails with:
Creating an instance of the COM component with CLSID {40700425-0080-11D2-851F-00C04FC21759} from the IClassFactory failed due to the following error: 80040111
The class is finally created if I use the native CoCreateInstance and specify class_id and one of the implemented interface IIDs.
As it turned out the problem lies in that the COM object's IClassFactory::CreateInstance doesn't support IID_IUnknown passed as the riid parameter, and therefore returns CLASS_E_CLASSNOTAVAILABLE (I identified it with disassembler and debugger). The component is MS SQL VDI.
Is there any way to force the .NET RCW to pass a different interface ID into the CreateInstance method rather than IID_IUnknown?
I searched the net a lot, but didn't find a solution for this.
As a workaround I'm using C++/CLI now to create the object, requesting the proper interface instead of IID_IUnknown for this purpose now; but I would like to have code in C#, because C++/CLI requires me to build a different DLL for each platform.
Thanks
I repro. Brr, painful. You could pinvoke CoCreateInstance:
[return: MarshalAs(UnmanagedType.Interface)]
[DllImport("ole32.dll", ExactSpelling=true, PreserveSig=false)]
public static extern object CoCreateInstance(ref Guid clsid,
[MarshalAs(UnmanagedType.Interface)] object punkOuter, int context, ref Guid iid);