Creating ATL DLL's for use by VB

I’m new to using ATL, so please forgive what may be lame questions. I’ve
created an ATL DLL and confirmed that a VB program can read/write its
properties and invoke its methods. However:

  1. VB does not recognize that methods return a value. MSDN suggests that VB
    can only see return values if they are returned via an extra passed
    parameter, an [out,retval] pointer which must be the last argument in the
    parameter list. True? I’m surprised this isn’t more obviously documented -
    it took some searching to find this (and I haven’t tried it to confirm that
    it solves the problem). ATL handles so many other details so cleanly, it
    sure seems like this ought to be handled, or mentioned, or something. Not a
    complaint, just wondering where the gap is in my understanding.

  2. VB doesn’t have the “auto-completion” behavior for my DLL that it does
    for other objects. The Object Browser can see the type information just
    fine, but a VB author doesn’t see the handy little menus popping up when
    referencing the DLL. Did I miss something when creating the DLL?

  3. I’m not sure when VB is binding to my type library. Is there any way to
    confirm that it is using early vs. late binding? Does the above behavior
    indicate this in some way? How can I force it one way or the other?

Thanks!

RLH

[this is general, not NT specific, but…]

On Friday 31 March 2000 you wrote:

I’m new to using ATL, so please forgive what may be lame questions. I’ve
created an ATL DLL and confirmed that a VB program can read/write its
properties and invoke its methods. However:

ATL’s dead easy. Much better than MFC. Generally though, you have do
to quite a bit of research in both areas before you can ensure the
maximum compatibility with any particular client environment
(including VB, VBA and VBS.)

  1. VB does not recognize that methods return a value. MSDN suggests that VB
    can only see return values if they are returned via an extra passed
    parameter, an [out,retval] pointer which must be the last argument in the
    parameter list. True? I’m surprised this isn’t more obviously documented -
    it took some searching to find this (and I haven’t tried it to confirm that
    it solves the problem).

This is absolutely a requirement of Automation compatible interfaces.
If your interface is marked as either ‘automation’ or ‘dual’ in the
.IDL file, then you have to follow these rules for return value and
parameters:

* The function must return a HRESULT. This is to allow the transport
layer to insert its own error messages if necessary, such as
RPC_E_SERVERFAULT

* Your function can pass either by-value or by-reference values, but
such must be marked in the .IDL file as either [in] or [in, out]
respectively. Typically the by-reference parameter is of type
pointer-to the type that the by-value parameter would be.
ie, if ByVal is long, ByRef is long*; if ByVal is IDispatch*,
ByRef is IDispatch**

* If you need to return a value as the real return value of the
function, this should be the last parameter of the function as
marked in the .IDL file, and should have attributes [out, retval]
Like ByRef parameters it should be of type pointer-to the real
return type (ie long*, VARIANT_BOOL*, BSTR* etc.)

* If you want to represent a property of the object, rather than a
method, you probably want to expose 2 functions. One should be
marked as [propput] (for normal types) or [propputref] (for object)
and have as the last parameter the normal type of the property you
are exposing. The other function should be marked as [propget] and
have as its last parameter the equivalent pointer-to type of the
last parameter of your [propput] function.

* You can only use certain types. The most common being long, BSTR,
IDispatch*, VARIANT_BOOL, etc.

Some of these are enforced by the MIDL compiler itself: you won’t be
able to fully compile if you get it wrong. Other mistakes will be
visible as the host/client application failing to fully expose your
object’s methods or properties.

ATL handles so many other details so cleanly, it
sure seems like this ought to be handled, or mentioned, or something. Not a
complaint, just wondering where the gap is in my understanding.

ATL is a general COM programming library: it supports Automation
programming too, but the restraints are fewer for non-Automation COM
and so ATL doesn’t enfore the ‘pure-Automation’ rules un-necessarily.

  1. VB doesn’t have the “auto-completion” behavior for my DLL that it does
    for other objects. The Object Browser can see the type information just
    fine, but a VB author doesn’t see the handy little menus popping up when
    referencing the DLL. Did I miss something when creating the DLL?

Mmm. Really not sure. Sometimes Visual C++ (6 has IntelliSense (sic)
too) gets it wrong as well.

In general, if you follow the Automation typing rules, and include
helpstring()s, make your interfaces properly [automation] or [dual]
and mark them in your coclasses, things should just work. The default
ATL IDipatchImpl<> exposes enough type information to get some
IntelliSense output from VB and VC.

  1. I’m not sure when VB is binding to my type library. Is there any way to
    confirm that it is using early vs. late binding? Does the above behavior
    indicate this in some way? How can I force it one way or the other?

In general, if you #import in ATL/VC++, or import the type library in
MFC/VC++, or reference the type library and use explicit typing in VB
(ie “Dim Foo As IMyObject”) you should be early binding. If you “Dim
Foo As Object” within VB then you’ll late bind (and I find that many
times VB will enforce this if it isn’t entirely happy about the typing
around the object.)

If you absolutely want to be sure, then you can stick a breakpoint on
IDispatchImpl<>::GetIDsOfNames() then it should become clear: early
binding should hit this breakpoint at compile time, if at all, late
binding will hit it at run time each time a method on your object is
called.

John

you gave me something that i could touch in a world where i’d had too much
something i could feel with my broken hands full of lost ideals but soon i’m
returning to you my friend and we’ll go where the rivers end in the silver sea
and i’ll carry you if you carry me

At 02:11 AM 04/01/2000 +0100, John Sullivan wrote:

* If you need to return a value as the real return value of the
function, this should be the last parameter of the function as
marked in the .IDL file, and should have attributes [out, retval]
Like ByRef parameters it should be of type pointer-to the real
return type (ie long*, VARIANT_BOOL*, BSTR* etc.)

Thank you for the details - very informative.

Speaking of returning a boolean, I’ve noticed that even though I define
booleans in my C code, VB sees them as longs. (Example: If I have a propput
function which accepts a boolean, VB says it accepts a long instead.) I know
that booleans are really int’s down deep, but VB natively supports a boolean
type. From your commentary above, it sounds like I’d have to package my
boolean into a Variant… which sounds like a lot of extra work just to pass
around a true/false indication. I really dislike all the extra work
necessary to support those VB-specific types such as BSTR’s, Variants, etc.
but it spooks the VB developers when they’re expecting a boolean and see a
long .

On Saturday 1 April 2000 Richard Hartman wrote:

Speaking of returning a boolean, I’ve noticed that even though I define
booleans in my C code, VB sees them as longs. (Example: If I have a propput
function which accepts a boolean, VB says it accepts a long instead.) I know
that booleans are really int’s down deep, but VB natively supports a boolean
type. From your commentary above, it sounds like I’d have to package my
boolean into a Variant… which sounds like a lot of extra work just to pass
around a true/false indication.

Again, if you’re using Automation then you need to use the correct
type. Booleans are actually pretty hairy here - you *can* pass real
booleans around such that VB will recognize them as the type
‘Boolean’, however you need to know a little more about the MIDL
compiler.

There are (I think) 3 basic types you can use here.

In the old days of mktyplib-compatible .ODL files, you would define a
dispinterface with separate properties: and methods: sections. In here
you really did use the keyword ‘boolean’ which by certain magic turned
into the right data-type for VB.

With the more up-to-date MIDL .IDL file syntax, boolean is actually a
MIDL base datatype: is equates to a byte (8 bits) which can take
either the value FALSE or the value TRUE. I’ve not looked for
confirmation here, but I suspect these equate to 0 and 1 respectively.
Note that this is very different from the treatment of the keyword
boolean within a mktyplib-compatible .ODL dispinterface definition!

You also get the choice of two other datatypes, which are *not* MIDL
base data-types, but are defined by the system include file
“oaidl.idl” as typedefs to other base types.

There is the type “BOOL”, which I think is typedeffed to int, and is
therefore 32 bits wide. (Being a wire-protocol, this is fixed: int
will not vary in size according to platform, unlike the same name in
many C compiler environments.) This is also not what you want however.

The MIDL type that actually equates to VB’s Boolean type is the
typedef VARIANT_BOOL, which is typedeffed to short, and so is 16 bits
wide. It should only ever be used to store the values VARIANT_FALSE
and VARIANT_TRUE (which are #defined to be ((VARIANT_BOOL)0) and
((VARIANT_BOOL)-1) respectively.) Note the value of true: -1. This is
very important and different to what C/C++ use, so you need to make
sure you get this right with code such as:

HRESULT CTest::ReturnABoolean(VARIANT_BOOL *pVal)
{
bool retval = MyBoolFunction();
*pVal = retval ? VARIANT_TRUE : VARIANT_FALSE;
return S_OK;
}

Note that incorrectly returning 1 instead of -1 will result in very
strange behaviour from VB:

Dim Foo As DodgyCObject
Dim Val As Boolean

Val = DodgyCObject.BoolFunc

If Val Then Debug.Print “It’s true!”
If Not Val Then Debug.Print “It’s not true!”

If BoolFunc were to return 1 for true, then *both* Print statements
would be executed! (The trick is that VB’s Not operator does a
bitwise, not a logical inversion, and the If statement tests against
zero/non-zero like C. “Not 1” equals -2 by this scheme, which is still
True.)

Anyway, that’s probably more than you wanted to know about Booleans
:wink:

I really dislike all the extra work
necessary to support those VB-specific types such as BSTR’s, Variants, etc.
but it spooks the VB developers when they’re expecting a boolean and see a
long .

Indeed, but you do get quite a bit of support from the runtime
libraries, either MFC or ATL. (I’d recommend ATL unless you really
need a UI.)

Under MFC you get COleVariant, which will handle most of the work of
dealing with the VARIANT data type. CString can also do a bit of the
BSTR handling for you. There is also now a wrapper for SAFEARRAYs
called COleSafeArray (I think), although I don’t use that because by
the time this was added to MFC I’d already written my own version.

Under ATL you get CComVariant, which is pretty much the same a
COleVariant, and also CComBSTR which is a wrapper round BSTR. The
conversion functions OLE2T, T2OLE, and T2BSTR are also available as
long as you put “USES_CONVERSION;” at the start of the function.
CComPtr also handles object interfaces, and
CComQIPtr will even call QueryInterface to the
appropriate IID when you assign any interface pointer into it.

The newer compilers also have built-in support for _bstr_t, _variant_t
and _com_ptr_t which are quite similar to CComBSTR, CComVariant and
CComQIPtr<> respectively. These are used automatically if you #import
a type library.

C{Com,Ole}Variant is so similar to VARIANT that you can freely cast a
VARIANT* to a CComVariant* to take advantage of the extra
functionality. You can also pass a CComVariant* to any function which
takes a VARIANT* parameter, and it’ll all just work.

CComBSTR is also just a BSTR in disguise, so you can pass CComBSTR* to
any function taking a BSTR*. A common idiom is:

{
CComBSTR mystr;
spMyOb->ReturnString(&mystr);
// Do something with mystr here
}

CComBSTR’s destructor will automatically call SysFreeString on the
contained BSTR for you, so you don’t have as much book-keeping to
worry about. (C{Com,Ole}Variant’s destructor will similarly call
VariantClear for you to release any contained BSTR or object pointer.)

The only really tricky data-type is the SAFEARRAY, but it can
occasionally prove so useful (for example you can implement varargs
functions in VB using it) that I’d recommend you read up on it and get
used to the SafeArrayXxx APIs.

John

you gave me something that i could touch in a world where i’d had too much
something i could feel with my broken hands full of lost ideals but soon i’m
returning to you my friend and we’ll go where the rivers end in the silver sea
and i’ll carry you if you carry me

> * You can only use certain types. The most common being long, BSTR,

IDispatch*, VARIANT_BOOL, etc.

Absolutely. No structures, no arrays except SAFEARRAY, no strings except
BSTR.
But the advantage is to be compatible with OA marshaller which will use the
TLB information to marshal calls. So - no proxy/stub DLL.

ATL is a general COM programming library: it supports Automation
programming too, but the restraints are fewer for non-Automation COM
and so ATL doesn’t enfore the ‘pure-Automation’ rules un-necessarily.

VB also supports general COM, not only OA.

If you absolutely want to be sure, then you can stick a breakpoint on
IDispatchImpl<>::GetIDsOfNames() then it should become clear: early
binding should hit this breakpoint at compile time, if at all, late

Maybe ITypeInfo::GetIDsOfNames() for compile time?

Max

> Speaking of returning a boolean, I’ve noticed that even though I define

booleans in my C code, VB sees them as longs. (Example: If I have a

You must not use boolean in OA, use VARIANT_BOOL instead.
It is (IIRC) 16bit signed integer which must be -1 for TRUE and 0 for FALSE.
The -1 value is very, very important for VB (which is weird for C programmer
since any nonzero value is TRUE in C).
So, use code snippets like:
*vbRetVal = (your expression) ? -1 : 0;

As about “strange VB datatypes” - they are not VB ones, but OA ones. And
OA is language-independent framework not tied to either C++ or VB.

Max

> The newer compilers also have built-in support for _bstr_t, _variant_t

and _com_ptr_t which are quite similar to CComBSTR, CComVariant and
CComQIPtr<> respectively. These are used automatically if you #import
a type library.

_bstr_t is better than CComBSTR and does not cost any DLL dependencies

  • it is part of the C runtime library now.
    But MFC’ CString is better than _bstr_t.

Max

On Saturday 1 April 2000 Maxim S. Shatskih wrote:

As about “strange VB datatypes” - they are not VB ones, but OA ones. And
OA is language-independent framework not tied to either C++ or VB.

Although this is entirely right, in a technical sense, I think it
should be borne in mind that the development of the Automation
framework has traditionally been driven by the needs of the Visual
Basic team at Microsoft.

John

you gave me something that i could touch in a world where i’d had too much
something i could feel with my broken hands full of lost ideals but soon i’m
returning to you my friend and we’ll go where the rivers end in the silver sea
and i’ll carry you if you carry me

At 02:01 PM 04/01/2000 +0100, John Sullivan wrote:

The MIDL type that actually equates to VB’s Boolean type is the
typedef VARIANT_BOOL, which is typedeffed to short, and so is 16 bits
wide. It should only ever be used to store the values VARIANT_FALSE
and VARIANT_TRUE (which are #defined to be ((VARIANT_BOOL)0) and
((VARIANT_BOOL)-1) respectively.) Note the value of true: -1. This is
very important and different to what C/C++ use, so you need to make
sure you get this right with code such as:

HRESULT CTest::ReturnABoolean(VARIANT_BOOL *pVal)
{
bool retval = MyBoolFunction();
*pVal = retval ? VARIANT_TRUE : VARIANT_FALSE;
return S_OK;
}

Are you sure this will work? I had been given the impression that the called
function was responsible for allocating the Variant to hold the return
value. The caller, in that case, is simply providing a pointer to a storage
location for the address of the newly-allocated Variant - NOT a pointer to
an existing Variant into which a new value may be written.

I believe BSTR’s may work the same way. Lots of weirdness and poor
documentation on this C-to-VB boundary.

RLH

On Monday 3 April 2000 Richard Hartman wrote:

> HRESULT CTest::ReturnABoolean(VARIANT_BOOL *pVal)
> {
> bool retval = MyBoolFunction();
> *pVal = retval ? VARIANT_TRUE : VARIANT_FALSE;
> return S_OK;
> }

Are you sure this will work?

Yes, absolutely. Why not try it out for your own peace of mind?

I had been given the impression that the called
function was responsible for allocating the Variant to hold the return
value.

What VARIANT? See below.

The caller, in that case, is simply providing a pointer to a storage
location for the address of the newly-allocated Variant - NOT a pointer to
an existing Variant into which a new value may be written.

Well, certainly not a pointer to any sort of VARIANT anyway.

Do not get VARIANT the structure confused with VARIANT_BOOL the
typedef. If you look at the definition of VARIANT, in OAIDL.H, you’ll
see something like:

struct tagVARIANT {
VARTYPE vt;
WORD wReserved1;
WORD wReserved2;
WORD wReserved3;
union {

VARIANT_BOOL boolVal; /* VT_BOOL */

}
};

In WTYPES.H, VARIANT_BOOL is simply a typedef for short.

So ultimately, the caller of ReturnABoolean() above is passing a
pointer to a short where the function can place its return value.

I believe BSTR’s may work the same way. Lots of weirdness and poor
documentation on this C-to-VB boundary.

Again, it’s not specifically C-to-VB. It’s not even restricted to
Automation. This stuff is standard COM and applicable everywhere that
COM is. And the documentation is perfectly adequate: you’ll find it in
the MSDN Library, under Platform SDK\Component Services.

Try to stay on top of how many levels of indirection there are in
parameters. A simple [in] parameter will have none: you get a long or
a VARIANT. An [in, out] or [out] will have one immediately obvious
level since you’re now being passed a long* or VARIANT*. These
pointers must point to a valid object of the appropriate type, and the
storage for this must be allocated by the caller. The callee will
never have to worry about this, and for simple data types such as
long and VARIANT_BOOL (but *not* BSTR, VARIANT and IUnknown*) that’s
the end of the story.

For complex data types such as BSTR, VARIANT and interface pointers
(but *not* VARIANT_BOOL which is a simple data type), there is an
extra layer possible: the structure of one of these objects does not
hold all the data of the object itself. A BSTR is for most purposes
synonymous with a pointer to a string. It does not itself contain the
string though, that must be allocated separately. A VARIANT can
potentially also point to non-resident data: it can be VT_BSTR,
VT_DISPATCH, VT_BYREF|anything, VT_ARRAY|anything. It is this extra
non-resident data which the callee, in some circumstances, may have to
allocate or deallocate.

The rules for allocation are:

[in] parameters. The caller takes care of everything. It is an error
for the callee to attempt to free any passed in data.

[in, out] parameters. The caller will allocation storage for at least
the first level of indirection. There may or may not be any extra data
after that. The callee is free to take the data as passed and take no
further action. If a data must be fed back up the the caller, then the
callee must free the old value, and allocate the new. The caller will
then free the new value when it has finished. All allocation must be
done using the standard CoXxx APIs which take advantage of the task
allocator for allocating real memory.

[out] parameters. Exactly the same as [in, out], except that the
passed in data should be NULL, so you shouldn’t necessarily have to
free it before replacing it with the out data.

It may also be helpful to always ignore the first ‘*’ after an
interface pointer. While technically an IUnknown* *is* a pointer to
something else, you will almost never have occasion to manipulate the
pointed to data directly. So it makes more sense to consider any
interface pointer as an opaque blob which you just happen to be able
to call functions against. If you consider an IUnknown* to be an
IUnknownBlob, then functions such as:

HRESULT MyInterfaceFunc([out] IUnknownBlob *pVal); /* actually means IUnknown **pVal */
HRESULT MyLongFunc([out] long *pVal);

are now clearly orthogonal. The caller is responsible for allocating
storage for the long or IUnknownBlob, and passes you a pointer to that
storage so you can fill it in. It is perhaps this apparent (but not
useful in any practical sense) double-indirection of the IUnknown**
used in [out] parameters that caused you to say:

The caller, in that case, is simply providing a pointer to a storage
location for the address of the newly-allocated Variant

But this is only ever the case for [out] IInterface** parameters. an
[out] BSTR is a BSTR*, and an [out] VARIANT is a VARIANT*, not a
BSTR** or a VARIANT**.

Hope I’ve made this more rather than less clear. :slight_smile:

John

you gave me something that i could touch in a world where i’d had too much
something i could feel with my broken hands full of lost ideals but soon i’m
returning to you my friend and we’ll go where the rivers end in the silver sea
and i’ll carry you if you carry me

> > HRESULT CTest::ReturnABoolean(VARIANT_BOOL *pVal)

> {
> bool retval = MyBoolFunction();
> *pVal = retval ? VARIANT_TRUE : VARIANT_FALSE;
> return S_OK;
> }

Are you sure this will work?

Yes. Surely.

function was responsible for allocating the Variant to hold the return
value.

No need for Variant if you have declared the function as having the
VARIANT_BOOL parameter.

I believe BSTR’s may work the same way.

Yes. BSTR is a subset of PWSTR - namely, PWSTRs allocated by
SysAllocString.
Any BSTR is PWSTR - the vice versa is not true.

Lots of weirdness and poor
documentation on this C-to-VB boundary.

Though OA is a language-independent thing, I was under impression that
“this is some of VB engine internals exported to us” when I first saw
OLEAUT32.DLL (well, it was OLE2DISP.DLL/TYPELIB.DLL in 16bit).

Max

> But this is only ever the case for [out] IInterface** parameters. an

[out] BSTR is a BSTR*, and an [out] VARIANT is a VARIANT*, not a
BSTR** or a VARIANT**.

Hope I’ve made this more rather than less clear. :slight_smile:

A pity - and great pity - C has no & references (VARIANT& var) as C++ has.

Max