Allowed “out” parameter types in a COM automation

2019-05-15 10:48发布

I'm implementing COM automation (dual interface) for an application. The automation interface will be called from VBScript. I'm not quite clear on what types are allowed for the method's arguments. I do know that basically values have to fit in a VARIANT, but does that mean every parameter of type int must be passed through a VARIANT, or can one pass int directly?

For example, two methods I have in my MIDL file are:

HRESULT SetDate([in] int Year, [in] int Month, [in] int Day);
HRESULT GetDate([out] int* pYear, [out] int* pMonth, [out] int* pDay);

Calling SetDate from VBScript works. Calling GetDate as shown fails, which is to say that in the implementation, which is in C++, ITypeInfo::Invoke returns a code indicating a type error.

I've observed that if I use a VARIANT instead of an int, as shown below, it works.

HRESULT GetDate([out] VARIANT* pYear, [out] VARIANT* pMonth, [out] VARIANT* pDay);

So is int not allowed for parameters (because of the dual interface), or must I be doing something else wrong? If int is not allowed, why does SetDate work - is there a difference between in and out parameters in this respect?

Furthermore, this pair of methods works, although both use int:

[propget] HRESULT System([out, retval] int* pSystem);
[propput] HRESULT System([in] int System);

How come - are the rules for the allowed parameter types different for a property, or when the parameter is declared as retval?

I can't quite make sense of it all - would be grateful if anyone can clarify this.

3条回答
叼着烟拽天下
2楼-- · 2019-05-15 11:11

Obviously, when you call the interface via IDispatch, all the parameters are always passed as VARIANTs. Yet, your implementation potentially uses other types. How is the gap bridged?

ATL (assuming that's what you're using) will implement Invoke for you, with code that converts arguments from VARIANTs to the proper types used by your method signature before forwarding the call to the actual method.

Here are the rules:

  • [in] parameters can be just about any type that fits in a VARIANT, as you discovered. ATL (or whatever library you're using) will take care of translating parameters for you.

  • [in, out] parameters must be VARIANT*. If you use anything else, either the call won't work or the return value will be lost (I don't remember which way it goes; you indicated you had a run-time error for this situation). Upon your method returning, ATL will convert the argument into an appropriate VARIANT so that VBScript (or whatever IDispatch client made the call) can get a hold of the output value.

  • [retval, out] are a special case. You can use a pointer to whatever type you choose, and ATL will take care of it. I presume that what makes this possible is that the return value is provided back outside of the DISPPARAMS mechanism.

  • [out]... just don't. They don't work - VBScript cannot use [out] parameters correctly. Mind you, they will "work" in that the method will execute without errors, but VBScript cannot distinguish between [out] and [in, out], which means that VBScript expects your method to release whatever value was on the parameter when you received it. If you use [out], whatever the client code placed on the parameter before making the method call will be leaked permanently.

查看更多
疯言疯语
3楼-- · 2019-05-15 11:15

Types you are going to have less troubles with are listed on top of VT_xxx enumeration in Windows SDK:

enum VARENUM
    {   VT_EMPTY    = 0,
    VT_NULL = 1,
    VT_I2   = 2,
    VT_I4   = 3,
    VT_R4   = 4,
    VT_R8   = 5,
    VT_CY   = 6,
    VT_DATE = 7,
    VT_BSTR = 8,
    VT_DISPATCH = 9,
    VT_ERROR    = 10,
    VT_BOOL = 11,
    VT_VARIANT  = 12,
    VT_UNKNOWN  = 13,
    VT_DECIMAL  = 14,
    VT_I1   = 16,
    VT_UI1  = 17,

You don't see INT there, do you? LONG instead (which is VT_I4) is going to work just fine and be supported nice everywhere.

For scripting environment you are good to go with VARIANTs and if this makes your life easier on C++ side - with simple types mentioned above. If you need an array, VARIANT is a good holder for them as well.

A nice table there also hints about compatibility of types:

/*
 * VARENUM usage key,
 *
 * * [V] - may appear in a VARIANT
 * * [T] - may appear in a TYPEDESC
 * * [P] - may appear in an OLE property set
 * * [S] - may appear in a Safe Array
 *
 *
 *  VT_EMPTY            [V]   [P]     nothing
 *  VT_NULL             [V]   [P]     SQL style Null
 *  VT_I2               [V][T][P][S]  2 byte signed int
 *  VT_I4               [V][T][P][S]  4 byte signed int
 *  VT_R4               [V][T][P][S]  4 byte real
 *  VT_R8               [V][T][P][S]  8 byte real
 *  VT_CY               [V][T][P][S]  currency
 *  VT_DATE             [V][T][P][S]  date
 *  VT_BSTR             [V][T][P][S]  OLE Automation string
 *  VT_DISPATCH         [V][T]   [S]  IDispatch *
 *  VT_ERROR            [V][T][P][S]  SCODE
 *  VT_BOOL             [V][T][P][S]  True=-1, False=0
 *  VT_VARIANT          [V][T][P][S]  VARIANT *
 *  VT_UNKNOWN          [V][T]   [S]  IUnknown *
 *  VT_DECIMAL          [V][T]   [S]  16 byte fixed point
 *  VT_RECORD           [V]   [P][S]  user defined type
 *  VT_I1               [V][T][P][s]  signed char
 *  VT_UI1              [V][T][P][S]  unsigned char
 *  VT_UI2              [V][T][P][S]  unsigned short
 *  VT_UI4              [V][T][P][S]  unsigned long
 *  VT_I8                  [T][P]     signed 64-bit int
 *  VT_UI8                 [T][P]     unsigned 64-bit int
 *  VT_INT              [V][T][P][S]  signed machine int
 *  VT_UINT             [V][T]   [S]  unsigned machine int
 *  VT_INT_PTR             [T]        signed machine register size width
 *  VT_UINT_PTR            [T]        unsigned machine register size width
 *  VT_VOID                [T]        C style void
 *  VT_HRESULT             [T]        Standard return type
 *  VT_PTR                 [T]        pointer type
 *  VT_SAFEARRAY           [T]        (use VT_ARRAY in VARIANT)
 *  VT_CARRAY              [T]        C style array
 *  VT_USERDEFINED         [T]        user defined type
 *  VT_LPSTR               [T][P]     null terminated string
 *  VT_LPWSTR              [T][P]     wide null terminated string
 *  VT_FILETIME               [P]     FILETIME
 *  VT_BLOB                   [P]     Length prefixed bytes
 *  VT_STREAM                 [P]     Name of the stream follows
 *  VT_STORAGE                [P]     Name of the storage follows
 *  VT_STREAMED_OBJECT        [P]     Stream contains an object
 *  VT_STORED_OBJECT          [P]     Storage contains an object
 *  VT_VERSIONED_STREAM       [P]     Stream with a GUID version
 *  VT_BLOB_OBJECT            [P]     Blob contains an object 
 *  VT_CF                     [P]     Clipboard format
 *  VT_CLSID                  [P]     A Class ID
 *  VT_VECTOR                 [P]     simple counted array
 *  VT_ARRAY            [V]           SAFEARRAY*
 *  VT_BYREF            [V]           void* for local use
 *  VT_BSTR_BLOB                      Reserved for system use
 */
查看更多
唯我独甜
4楼-- · 2019-05-15 11:37

There are two different technologies involved in your question:

  • COM Automation, wich evolves around the IDispatch interface and friends (especially the restricted type system which doesn't need specific marshaling code because OLEAUT does the job automatically when it gets a TLB)
  • VBScript, which is one specific COM Automation client.

out or in/out parameters that are not VARIANT are fine for COM automation in general, but not for VBScript, as VBScript basically only knows VARIANTs.

You will find the definitive explanation on Eric Lippert's blob here: In, Out, In-Out, Make Up Your Mind Already

查看更多
登录 后发表回答