create vbscript vbdataobject - vbscript

I am using a program which makes it possible to customize dialogues via vbscript. Now, the form has an attribute which seems to be a vbDataObject (VarType returns 13).
How can I read the content and create a new vbDataObject to assign it to the attribute?

Don't know if this is exactly the answer, but it seems that vbDataObject's are indeed COM objects, but they just do not expose an IDispatch interface (so they're not true automation objects). As such, VBScript just isn't capable of accessing their content, since it needs an IDispatch interface for it. It just has an IUnknown interface which it doesn't know what to do with.
Passing them around should be possible, though, so if another true automation COM object can manipulate them for you (maybe there is such an object in the app designed to do this?), you could use that to make an object with the correct settings and pass that to the form.
Another option is to contact the authors of the app and ask them if they're willing to give this one object an IDispatch interface as well. Since they presumably went the length to put IDispatch interfaces on most of their objects, this omission might just be an oversight on their part.

Related

Standard COM marshaler fails with REGDB_E_IIDNOTREG

I'm trying to marshal an interface to another thread.
Windows provides the convenient CoMarshalInterThreadInterfaceInStream helper function to take care of the boilerplate code associated with using CoMarshalInterface directly.
const Guid CLSID_Widget = "{F8383852-FCD3-11d1-A6B9-006097DF5BD4}";
const Guid IID_IWidget = "{EBBC7C04-315E-11D2-B62F-006097DF5BD4}";
//Create our widget
HRESULT hr = CoCreateInstance(CLSID_Widget, null,
CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER,
IID_IWidget, out widget);
OleCheck(hr);
//Marshall the interface into an IStream
IStream stm;
hr = CoMarshalInterThreadInterfaceInStream(IID_IWidget, widget, out stm);
OleCheck(hr);
Except that the call to CoMarshalThreadInterfaceInStream fails with:
REGDB_E_IIDNOTREG (0x80040155)
Interface not registered
Go directly to CoMarshalInterface
The COM API function CoMarshalInterThreadInterfaceInStream provides a simple wrapper around CreateStreamOnHGlobal and CoMarshalInterface, as shown here:
// from OLE32.DLL (approx.)
HRESULT CoMarsha1InterThreadInterfaceInStream(
REFIID riid, IUnknown *pItf, IStream **ppStm)
{
HRESULT hr = CreateStreamOnHGlobal(0, TRUE, ppStm);
if (SUCCEEDED(hr))
hr = CoMarshalInterface(*ppStm, riid, pItf,
MSHCTX_INPROC, 0, MSHLFLAGS_NORMAL);
return hr;
}
So we can try ourselves.
IStream stm = new Stream()
hr = CoMarshallInterface(stm, IID_IWidget, widget,
MSHCTX_INPROC, // destination context is in-process/same host
NULL, // reserved, must be null
MSHLFLAGS_NORMAL // marshal once, unmarshal once
);
OleCheck(hr);
But that fails with:
REGDB_E_IIDNOTREG (0x80040155)
Interface not registered
Use standard marshaling
My class does not implement IMarhsal interface. This is right and normal.
By default, when CoMarshalInterface is first called on an object, the object is asked whether it wishes to handle its own cross-apartment communications. This question comes in the form of a QueryInterface request for the IMarshal interface. Most objects do not implement the IMarshal interface and fail this
QueryInterface request, indicating that they are perfectly happy to let COM
handle all communications via ORPC calls. Objects that do implement the
IMarshal interface are indicating that ORPC is inappropriate and that the object implementor would prefer to handle all cross-apartment communications
via a custom proxy. When an object implements the IMarshalinterface all references to the object will be custom marshaled.
When an object does not implement the IMarshal interface, all references to the object will be standard marshaled. Most objects elect to use standard marshaling.
So the the question becomes why is the standard COM marshaler having so much problems? What is the source of the error Interface not registered?
The interface is, in fact, not registered
The requires for COM are undocumented, but i can tell you that my interface GUID does not exist in:
HKEY_CLASSES_ROOT\Interface\{EBBC7C04-315E-11D2-B62F-006097DF5BD4}
The reason for this will be explained at the end.
I know that Windows provides you the CoRegisterPSClsid function that allows you to register an interface inside your process, so that the standard marshaler will be able to marshal it:
Enables a downloaded DLL to register its custom interfaces within its running process so that the marshaling code will be able to marshal those interfaces.
HRESULT CoRegisterPSClsid(
_In_ REFIID riid,
_In_ REFCLSID rclsid
);
Parameters:
riid [in]: A pointer to the IID of the interface to be registered.
rclsid [in]: A pointer to the CLSID of the DLL that contains the proxy/stub code for the custom interface specified by riid.
Which i can try calling, but what clsid do i use?
CoRegisterPSClsid(IID_IWidget, ???);
What is the CLSID of the DLL that contains the proxy/stub code for the custom interface specified by riid? Do i use my class itself?
CoRegisterPSClsid(IID_IWidget, CLSID_Widget);
That doesn't sound right; but i don't understand the COM standard marshaler well enough. Doesn't that CLSID have to be one of the standard COM marsharling classes; implementing IPSFactoryBuffer?
Either way, it doesn't work. I still get the error "Interface not registered".
Register the interface
I of course can register my interface in the registry:
HKEY_CURRENT_USER\Software\Classes\Interface\{EBBC7C04-315E-11D2-B62F-006097DF5BD4}
(default) = "IWidget"
But that doesn't fix it. Spelunking through the Interface registry keys, i notice that many specify a ProxyStubClsid32 entry.
When a new interface is requested on an object, the proxy and stub managers
must resolve the requested IID onto the CLSID of the interface marshaler.
Under Windows NT 5.0, the class store maintains these mappings in the NT
directory, and they are cached at each host machine in the local registry. The
machine-wide IID-to-CLSID mappings are cached at
HKEY_CLASSES_ROOT\Interface
and the per-user mappings are cached at
HKEY_CURRENT_USER\Software\Classes\Interface
One or both of these keys will contain a subkey for each known interface. If the interface has an interface marshaler installed, there will be an additional
subkey (ProxyStubClsid32) that indicates the CLSID of the interface
marshaler.
Except what class implements marshaling? I don't have a marshaler.
Can COM automatically marshal based on a TypeLibrary
Is it possible that if i register a Type Library with my Interface, that COM's standard marshaler will be able to bootstrap a proxy class on the fly?
I registered my interface above. Now i manually include the TypeLibrary:
HKEY_CURRENT_USER\Software\Classes\Interface\{EBBC7C04-315E-11D2-B62F-006097DF5BD4}\TypeLib
(default) = "{38D528BD-4948-4F28-8E5E-141A51090580}"
And if i monitor the registry during the call to CoMarshalInterface i see that it attempts, and does find, my Interface IID:
Operation: RegOpenKey
Path: HKCR\WOW6432Node\Interface\{EBBC7C04-315E-11D2-B62F-006097DF5BD4}
Result: SUCCESS
It then tries to look for a ProxyStubClsid32, and fails:
Operation: RegOpenKey
Path: HKCR\WOW6432Node\Interface\{668790E3-83CC-47E0-907F-A44BA9A99C8D}\ProxyStubClsid32
Result: NAME NOT FOUND
My hope would then be that the standard COM marshaler attempts to look for:
Operation: RegOpenKey
Path: HKCR\WOW6432Node\Interface\{668790E3-83CC-47E0-907F-A44BA9A99C8D}\TypeLib
But it doesn't.
The OLE Automation marshaler
According to Don Box, the Ole Automation marshaler (PSOAInterface - {00020424-0000-0000-C000-000000000046}) is able to build a stub/proxy out of a type library:
The Type Library Marshaler
When these specially annotated interfaces are encountered by RegisterTypeLib (or LoadTypeLib in legacy mode), COM adds ProxyStubClsid32 entries for the interface with the value {00020424-0000-0000-C0000-000000000046}. This GUID corresponds to the class PSOAInterface that is registered as living in OLEAUT32.DLL, the OLE automation DLL. Because of the DLL that it lives in, this marshaler is sometimes called the [oleautomation] marshaler, although it is also called the type library marshaler or the universal marshaler. I'll refer to it as the type library marshaler, since it really has very little to do with IDispatch. (In fact, it is common to use the [oleautomation] attribute on interfaces that don't derive from IDispatch directly or indirectly.)
The class factory for the type library marshaler does something very tricky in its CreateProxy and CreateStub routines. Rather than return a statically compiled vtable (which is impossible given the fact that the requested interface didn't exist when OLEAUT32.DLL was built as part of the OS), the type library marshaler actually builds an /Oicf-style proxy and stub based on the type library for the interface. Because there is no efficient way to find the ITypeInfo for an arbitrary interface, the LIBID and version of the interface's type library must be stored under the:
HKCR\Interface\{XXX}\TypeLib
registry key.
I tried to set PSOAInterface as my interface's ProxyStub class:
Register standard COM class PSOAInterface as our proxy stub clsid
HKEY_CURRENT_USER\Software\Classes\Interface\{EBBC7C04-315E-11D2-B62F-006097DF5BD4}\ProxyStubClsid32
(default) = "{00020424-0000-0000-C0000-000000000046}"
Register our type library for our interface
HKEY_CURRENT_USER\Software\Classes\Interface\{EBBC7C04-315E-11D2-B62F-006097DF5BD4}\TypeLib
(default) = "{38D528BD-4948-4F28-8E5E-141A51090580}"
Version = "1.0"
The type library itself is already registered:
HKEY_CURRENT_USER\Software\TypeLib\{38D528BD-4948-4F28-8E5E-141A51090580}\1.0\0\win32
(default) = "D:\Junk\ComLibraryProxyTest\Win32\Project1.dll"
But it still fails.
it reads HKCR\Interface\[IID_IWidget]
it reads HKCR\Interface\[IID_IWidget]\ProxyStubClsid32
But it:
never reads: HKCR\Interface\[IID_IWidget]\TypeLib
So it fails to proxy the object.
Questions
Is it possible for the standard COM "Ole Automation" marshaler to build a proxy class out of a Type Library at runtime?
Is it possible for me to build a proxy class out of a Type Library at runtime?
Background
CoMarshalInterface takes an interface pointer on input and writes the
serialized representation of the pointer to a caller-provided byte stream. This byte stream can then be passed to another apartment, where the
CoUnmarshalInterface API function uses the byte stream to return an interface
pointer that is semantically equivalent to the original object yet can be
legally accessed in the apartment that executes the CoUnmarshalInterface
call. When calling CoMarshalInterface, the caller must indicate how far away
the importing apartment is expected to be. COM defines an enumeration for
expressing this distance:
enum MSHCTX {
MSHCTX_INPROC = 4, // in-process/same host
MSHCTX_LOCAL = 0, // out-of-process/same host
MSHCTX_NOSHAREDMEM = 1, //16/32 bit/same host
MSHCTX_DIFFERENTMACHINE = 2, // off-host
};
It is legal to specify a greater distance than required, but it is more efficient
to use the correct MSHCTX when possible. CoMarshalInterface also allows
the caller to specify the marshaling semantics using the following marshal flags:
enum MSHLFLAGS {
MSHLFLAGS_NORMAL, // marshal once, unmarshal once
MSHLFLAGS_TABLESTRONG, // marshal once, unmarshal many
MSHLFLAGS_TABLEWEAK, // marshal once, unmarshal many
MSHLFlAGS_NOPING = 4 // suppress dist. garbage collection
Normal marshaling (sometimes called call marshaling) indicates that the
marshaled object reference must be unmarshaled only once, and if additional
proxies are needed, additional calls to CoMarshalInterface are required. Table
marshaling indicates that the marshaled object reference may be unmarshaled
zero or more times without requiring additional calls to CoMarshalInterface.
I think that all COM object type libraries, when compiled by MIDL, can automatically create a proxy/stub factory. But in my case:
if the COM standard marshaler can't find a proxy/stub factory for an interface, it returns the error REGDB_E_IIDNOTREG.
It may be the case that i have to either:
use CreateProxyFromTypeInfo and CreateStubFromTypeInfo to create my own proxy
let the standard COM marshaler automatically create a proxy/stub if there's a typeinfo chunk associated with the interface GUID.
Bonus Reading
Old New Thing: What are the rules for CoMarshalInterThreadInterfaceInStream and CoGetInterfaceAndReleaseStream?
Old New Thing: Why do I get the error REGDB_E_IIDNOTREG when I call a method that returns an interface?
Old New Thing: Why do I get E_NOINTERFACE when creating an object that supports that interface?
Don Box - MSJ: Standard Marshalling in COM (archive)
Mysterious disappearing acts by samples from Microsoft SDKs
What CoMarshalInterface actually needs is IMarshal implementation (pointer) for the given interface, so that API could request the marshaling magic from it and, in particular, request IMarshal::GetUnmarshalClass in order to obtain information who is going to do the reverse magic afterwards:
This method is called indirectly, in a call to CoMarshalInterface, by whatever code in the server process is responsible for marshaling a pointer to an interface on an object. This marshaling code is usually a stub generated by COM for one of several interfaces that can marshal a pointer to an interface implemented on an entirely different object.
You don't have IMarshal implemented on your widget, so you are going to get it somewhere from.
As you started your question mentioning that you "want to marshal an interface to another thread" and the code comment says "Create our widget" there is a chance that you can utilize IMarhsal implementation of free threaded marshaler. The question does not provide information to tell whether it is possible and/or acceptable solution.
Back to the challenge of obtaining a marshaler, you are trying to work this around by utilizing a "standard" marshaler by "registering the interface":
why is the standard COM marshaler having so much problems
[...]
I of course can register my interface in the registry
Well, this is not how things actually work.
Interface registration is not just a registry key that "okay, this IID has its own key in the registry". The purpose of such registration is to point where to look for proxy/stub pair for this interface. Your manual creating of registry entries cannot help here if you don't have a proxy/stub DLL for the interface in question. If you had it, you would just regsvr32 it the usual way to create the registry keys.
So called standard marshaler, your next try, is not supposed to marshal any interface and your IWidget in particular. OLE supplies so called "PSOAInterface" marshaler, which is capable of supplying proxy/stub pairs for OLE Automation compatible interfaces. Just for them! The marshaler does not have so much problems, it actually has just one: your IWidget is unlikely to be compatible or you would not have the problem in first place.
If your IWidget was compatible, had an associated type library, where it was marked as [oleautomation], the type library registration process would have automatically created the registry keys referencing PSOAInterface and supplying ProxyStubClsid32. Then marshaling API would have picked PSOAInterface for your widget, it would have picked up the registered type library, load details of the interface, then provided standard proxy/stub pair for it and that's it. Standard marshaler works just within these contraints and not just for any interface pointed to.
That is, your options are:
implement IMarshal on the widget server
make IWidget OLE Automation compatible, with type library, so that type library registration process activates "standard marshaler" PSOAInterface for your interface
build, if possible and applicable, proxy/stub implementation for your interface automatically generated by MIDL complier (you can check standard ATL DLL project, created from Visual Studio template on how it can be done - it creates additional project with "PS" suffix, for a supplementary proxy/stub DLL)
implement separately IMarshal in a standalone DLL, and register it with the registry and your IWidget interface, so that marshaling API would pick it up when it attempts to marshal the interface pointer (I suppose it's the only option here if your IWidget is OLE incompatible and you have reasons to not alter the original implementation)
use free threaded marshaler in the widget server if its restrictions are acceptable
Oh wait, hold on - there is another weird one. If you don't want or cannot afford, or you are reluctant to change the COM server (widget, that is) but you can modify client side code as you like, you can create a thin wrapper that implements two interfaces IWidget (all methods forward calls to real server) and IMarshal on client side, and pass its IWidget to the CoMarshalInterThreadInterfaceInStream API. This will force COM to use your marshaling without altering the original server. You are on your own, of course, to do the actual marshaling afterwards. It is unlikely that it matches your actual need, and it is just an abstract note to the discussion (which mostly consists of attempts to do impossible without details on interface itself and available options on modification of server implementation).
TL;DR: Actual questions:
Is it possible for the standard COM "Ole Automation" marshaler to build a proxy class out of a Type Library at runtime?
Short answer: yes.
Is it possible for me to build a proxy class out of a Type Library at runtime?
Short answer: yes, with the type library marshaler and IPSFactoryBuffer. Or if you're willing to use the undocumented CreateProxyFromTypeInfo and CreateStubFromTypeInfo.
I wonder why you'd want to this.
This question is riddled with red herrings.
I'm trying to marshal an interface to another thread.
(...) the call to CoMarshalThreadInterfaceInStream fails with:
REGDB_E_IIDNOTREG (0x80040155)
Interface not registered
A useful error code, in this case.
Go directly to CoMarshalInterface
(...) that fails with:
REGDB_E_IIDNOTREG (0x80040155)
Interface not registered
It's not by switching API calls that do essentially the same thing that you could solve your issue.
Use standard marshaling
My class does not implement IMarhsal interface.
Can you confirm it doesn't implement INoMarshal, IStdMarshalInfo and IAgileObject either, just to be exhaustive?
The interface is, in fact, not registered
This was expected.
I know that Windows provides you the CoRegisterPSClsid
(...)
Which i can try calling, but what clsid do i use?
CoRegisterPSClsid(IID_IWidget, ???);
If you don't have a proxy/stub, why would you go through this?
But to answer this question, a standard marshalers' CLSID is usually the same GUID as the first IID found in an IDL file.
Register the interface
I of course can register my interface in the registry:
HKEY_CURRENT_USER\Software\Classes\Interface\{EBBC7C04-315E-11D2-B62F-006097DF5BD4}
(default) = "IWidget"
But that doesn't fix it. Spelunking through the Interface registry keys, i notice that many specify a ProxyStubClsid32 entry.
You should read the documentation, instead of relying on what other registry entries contain.
(...) I don't have a marshaler.
This should be the actual problem. If this object is yours, how do you intend to marshal it? Please see the bottom section of my answer.
Can COM automatically marshal based on a TypeLibrary
This is clearly rhetoric. Why would you think of a type library just now, instead of to begin with? The rest of your question corroborates.
Now i manually include the TypeLibrary:
HKEY_CURRENT_USER\Software\Classes\Interface\{EBBC7C04-315E-11D2-B62F-006097DF5BD4}\TypeLib
(default) = "{38D528BD-4948-4F28-8E5E-141A51090580}"
You mention this twice.
But it:
never reads: HKCR\Interface[IID_IWidget]\TypeLib
The type library marshaler has its own cache of interface->type library. To clear the cache, try recreating the registry entries, logging off and logging on, or ultimately, reboot.
The object may implement IProvideClassInfo, I don't know if the type library marshaler actually cares to QueryInterface for this to fetch a runtime ITypeInfo to work with.
These are the main types of marshalers:
Standard marshalers, which are usually compiled into a DLL from C/C++ source code generated by MIDL (which consists mainly of declarations on how to marshal types)
The type library marshaler, which can marshal types at runtime based on automation-compatible type information
The free-threaded marshaler aggregated with CoCreateFreeThreadedMarshaler, which avoids marshaling between threads within the same process
Custom marshalers, which do whatever the developer wants, most commonly to implement marshal-by-value, or a free-threaded marshaler back when CoCreateFreeThreadedMarshaler didn't exist
The standard marshalers generated by MIDL consist mainly of declarations on how to marshal types in and out, so the way they work is in the same vein as the type library marshaler. According to Don Box, the result is very similar.
However, the actual declarations are very different. The type library marshaler works on type information that is meant to work in VB6 and (excluding certain things, such as user-defined types) scripting languages (mainly VBScript and JScript) and is intended to be consumed by IDEs (e.g. VB6, VBA, Delphi, OleView) and interop code generators (e.g. VC++ #import, tlbimp.exe, Delphi's "Import Type Library", Lisp 1 2), etc.

Registering Window Class

I guess my question is relatively easy for those of you who spent time dealing with Win32 API.
So my question is:
After initializing a WNDCLASSEX instance we need to "register" it using the "RegisterClassEx" function, why? Why do we do that? What's the meaning of this registration and in what cases I need to register things?
The ATOM returned by RegisterClassEx uniquely identifies your "window class" which can then be referred to in other windows APIs. [MSDN]
Effectively it is a hash so as to reduce the amount of data processed each time a window is created or looked for. It does also mean that multiple windows with same features can be easily created and identified.
I was addressing the practical reasons above. Hans Passant's answer correctly explains this is the OO class concept provided for C. Further MSDN example.
The word Class in the function name is significant. When you write code in an object oriented language, like C++, Delphi, Java or C# etcetera, then you use the class keyword to create objects that have behavior. But the winapi was designed to be used from C, a language that doesn't have such functionality. The RegisterClassEx() function is an emulation of that, it lets you create a window that "derives" its behavior from a named class, behavior that you can override. With every window that you create using that class name behaving identically.
The WNDCLASSEX structure you pass gives a window its default behavior. The most significant members of this structure are:
lpszClassName. That's the equivalent of the C++ class name. You can later call CreateWindowEx() and pass that name to get a window that behaves a certain way. Windows itself calls RegisterClassEx() to register several of its built-in window classes that you then can readily re-use in your own code. "EDIT", "BUTTON" and "LISTBOX" are good examples of that.
lpfnWndProc. This is what gives a window class its specific default behavior. The address of its window procedure that implement message handlers for specific messages. You can further customize the default behavior, in other words "derive" your own class from the base class, by specifying another window procedure in the CreateWindowEx() call. Such a window procedure must always call DefWindowProc(), the equivalent of calling the base class method. Or in other words, a window has one virtual method.
hIcon, etcetera. These are the equivalent of properties of the base class, they set default values that affect the default message handlers. Helping you to keep your window procedure simple. It is for example rarely necessary to write a message handler for WM_ERASEBKGND, the hbrBackground member sets the default background for a window.
Windows requires you to call RegisterClassEx() even if you don't plan on re-using a window. Which is by far the most common usage of the function in your own code. You don't start to really take advantage of it until you write a library that implements controls, windows that other code can use. Like "EDIT".

Using Printer.Print in VB6

I come from a Java and .NET background.
In VB6 it appears that you do not always have to create an instance of a class when using it. For example, when using the Printer class you can simply say Printer.print instead of having to create an instance first i.e. Dim printer As New Printer and then running printer.Print. I know that Printer is a system object in VB6, but why don't you have to create an instance?
Visual Basic traditionally had a large number of pre-defined identifiers that are directly recognized by the compiler. The Printer object is one of those. Under the hood, this is implemented with the [appobject] attribute but that's carefully hidden in the language. The runtime creates an instance of the COM coclass automatically, much like the As New syntax. The DAO DBEngine object would be an example of one that isn't predefined in the language parser.
This is over and done with in VB.NET, a truly object oriented language with a large class library, much like Java. There is no Printer object anymore, you're supposed to use the PrintDocument class. The Printer object is still supported for legacy code, available in the Microsoft.VisualBasic.PowerPacks.Printing.Compatibility.VB6 namespace. It however requires the New keyword to create an instance of it.
Be careful investing a lot of time and energy in VB6, it is in all respects a badly outdated language.
VB6 isn't an object oriented language in the same way as you would expect if you are used to newer languages. VB6 will do implicit instantiation and you can treat certain things as if it were static. For example, you can declare a variable of a form, but you don't have to. You can directly call a form and manipulate it without declaring it. In the case of the printer, it can't be explicitly declared and instantiated, but VB6 already has one available.

vb6 : accessing registry values

I'm writing a simple vb6 button which tests the access of the registry values.
I have the following:
Private Function registry_read(key_path, key_name) as variant
Dim registry as object
set registry = CreateObject("WScript.shell")
registry_read = registry.regread(key_path & key_name)
End function
Private Sub Command1_Click()
MsgBox registry_read("HKEY_LOCAL_MACHINE\SOFTWARE\PROCESS\frmMain_Values\", "Version")
end Sub
I have Project Menu -> References
and select Microsoft WMI Scripting V1.1 Library selected
and Windows Script Host Object Model referenced
however my msgbox is still coming up blank. I did check the registry path and it is correct. any ideas?
thanks in advance.
You need to comment out the line 'on error resume next' while you are developing. If an error is occurring, you will not be able to see the details. It could be not found or access denied etc.
Also there are two ways to reference an object. Early binding ie Dim rs as new adobdb.recordset and late binding set rs = CreateObject("Adodb.recordset"). The first method (early binding) forces you to declare a reference and the second (late) does not. There are advantages and disadvantages to both (ie early binding is faster, gives intellisense, easier debugging, etc) http://word.mvps.org/faqs/interdev/earlyvslatebinding.htm
#bugtussle While your statements are correct, wqw's statements are also. Whether you use the New keyword or CreateObject actually hasn't anything to do with whether an object is early or late bound. What does matter is whether you declare the object variable with a registered type or not. I believe that you actually explain this correctly in your article.
I'd like to mention also that your article is well-written and has good information, but IMHO contains also a couple of minor inaccuracies. What you call "Dual Interface" binding in your article (and explain well) is generally referred to as "vTable" or "very early" binding. VB6 supports vTable binding where possible.
Now, as you have said, the sole requirement to be a COM class is that the class must implement iUnknown. A "dual interface" simply means a COM class that implements both iUnknown and iDispatch: a COM class that supports late binding must implement the latter. VB doesn't directly support COM objects that don't implement iDispatch (having some COM classes that don't support late binding and some that do would pretty clearly be problematical in VB); in other words VB only supports COM classes that implement a dual interface. (However, there are tricks using SendMessage's GETOLEINTERFACE message that bypass the requirement.)
Also, it isn't quite that iUnknown is bypassed altogether, it is that iUnknown.QueryInterface() is bypassed, instead going directly to the virtual table. iUnknown.AddRef() is still called, of course.
Regarding New vs. CreateObject: VB has an optimization strategy for classes defined within a project that are instantiated within that project using the New keyword. However, there are also important differences between the two if you are using a class outside of a project context; this page http://msdn.microsoft.com/en-us/library/Aa241758 does a good job of summarizing them.
I'm curious as well to know what error the OP got. :)

Which error code to return when a passed object doesn't implement a necessary interface?

In COM when I have a well-known interface that I can't change:
interface IWellKnownInterface {
HRESULT DoStuff( IUnknown* );
};
and my implementation of IWellKnownInterface::DoStuff() can only work when the passed object implements some specific interface how do I handle this situation?
HRESULT CWellKnownInterfaceImpl::DoStuff( IUnknown* param )
{
//this will QI for the specific interface
ATL::CComQIPtr<ISpecificInterface> object( param );
if( object == 0 )
//clearly the specifil interface is not supported
return E_INVALIDARG;
}
// proceed with implementation
}
In case the specific interface is not supported which error code should I return? Is returning E_INVALIDARG appropriate?
E_INVALIDARG is a fine choice, but the most important thing is to ensure that the precise conditions for each return code that you use are well documented.
Additionally, you could consider implementing ISupportErrorInfo and then returning rich error information via CreateErrorInfo and SetErrorInfo. This is especially useful in cases where you think callers may benefit from having a custom error message generated at the point of failure, with all of the relevant context contained therein. In your case, this might be to identify specifically which argument is invalid and which interface was unimplemented for it to be so. Even though such a message is unlikely to be of value to an end user, it could be invaluable to a developer if it shows up in a log file or the event viewer.
Yes. At least, that is what I've learned to expect a microsoft API call to return in such a case.
I beg to differ with #nobugz. E_NOINTERFACE has the very specific meaning that your object does not support a certain interface that your client has just requested.
In general, you should only return E_NOINTERFACE from your own IUnknown::QueryInterface(). Returning E_NOINTERFACE from DoStuff() would surprise end users and tools alike. If I saw E_NOINTERFACE coming from anywhere other than QueryInterface(), my immediate thought would be "abstraction leak!".
At first sight, E_INVALIDARG looks like the best options available -- after all, you were passed an argument that doesn't work for you. But in my view there is a subtlety here, so I'm not sure if I can make a universal rule out of that.
In an ideal world, the error codes returned by a COM method are more about the Interface than about the object. Concretely, IWellKnownInterface::DoStuff() doesn't define (necessarily) that the object being passed in must implement ISpecificInterface, or it would have a different argument type. So, technically you're cheating potential users of your object by not quite fully implementing the semantics of your interface. Of course, in the real world, it's very hard to create a meaningful open system if you make that into an absolute rule and interfaces are that rigid.
So: if you can provide reduced functionality that still meets the interface semantics even if your argument doesn't implement ISpecificInterface, you should consider doing that.
Assuming that you must return in error: if this is a well-know, well documented (well designed?) interface, I would probably glance at the interface documentation first, to see if they give you any guidance. Lacking that, I would probably go ahead and use E_INVALIDARG.
#Phil Booth has some nice recommendations about how to provide more information to your user about the nature of the error.
Obviously, if you had the chance you would want to change the interface to take a ISpecificInterface* param instead.
Here's another possibilty you might or might not like better.
In Visual Basic 6, there are many objects with methods that take a VARIANT argument and look for several alternative interfaces they can use (e.g. the DataSource member of data-bound controls). That looks a lot like your situation. Those objects return DISP_E_TYPEMISMATCH, which is a well understood return value by most people.
On the other hand, I totally understand if you don't want to return a DISP_xxxx error code from non-IDISPATCH based interfaces. There is a strong argument for saying that DISP_xxxx error codes are intened for VARIANT parameters only, and a method that takes an IUnknown* should not be returning error them.
I'm on the fence on this one.

Resources