Windows System Software -- Consulting, Training, Development -- Unique Expertise, Guaranteed Results

Home NTDEV

Before Posting...

Please check out the Community Guidelines in the Announcements and Administration Category.

More Info on Driver Writing and Debugging


The free OSR Learning Library has more than 50 articles on a wide variety of topics about writing and debugging device drivers and Minifilters. From introductory level to advanced. All the articles have been recently reviewed and updated, and are written using the clear and definitive style you've come to expect from OSR over the years.


Check out The OSR Learning Library at: https://www.osr.com/osr-learning-library/


Creating ATL DLL's for use by VB

OSR_Community_UserOSR_Community_User Member Posts: 110,217
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

Comments

  • OSR_Community_UserOSR_Community_User Member Posts: 110,217
    [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.

    > 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?

    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.

    > 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?

    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
  • OSR_Community_UserOSR_Community_User Member Posts: 110,217
    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 <sigh>.
  • OSR_Community_UserOSR_Community_User Member Posts: 110,217
    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
    ;-)

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

    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<IInterfaceType> also handles object interfaces, and
    CComQIPtr<IInterfaceType> 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
  • OSR_Community_UserOSR_Community_User Member Posts: 110,217
    > * 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
  • OSR_Community_UserOSR_Community_User Member Posts: 110,217
    > 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
  • OSR_Community_UserOSR_Community_User Member Posts: 110,217
    > 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
  • OSR_Community_UserOSR_Community_User Member Posts: 110,217
    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
  • OSR_Community_UserOSR_Community_User Member Posts: 110,217
    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
  • OSR_Community_UserOSR_Community_User Member Posts: 110,217
    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. :-)

    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
  • OSR_Community_UserOSR_Community_User Member Posts: 110,217
    > > 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
  • OSR_Community_UserOSR_Community_User Member Posts: 110,217
    > 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. :-)

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

    Max
Sign In or Register to comment.

Howdy, Stranger!

It looks like you're new here. Sign in or register to get started.

Upcoming OSR Seminars
OSR has suspended in-person seminars due to the Covid-19 outbreak. But, don't miss your training! Attend via the internet instead!
Internals & Software Drivers 7 February 2022 Live, Online
Kernel Debugging 21 March 2022 Live, Online
Developing Minifilters 23 May 2022 Live, Online
Writing WDF Drivers 12 September 2022 Live, Online