Discussion:
How to return string through output VARIANT from C++ COM object
(too old to reply)
Phil Sherrod
2004-07-25 02:51:42 UTC
Permalink
I need help figuring out how to return a string from a C++ ATL-generated COM
object through an output VARIANT parameter that is not the [out,retval] for the
function. Here is an example function declaration:

ReturnString([out] VARIANT *outString, [out,retval] long *outStatusValue)

Here is a simplified fragment of the code I tried to use:

STDMETHODIMP CDTREG::ReturnString(VARIANT *outString, long *outStatusValue)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState())

if (outString->vt & VT_BYREF) {
*v->vt = VT_BYREF | VT_BSTR;
*v->pbstrVal = _bstr_t("TestOutput");
} else {
v->vt = VT_BYREF | VT_BSTR;
v->bstrVal = _bstr_t("TestOutput");
}
return S_OK;
}

(I believe the VT_BYREF flag is always set, so the Else statements are never
used.)

But the returned string does not show up in the Visual Basic test program that
is calling this method.

I've also tried to manually construct a BSTR using SysAllocString and putting
the 4-byte length header, etc. But that doesn't work either.

Can someone please show me a simple example of how to return a string through a
VARIANT output parameter that is not the [out,retval] parameter for the
function?
--
Phil Sherrod
(phil.sherrod 'at' sandh.com)
http://www.dtreg.com (decision tree modeling)
http://www.nlreg.com (nonlinear regression)
Kim Gräsman
2004-07-25 09:22:56 UTC
Permalink
Hi Phil,
Post by Phil Sherrod
I need help figuring out how to return a string from a C++ ATL-generated COM
object
What are the expected clients? VB and VBScript can't handle [out] only
parameters (they will leak memory), so you may want to revise your design if
you're targetting those languages.
Post by Phil Sherrod
ReturnString([out] VARIANT *outString, [out,retval] long *outStatusValue)
STDMETHODIMP CDTREG::ReturnString(VARIANT *outString, long
*outStatusValue)
Post by Phil Sherrod
{
if (outString->vt & VT_BYREF) {
Since outstring is [out] only, checking any values on it on the way in is
pointless. An [out] parameter should be regarded as initialized when you get
it.
Post by Phil Sherrod
if (outString->vt & VT_BYREF) {
*v->vt = VT_BYREF | VT_BSTR;
*v->pbstrVal = _bstr_t("TestOutput");
} else {
Danger! Remember that [out] parameters require the callee (server) to
allocate memory for the client to release. The _bstr_t instance you're
constructing here will destruct by the time you leave the if-scope, so the
client gets garbage. Besides that, I think the VT_BYREF stuff is overkill.
I'd do something like this:

STDMETHODIMP CDTReg::ReturnString(VARIANT *outString, long *outStatusValue)
{
if(outString == NULL) return E_POINTER;
if(outStatusValue == NULL) return E_POINTER;

outString->bstrVal = ::SysAllocString(L"TestOutput);
if(outString->bstrVal == NULL)
{
return E_OUTOFMEMORY;
}

outString->vt = VT_BSTR;

return S_OK;
}

Hope that helps!
--
Best regards,
Kim Gräsman
Phil Sherrod
2004-07-25 11:12:00 UTC
Permalink
Thank you for your help.
Post by Kim Gräsman
What are the expected clients? VB and VBScript can't handle [out] only
parameters (they will leak memory), so you may want to revise your design if
you're targetting those languages.
Yes, I am targeting VB. How do you suggest I change the design?

Isn't it possible to write a COM server method that returns a string parameter
without having the calling VB client preallocate a string to receive it? Some
of the strings I will be returning are long. I would like the client to be able
to say:

Dim mystring As String
Dim status As Long
status = mycontrol.GetString(mystring)
Post by Kim Gräsman
Since outstring is [out] only, checking any values on it on the way in is
pointless. An [out] parameter should be regarded as initialized when you get
it.
That's interesting but confusing. Are you saying that the VT_BYREF and type
flags such as VT_BSTR can't be checked? I'm trying to write a general routine
that will return values in whatever type is expected by the caller; I can
definitely see the type flags in the vt member item change when I change the
calling VB program. My current routine recognizes the return type expected by
the VB client and does the appropriate conversions; this works for all data
types other than string which requires memory allocation.

I note that [out,retval] parameters behave differently. They do not have the
VT_BYREF flag set, and I can successfully use this code to pass out string
values:

/*-------------------------------------------------------------------
* Create a Variant with a BSTR value.
*/
void PutString(VARIANT *pv, char *outstr)
{
long outlen;

/*
* Initialize the variant.
*/
VariantInit(pv);
/*
* Set the data type.
*/
pv->vt = VT_BSTR;
/*
* Allocate space for the output string.
*/
outlen = strlen(outstr);
pv->bstrVal = SysAllocStringLen(0,outlen);
/*
* Convert ascii to wide-character output string.
*/
mbstowcs(pv->bstrVal,outstr,outlen);
/*
* Finished
*/
return;
}

So if I can return an allocated string through an [out,retval] parameter, why
not through an [out] parameter?
Post by Kim Gräsman
STDMETHODIMP CDTReg::ReturnString(VARIANT *outString, long *outStatusValue)
{
if(outString == NULL) return E_POINTER;
if(outStatusValue == NULL) return E_POINTER;
outString->bstrVal = ::SysAllocString(L"TestOutput);
if(outString->bstrVal == NULL)
{
return E_OUTOFMEMORY;
}
outString->vt = VT_BSTR;
return S_OK;
}
That's pretty similar to the code that I have been using for the [out,retval]
parameters. However, I question whether your code is going to work if the
parameter is passed in with the VT_BYREF flag set. I believe you need an extra
level of indirection in that case.

On a related question, does SysAllocString allocate a full BSTR string with a
4-byte length prefix and a 16-bit null at the end?
--
Phil Sherrod
(phil.sherrod 'at' sandh.com)
http://www.dtreg.com (decision tree modeling)
http://www.nlreg.com (nonlinear regression)
Kim Gräsman
2004-07-25 12:33:50 UTC
Permalink
Hi Phil,
Post by Phil Sherrod
Yes, I am targeting VB. How do you suggest I change the design?
Isn't it possible to write a COM server method that returns a string parameter
without having the calling VB client preallocate a string to receive it?
Some
Post by Phil Sherrod
of the strings I will be returning are long. I would like the client to be able
Dim mystring As String
Dim status As Long
status = mycontrol.GetString(mystring)
Sure, the client never pre-allocates anything. However, for VB(S), you need
to use [in, out], which seems to be what you're aiming for anyway. The
difference is that with [in, out] your server is responsible for freeing any
existing value before overwriting it, whereas with [out]-only there can be
no existing value. VB and VBS have some trouble with determining if your
param is [out] or [in, out], however (see [1]).

So, bottom line is - [out] is bad for VB and VBS, use [in, out] instead.
Post by Phil Sherrod
That's interesting but confusing. Are you saying that the VT_BYREF and type
flags such as VT_BSTR can't be checked?
I suppose they can, but I think you're seeing undefined behaviour rather
than supported behaviour. Since the parameter is marked [out], the run-time
is informed that the value coming in is not of interest. I suspect that VB
just lazily passes in the original value, but if you were to call this
method cross-process, incurring marshalling, you would likely get garbage on
the way in.
Post by Phil Sherrod
I'm trying to write a general routine
that will return values in whatever type is expected by the caller; I can
definitely see the type flags in the vt member item change when I change the
calling VB program. My current routine recognizes the return type expected by
the VB client and does the appropriate conversions; this works for all data
types other than string which requires memory allocation.
Again, then you'll want to use [in, out].
Post by Phil Sherrod
I note that [out,retval] parameters behave differently. They do not have the
VT_BYREF flag set, and I can successfully use this code to pass out string
[...]
So if I can return an allocated string through an [out,retval] parameter, why
not through an [out] parameter?
Probably because VB knows it doesn't need to initialize it, since it's
regarded as a return value, and will be initialized with the value coming
out of your COM server. For [out], it can't know.

Note that this is only a problem with VB and VBS, C++ clients are much more
explicit in their memory management, and can make sure to handle data
properly.
Post by Phil Sherrod
On a related question, does SysAllocString allocate a full BSTR string with a
4-byte length prefix and a 16-bit null at the end?
Yes it will, actually, that's why it exists at all. However, it will not
convert between ANSI and Unicode strings, so you may need to use A2BSTR from
the ATL conversion macros, if you need to go from a char* to a BSTR.

There were three main problems with your original code:

- Passing [out] data to VB; this can cause leaks in Visual Basic, since it
can't follow the protocol for [out]
- Assuming that the [out] parameter held any meaningful data. VB caused it
to do so, but counting on that will break
- Assigning temporary constructed _bstr_t's to the VARIANT's bstrVal member,
they went out of scope before the client had a chance to see them.

Hope that helps!

[1] http://blogs.msdn.com/ericlippert/archive/2004/07/14/183241.aspx
--
Best regards,
Kim Gräsman
Phil Sherrod
2004-07-25 13:28:45 UTC
Permalink
Thank you for the help.
Post by Kim Gräsman
Post by Phil Sherrod
Dim mystring As String
Dim status As Long
status = mycontrol.GetString(mystring)
Sure, the client never pre-allocates anything. However, for VB(S), you need
to use [in, out], which seems to be what you're aiming for anyway. The
difference is that with [in, out] your server is responsible for freeing any
existing value before overwriting it, whereas with [out]-only there can be
no existing value. VB and VBS have some trouble with determining if your
param is [out] or [in, out], however (see [1]).
OK. If I change the parameter to be [in, out], what is the proper way to
deallocate any string that VB passed in before allocating my output string?
Would VariantClear() be the correct method to use?

Here is a function I have been testing today. It appears to work correctly
with [out] parameters from VB:

STDMETHODIMP CTest::ReturnVariant(VARIANT *outString, VARIANT *outStatus)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState())

char buf[100];
long outlen;
VARTYPE type;

/* Set up a simulated asciz (not wide char) output string */
strcpy(buf,"Simulated output text");
/* Save the current type flags */
type = outString->vt;
/* Deallocate any existing data */
VariantClear(outString);
/* Put back the type flags which were cleared by VariantClear */
outString->vt = type;
outlen = strlen(buf);
if (outString->vt & VT_BYREF) {
/* Allocate system memory for the returned string */
*outString->pbstrVal = ::SysAllocStringLen(0,outlen);
/* Convert the asciz string to wide characters and store into system memory
*/
mbstowcs(*outString->pbstrVal,buf,outlen);
} else {
outString->bstrVal = ::SysAllocStringLen(0,outlen);
mbstowcs(outString->bstrVal,buf,outlen);
}
return S_OK;
}

I called this 100,000,000 times from within a loop in a VB client application
and monitored memory usage. There was no increase in memory usage during the
looping, so it does not appear to be leaking memory.

The VT_BYREF flag is set on entry as is VT_BSTR. In the case of a [out,retval]
parameter, VT_BYREF is not set. I tried changing the type of the variable
declaration in the calling VB client, and the passed in VT_xxx flags correctly
indicate the type of the parameter.

I would appreciate your comments about this procedure.
--
Phil Sherrod
(phil.sherrod 'at' sandh.com)
http://www.dtreg.com (decision tree modeling)
http://www.nlreg.com (nonlinear regression)
Kim Gräsman
2004-07-25 15:10:15 UTC
Permalink
Hi Phil,
Post by Phil Sherrod
OK. If I change the parameter to be [in, out], what is the proper way to
deallocate any string that VB passed in before allocating my output string?
Would VariantClear() be the correct method to use?
Yes, that's the way to go.
Post by Phil Sherrod
[snip]
I called this 100,000,000 times from within a loop in a VB client application
and monitored memory usage. There was no increase in memory usage during the
looping, so it does not appear to be leaking memory.
The VT_BYREF flag is set on entry as is VT_BSTR. In the case of a [out,retval]
parameter, VT_BYREF is not set. I tried changing the type of the variable
declaration in the calling VB client, and the passed in VT_xxx flags correctly
indicate the type of the parameter.
I would appreciate your comments about this procedure.
There's a potential problem with C++ clients here. It's perfectly legal for
a client to do this:

// No need to initialize these, they'll be overwritten anyway
VARIANT va, vaRet;
hr = pTest->ReturnVariant(&va, &vaRet);
if(FAILED(hr)) return hr;

Since C++ does no automatic initialization, the contents of va is basically
garbage, and your VariantClear is most likely going to blow.

Try configuring this test object as a server application in COM+, if you
know how, and attempt to call it cross process - you should see that the
[out] parameter's original contents doesn't transport from VB into your
server, but rather you will be VariantClear:ing a temporary VARIANT set up
by the marshaller. Or maybe easier, build a simple EXE server with the same
test object, and call that from VB. That should expose the same behaviour.

Essentially your current code is working as if outString was [in, out], so
you should mark it as [in, out]. The fact that it works with VB is mostly
luck, in my opinion.
--
Best regards,
Kim Gräsman
Phil Sherrod
2004-07-25 18:02:44 UTC
Permalink
Post by Phil Sherrod
Post by Phil Sherrod
OK. If I change the parameter to be [in, out], what is the proper way to
deallocate any string that VB passed in before allocating my output
string?
Post by Phil Sherrod
Would VariantClear() be the correct method to use?
Yes, that's the way to go.
OK, that sounds like a plan: I will switch to [in,out] and use VariantClear().

Please confirm that with [in,out] there is no need for the calling VB(S) client
to initialize the string being passed in.

Is it possible to change the parameter types from [out] to [in,out] in existing
methods, or am I going to have to come up with new methods with alternate names
or (worse) start over with a new COM object project?
--
Phil Sherrod
(phil.sherrod 'at' sandh.com)
http://www.dtreg.com (decision tree modeling)
http://www.nlreg.com (nonlinear regression)
Kim Gräsman
2004-07-25 18:42:53 UTC
Permalink
Phil,
Post by Phil Sherrod
Please confirm that with [in,out] there is no need for the calling VB(S) client
to initialize the string being passed in.
VB(S) initializes implicitly, so yes, I guess. A C++ client MUST initialize
[in, out], however, as opposed to [out]-only.
Post by Phil Sherrod
Is it possible to change the parameter types from [out] to [in,out] in existing
methods, or am I going to have to come up with new methods with alternate names
or (worse) start over with a new COM object project?
It's possible, but it's really a break of the interface since existing
clients are expected to initialize any VARIANTs they pass in. As I
mentioned, though, for late-bound VB(S) clients, it doesn't matter, since
they initialize their VARIANTs implicitly.
--
Best regards,
Kim Gräsman
Phil Sherrod
2004-07-25 19:01:23 UTC
Permalink
Post by Phil Sherrod
Post by Phil Sherrod
OK. If I change the parameter to be [in, out], what is the proper way to
deallocate any string that VB passed in before allocating my output
string?
Post by Phil Sherrod
Would VariantClear() be the correct method to use?
Yes, that's the way to go.
I wrote a test method with [in,out] parameter and used VariantClear() as shown
below. It had a severe memory leak and quickly used hundreds of megabytes of
virtual memory.

I reread the description of VariantClear() and found this:

"If the variant to be cleared is a COM object that is passed by reference, the
vt field of the pvarg parameter is VT_DISPATCH | VT_BYREF or VT_UNKNOWN |
VT_BYREF. In this case, VariantClear does not release the object. Because the
variant being cleared is a pointer to a reference to an object, VariantClear
has no way to determine if it is necessary to release the object. It is
therefore the responsibility of the caller to release the object or not, as
appropriate."

The VARIANT object for my [in,out] parameter does have VT_BYREF set, so
(according to the above description) VariantClear() is not going to free the
string. Hence we have a major memory leak.

Would you suggest that I manually free the string space using SysFreeString()?

Are there any simple, working examples of passing string values out of C++ COM
objects through either [in,out] or [out] parameters other than the final
[out,retval] parameter? I can't believe I'm the only person trying to do this.

Here is the routine I wrote that has the memory leak because VariantClear()
isn't freeing string space if VT_BYREF is set:

STDMETHODIMP CTest::ReturnInout(VARIANT *outString, VARIANT *outResult)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState())

char buf[100];
long outlen;
VARTYPE type;
strcpy(buf,"Text copied to buf");

type = outString->vt;
VariantClear(outString);
outString->vt = type;
outlen = strlen(buf);
if (outString->vt & VT_BYREF) {
*outString->pbstrVal = ::SysAllocStringLen(0,outlen);
mbstowcs(*outString->pbstrVal,buf,outlen);
} else {
outString->bstrVal = ::SysAllocStringLen(0,outlen);
mbstowcs(outString->bstrVal,buf,outlen);
}
return S_OK;
}
--
Phil Sherrod
(phil.sherrod 'at' sandh.com)
http://www.dtreg.com (decision tree modeling)
http://www.nlreg.com (nonlinear regression)
Kim Gräsman
2004-07-25 19:37:24 UTC
Permalink
Hi Phil,
Post by Phil Sherrod
I wrote a test method with [in,out] parameter and used VariantClear() as shown
below. It had a severe memory leak and quickly used hundreds of megabytes of
virtual memory.
"If the variant to be cleared is a COM object that is passed by reference, the
vt field of the pvarg parameter is VT_DISPATCH | VT_BYREF or VT_UNKNOWN |
VT_BYREF. In this case, VariantClear does not release the object. Because the
variant being cleared is a pointer to a reference to an object, VariantClear
has no way to determine if it is necessary to release the object. It is
therefore the responsibility of the caller to release the object or not, as
appropriate."
Gah! I had no idea... I thought VariantClear was smarter than that.

You would have to dereference the VARIANT then, before clearing its
contents.

Try something like:

HRESULT DeepVariantClear(VARIANT* pvar)
{
HRESULT hr;

if(pvar->vt & VT_BYREF)
{
VARTYPE actualType = (pvar->vt & (~VT_BYREF));
switch(actualType)
{
case VT_BSTR:
SysFreeString(*(pvar->pbstrVal));
break;
case VT_UNKNOWN:
case VT_DISPATCH:
IUnknown* pUnkVal = *(pvar->ppunkVal);
if(pUnkVal)
{
pUnkVal->Release();
}
break;
case VT_VARIANT:
hr = VariantClear(pvar->pvarVal);
if(FAILED(hr)) return hr;
break;
}
}

return VariantClear(pvar);
}

And call DeepVariantClear instead of VariantClear from your method. You may
want to take care of VT_ARRAY's there as well, and possibly VT_RECORD, etc.

I'm so sorry about the disinformation, I had no idea VariantClear did not
address by-ref Variants.
Thanks for persisting.
--
Best regards,
Kim Gräsman
Igor Tandetnik
2004-07-26 22:35:05 UTC
Permalink
Post by Kim Gräsman
Post by Phil Sherrod
"If the variant to be cleared is a COM object that is passed by
reference, the vt field of the pvarg parameter is VT_DISPATCH |
VT_BYREF or VT_UNKNOWN | VT_BYREF. In this case, VariantClear does
not release the object. Because the variant being cleared is a
pointer to a reference to an object, VariantClear has no way to
determine if it is necessary to release the object. It is therefore
the responsibility of the caller to release the object or not, as
appropriate."
Gah! I had no idea... I thought VariantClear was smarter than that.
It is plenty smart. A VT_BYREF variant does not own the resource it
holds a reference to. Hence VariantClear is not supposed to release it.
You can legally have

VARIANT var;
var.vt = VT_BSTR;
var.bstrVal = SysAllocString(OLESTR("blah"));

VARIANT varRef;
varRef.vt = VT_BSTR | VT_BYREF;
varRef.pbstrVal = &var.bstrVal;

VariantClear(&varRef); // does not relase the string
VariantClear(&var); // releases the string
Post by Kim Gräsman
You would have to dereference the VARIANT then, before clearing its
contents.
Illegal. The server is not supposed to touch the referred-to data, as
opposed to the data directly contained in the VARIANT. Straight
VariantClear implements correct semantics.
--
With best wishes,
Igor Tandetnik

"For every complex problem, there is a solution that is simple, neat,
and wrong." H.L. Mencken
Phil Sherrod
2004-07-26 23:04:24 UTC
Permalink
Post by Igor Tandetnik
It is plenty smart. A VT_BYREF variant does not own the resource it
holds a reference to. Hence VariantClear is not supposed to release it.
Illegal. The server is not supposed to touch the referred-to data, as
opposed to the data directly contained in the VARIANT. Straight
VariantClear implements correct semantics.
OK, then how do you return a value through an [in,out] parameter. The VT_BYREF
flag is set in all of the cases that I've seen (VB client). Since VariantClear
does not free the string that was passed in, don't you have to manually free it
before setting a pointer to the new, output string? Surely you're not
suggesting that I just store a pointer into pbstrVal overwriting the old string
pointer with freeing it first.

Here is the code that I am currently using. It has worked correctly in a
variety of tests:

/*--------------------------------------------------------------------
* Store a string into an output VARIANT com object.
*/
void StoreComString(VARIANT *v, char *outstr)
{
long outlen;

outlen = strlen(outstr);
if (v->vt & VT_BYREF) {
/* ByRef case: If there is an allocated string already, free it */
if (*v->pbstrVal != 0) {
::SysFreeString(*v->pbstrVal);
}
/* Allocate space for the output BSTR string */
*v->pbstrVal = ::SysAllocStringLen(0,outlen);
/* Copy our string to buffer and convert asciz to wide char */
mbstowcs(*v->pbstrVal,outstr,outlen);
} else {
/* Not ByRef: If there is an allocated string already, free it */
if (v->bstrVal != 0) {
::SysFreeString(v->bstrVal);
}
/* Allocate space for the output BSTR string */
v->bstrVal = ::SysAllocStringLen(0,outlen);
/* Copy our string to buffer and convert asciz to wide char */
mbstowcs(v->bstrVal,outstr,outlen);
}
/*
* Finished
*/
return;
}
--
Phil Sherrod
(phil.sherrod 'at' sandh.com)
http://www.dtreg.com (decision tree modeling)
http://www.nlreg.com (nonlinear regression)
Igor Tandetnik
2004-07-26 23:07:27 UTC
Permalink
Post by Phil Sherrod
OK, then how do you return a value through an [in,out] parameter.
See my other post in this thread.
--
With best wishes,
Igor Tandetnik

"For every complex problem, there is a solution that is simple, neat,
and wrong." H.L. Mencken
Igor Tandetnik
2004-07-26 22:39:19 UTC
Permalink
Post by Phil Sherrod
Here is the routine I wrote that has the memory leak because
STDMETHODIMP CTest::ReturnInout(VARIANT *outString, VARIANT
*outResult) {
AFX_MANAGE_STATE(AfxGetStaticModuleState())
char buf[100];
long outlen;
VARTYPE type;
strcpy(buf,"Text copied to buf");
type = outString->vt;
VariantClear(outString);
outString->vt = type;
outlen = strlen(buf);
if (outString->vt & VT_BYREF) {
*outString->pbstrVal = ::SysAllocStringLen(0,outlen);
mbstowcs(*outString->pbstrVal,buf,outlen);
} else {
outString->bstrVal = ::SysAllocStringLen(0,outlen);
mbstowcs(outString->bstrVal,buf,outlen);
}
return S_OK;
}
You should not do this. The VARIANT is passed as [in, out], meaning that
you can change it at will. Just stuff it with new data, type and all.
Like this:

VariantClear(outString);
outString->vt = VT_BSTR;
outString.bstrVal = SysAllocString(...);

If you insist on preserving the type for some reason, you need to free
outString->pbstrVal before overwriting it. By the way, you never even
check that the variant is VT_BSTR (byref or otherwise) on input - what
if it is a VT_I4 or something?
--
With best wishes,
Igor Tandetnik

"For every complex problem, there is a solution that is simple, neat,
and wrong." H.L. Mencken
Phil Sherrod
2004-07-26 23:09:33 UTC
Permalink
Post by Igor Tandetnik
You should not do this. The VARIANT is passed as [in, out], meaning that
you can change it at will. Just stuff it with new data, type and all.
VariantClear(outString);
outString->vt = VT_BSTR;
outString.bstrVal = SysAllocString(...);
Your code loses the VT_BYREF flag which is set in all of the cases I have
checked. It also does not free the string that was passed in, so I believe you
have a major memory leak
Post by Igor Tandetnik
By the way, you never even
check that the variant is VT_BSTR (byref or otherwise) on input - what
if it is a VT_I4 or something?
For brevity in this discussion, I extracted the VT_BSTR case from a larger
routine that handles the other types appropriately. The other types are easy
because they do not require memory allocation. Here is the full routine.
There are similar routines for passing out integer and real values into all
possible data types.

/*-------------------------------------------------------------------
* Output a string parameter value.
*/
BOOL PutString(VARIANT *v, char *outstr)
{
VARTYPE type,byref;
long value;

/*
* See if this is a call by reference.
*/
type = v->vt & ~VT_BYREF;
byref = (v->vt & VT_BYREF);
if (type != VT_BSTR) {
/* Convert string to integer value for output to integer type data parameters
*/
value = atol(outstr);
}
/*
* Initialize the variant.
*/
VariantClear(v);
v->vt = type | byref;
/*
* Return the requested type of value.
*/
switch (type) {
case VT_I2: /* Short */
if (byref) {
*v->piVal = (short)value;
} else {
v->iVal = (short)value;
}
break;
case VT_UI2: /* Unsigned Short */
if (byref) {
*v->puiVal = (unsigned short)value;
} else {
v->uiVal = (unsigned short)value;
}
break;
case VT_I4: /* Long */
if (byref) {
*v->plVal = (long)value;
} else {
v->lVal = (long)value;
}
break;
case VT_UI4: /* Unsigned Long */
if (byref) {
*v->pulVal = (unsigned long)value;
} else {
v->ulVal = (unsigned long)value;
}
break;
case VT_INT: /* Int */
if (byref) {
*v->pintVal = (int)value;
} else {
v->intVal = (int)value;
}
break;
case VT_UINT: /* Unsigned Int */
if (byref) {
*v->puintVal = (unsigned int)value;
} else {
v->uintVal = (unsigned int)value;
}
break;
case VT_R4: /* Real */
if (byref) {
*v->pfltVal = (float)atof(outstr);
} else {
v->fltVal = (float)atof(outstr);
}
break;
case VT_R8: /* Double */
if (byref) {
*v->pdblVal = (double)atof(outstr);
} else {
v->dblVal = (double)atof(outstr);
}
break;
case VT_BSTR: /* String */
StoreComString(v,outstr);
break;
default:
return(FALSE);
}
/*
* Finished
*/
return(TRUE);
}

/*--------------------------------------------------------------------
* Store a string into an output VARIANT com object.
*/
void StoreComString(VARIANT *v, char *outstr)
{
long outlen;

outlen = strlen(outstr);
if (v->vt & VT_BYREF) {
/* If there is an allocated string already, free it */
if (*v->pbstrVal != 0) {
::SysFreeString(*v->pbstrVal);
}
/* Allocate space for the output BSTR string */
*v->pbstrVal = ::SysAllocStringLen(0,outlen);
/* Copy our string to buffer and convert asciz to wide char */
mbstowcs(*v->pbstrVal,outstr,outlen);
} else {
/* If there is an allocated string already, free it */
if (v->bstrVal != 0) {
::SysFreeString(v->bstrVal);
}
/* Allocate space for the output BSTR string */
v->bstrVal = ::SysAllocStringLen(0,outlen);
/* Copy our string to buffer and convert asciz to wide char */
mbstowcs(v->bstrVal,outstr,outlen);
}
/*
* Finished
*/
return;
}
--
Phil Sherrod
(phil.sherrod 'at' sandh.com)
http://www.dtreg.com (decision tree modeling)
http://www.nlreg.com (nonlinear regression)
Igor Tandetnik
2004-07-26 23:14:11 UTC
Permalink
Post by Phil Sherrod
Post by Igor Tandetnik
You should not do this. The VARIANT is passed as [in, out], meaning
that you can change it at will. Just stuff it with new data, type
VariantClear(outString);
outString->vt = VT_BSTR;
outString.bstrVal = SysAllocString(...);
Your code loses the VT_BYREF flag which is set in all of the cases I
have checked.
It's not supposed to keep it.
Post by Phil Sherrod
It also does not free the string that was passed in,
so I believe you have a major memory leak
It is not supposed to free it.

The referred-to data is owned by the client, not by the VARIANT that
refers to it. If you actually want to replace the referred-to data, you
need to free it first of course. However, it is perfectly valid to
change the VARIANT itself to contain new data and lose the reference.
The client still owns the original data, the variant just does not refer
to it anymore.
--
With best wishes,
Igor Tandetnik

"For every complex problem, there is a solution that is simple, neat,
and wrong." H.L. Mencken
Alexander Nickolov
2004-07-27 00:04:16 UTC
Permalink
Igor, that's not the way things work with VT_BYREF. One has
to modify the data referenced by the VARIANT and leave the
topmost VARIANT intact as the OP was saying. The VARIANT
itself is not the data, you can consider it as a pointer.
--
=====================================
Alexander Nickolov
Microsoft MVP [VC], MCSD
email: ***@mvps.org
MVP VC FAQ: http://www.mvps.org/vcfaq
=====================================
Post by Igor Tandetnik
Post by Phil Sherrod
Post by Igor Tandetnik
You should not do this. The VARIANT is passed as [in, out], meaning
that you can change it at will. Just stuff it with new data, type
VariantClear(outString);
outString->vt = VT_BSTR;
outString.bstrVal = SysAllocString(...);
Your code loses the VT_BYREF flag which is set in all of the cases I
have checked.
It's not supposed to keep it.
Post by Phil Sherrod
It also does not free the string that was passed in,
so I believe you have a major memory leak
It is not supposed to free it.
The referred-to data is owned by the client, not by the VARIANT that
refers to it. If you actually want to replace the referred-to data, you
need to free it first of course. However, it is perfectly valid to
change the VARIANT itself to contain new data and lose the reference.
The client still owns the original data, the variant just does not refer
to it anymore.
--
With best wishes,
Igor Tandetnik
"For every complex problem, there is a solution that is simple, neat,
and wrong." H.L. Mencken
Kim Gräsman
2004-07-27 08:45:51 UTC
Permalink
Now I'm curious, this seemed so simple. I think Igor's reasoning sounds good
from a convenience standpoint, and for the fact that the client does indeed
own the data.

Are you sure, Alexander? I don't know what I think anymore...
--
Best regards,
Kim Gräsman
Post by Alexander Nickolov
Igor, that's not the way things work with VT_BYREF. One has
to modify the data referenced by the VARIANT and leave the
topmost VARIANT intact as the OP was saying. The VARIANT
itself is not the data, you can consider it as a pointer.
Igor Tandetnik
2004-07-27 13:36:49 UTC
Permalink
Post by Alexander Nickolov
Igor, that's not the way things work with VT_BYREF. One has
to modify the data referenced by the VARIANT and leave the
topmost VARIANT intact as the OP was saying. The VARIANT
itself is not the data, you can consider it as a pointer.
You sure? We are talking [in, out] VARIANT* parameter here. I'd expect
that I can safely ignore any [in] data (except that I need to clear it
with VariantClear before replacing), and put whatever I want in the
VARIANT - that's what [out] part is all about. Am I missing something
obvious?

Are you maybe thinking about something like [in, out] BSTR* parameter
that comes wrapped into VT_BSTR | VT_BYREF in IDispatch::Invoke? Here of
course you need to store into a BSTR*, not into the VARIANT itself.
--
With best wishes,
Igor Tandetnik

"For every complex problem, there is a solution that is simple, neat,
and wrong." H.L. Mencken
Phil Sherrod
2004-07-27 14:25:22 UTC
Permalink
Post by Igor Tandetnik
Post by Alexander Nickolov
Igor, that's not the way things work with VT_BYREF. One has
to modify the data referenced by the VARIANT and leave the
topmost VARIANT intact as the OP was saying. The VARIANT
itself is not the data, you can consider it as a pointer.
You sure? We are talking [in, out] VARIANT* parameter here. I'd expect
that I can safely ignore any [in] data (except that I need to clear it
with VariantClear before replacing), and put whatever I want in the
VARIANT - that's what [out] part is all about. Am I missing something
obvious?
Yes, I believe you are missing a couple of things:

1. [in, out] parameters from VB are passed with VT_BYREF set. VariantClear will
not release memory for a parameter that has VT-BYREF set (see the description
of VariantClear).

2. If you just use SysAlloc to allocate new memory and cram it into a VARIANT
without freeing the memory space that was passed in, you will have a memory
leak. (This should be obvious).

3. The code you suggested stored a pointer to the newly allocated memory into
the bstrVal member of the VARIANT rather than going indirect and using
pbstrVal. That is,

You suggested:

v->bstrVal = new-mem-pointer

rather than

*v->pbstrVal = new-mem-ptr

I am 99.95% sure that doing this will NOT transfer the new data back to the
client when VT_BYREF is set (which is always the case for [in, out] parameters
from VB). Note that if VT_BYREF is not set (which is always the case for [out,
retval] parameters), then you do store the pointer into bstrVal. In the case of
[out,retval] parameters there is no allocated memory coming from the client
that needs to be freed.

I've now done a a good bit of testing, and I can say with confidence that for
an in-process, local, DLL COM object the following code works correctly:

/*--------------------------------------------------------------------
* Store a string into an output VARIANT com object.
*/
void StoreComString(VARIANT *v, char *outstr)
{
long outlen;

outlen = strlen(outstr);
if (v->vt & VT_BYREF) {
/* If there is an allocated string already, free it */
if (*v->pbstrVal != 0) {
::SysFreeString(*v->pbstrVal);
}
/* Allocate space for the output BSTR string */
*v->pbstrVal = ::SysAllocStringLen(0,outlen);
/* Copy our string to buffer and convert asciz to wide char */
mbstowcs(*v->pbstrVal,outstr,outlen);
} else {
/* If there is an allocated string already, free it */
if (v->bstrVal != 0) {
::SysFreeString(v->bstrVal);
}
/* Allocate space for the output BSTR string */
v->bstrVal = ::SysAllocStringLen(0,outlen);
/* Copy our string to buffer and convert asciz to wide char */
mbstowcs(v->bstrVal,outstr,outlen);
}
/*
* Finished
*/
return;
}

If you disagree with this, please write alternate code, verify that the
returned value is getting back to the VB client, test it by calling it a few
million times to verify that you don't have a memory leak, and then post it
here. I have done these tests using my code, and it works.
--
Phil Sherrod
(phil.sherrod 'at' sandh.com)
http://www.dtreg.com (decision tree modeling)
http://www.nlreg.com (nonlinear regression)
Igor Tandetnik
2004-07-27 14:57:02 UTC
Permalink
Post by Phil Sherrod
Post by Igor Tandetnik
You sure? We are talking [in, out] VARIANT* parameter here. I'd
expect that I can safely ignore any [in] data (except that I need to
clear it with VariantClear before replacing), and put whatever I
want in the VARIANT - that's what [out] part is all about. Am I
missing something obvious?
1. [in, out] parameters from VB are passed with VT_BYREF set.
VariantClear will not release memory for a parameter that has
VT-BYREF set (see the description of VariantClear).
It does _not_ matter what's passed [in] in a VARIANT, as long as it's
cleared with VariantClear before being overwritten. VB may choose to
pass ByRef data all day long - it does not matter in the slightest,
unless you are actually interested in the input, which you are
apparently not.

A BSTR passed as VT_BSTR is owned by the variant and must be cleared
first. A BSTR pointed to by pbstrVal in VT_BSTR | VT_BYREF variant is
not owned by this variant and does not need to be cleared. It only needs
to be cleared if you intend to overwrite the string itself, as in

SysFreeString(*var.pbstrVal);
*var.pbstrVal = SysAllocString(OLESTR("blah"));

But an equally valid strategy is to store the new string directly in the
VARIANT, as in

var.vt = VT_BSTR;
var.bstrVal = SysAllocString(OLESTR("blah"));

This all assuming your parameter is declared as [in, out] VARIANT*.
[out] here means you can legally change the type of the variant, as well
as the value.

Imagine that the server wants to return a long via [in, out] VARIANT. So
it does something like this:

DeepVariantClear(pvar); // this follows VT_BYREFs as you want to do
pvar->vt = VT_I4;
pvar->longVal = 1;

The client does not know that the server returns an integer, and for
whatever reason it chooses to pass a ByRef string (perhaps a leftover
from earlier code):

BSTR myStr = SysAllocString(...);
VARIANT var;
var.vt = VT_BSTR | VT_BYREF;
var.pbstrVal = &myStr;

pYourObject->YourMethod(&var);

Oops! Suddently I lost my fine string (hopefully you also NULL the
variable, not just free the memory - this at least gives the client some
hope not to crash soon afterwards), and got some long instead. This is
_not_ supposed to happen. var does not own the BSTR - myStr does.
Post by Phil Sherrod
2. If you just use SysAlloc to allocate new memory and cram it into a
VARIANT without freeing the memory space that was passed in, you will
have a memory leak. (This should be obvious).
But no memory space was passed in. An indirect reference to a memory
space was passed in. Some other variable in the client code owns it, not
the variant. Freeing a memory that does not belong to you is at least as
bad, and probably worse, than not freeing memory you are supposed to
free.
Post by Phil Sherrod
3. The code you suggested stored a pointer to the newly allocated
memory into the bstrVal member of the VARIANT rather than going
indirect and using pbstrVal.
Right. I also changed the type to match. If you don't want that, declare
your parameter as [in, out] BSTR*, not [in, out] VARIANT*.
Post by Phil Sherrod
v->bstrVal = new-mem-pointer
rather than
*v->pbstrVal = new-mem-ptr
Both are legal (assuming the original variant is indeed VT_BSTR |
VT_BYREF). In the first case, you need to change the variant type to
VT_BSTR. In the second case, you need to free the original string first.
Post by Phil Sherrod
I am 99.95% sure that doing this will NOT transfer the new data back
to the client when VT_BYREF is set (which is always the case for [in,
out] parameters from VB).
That's why I also change the type to VT_BSTR.
Post by Phil Sherrod
I've now done a a good bit of testing, and I can say with confidence
that for an in-process, local, DLL COM object the following code
Right. Like I said, both appoaches are correct. You just choose to
conditionally execute one or the other, unnecessarily complicating your
code. Besides, I don't see how you are going to handle the situation
where the incoming variant is neither VT_BSTR nor VT_BSTR | VT_BYREF.
Imagine the following valid VB code

Dim v As Object
v = 1
StoreComString v

What are you going to do here? The incoming variant would be either
VT_I4 or VT_I4 | VT_BYREF. What if I replace v = 1 with v =
CreateObject("SomeProgId") ?
--
With best wishes,
Igor Tandetnik

"For every complex problem, there is a solution that is simple, neat,
and wrong." H.L. Mencken
Phil Sherrod
2004-07-28 00:44:57 UTC
Permalink
Post by Igor Tandetnik
It does _not_ matter what's passed [in] in a VARIANT, as long as it's
cleared with VariantClear before being overwritten. VB may choose to
pass ByRef data all day long - it does not matter in the slightest,
unless you are actually interested in the input, which you are
apparently not.
A BSTR passed as VT_BSTR is owned by the variant and must be cleared
first. A BSTR pointed to by pbstrVal in VT_BSTR | VT_BYREF variant is
not owned by this variant and does not need to be cleared. It only needs
to be cleared if you intend to overwrite the string itself, as in
SysFreeString(*var.pbstrVal);
*var.pbstrVal = SysAllocString(OLESTR("blah"));
But an equally valid strategy is to store the new string directly in the
VARIANT, as in
var.vt = VT_BSTR;
var.bstrVal = SysAllocString(OLESTR("blah"));
This all assuming your parameter is declared as [in, out] VARIANT*.
[out] here means you can legally change the type of the variant, as well
as the value.
You are absolutely wrong. I tested the following routine which corresponds to
your suggestion:

STDMETHODIMP CTest::ReturnVariant(VARIANT *outString, VARIANT *outStatus)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState())

char buf[100];
long outlen;

strcpy(buf,"Test output text");
VariantClear(outString);
outString->vt = VT_BSTR;
outlen = strlen(buf);
outString->bstrVal = ::SysAllocStringLen(0,outlen);
mbstowcs(outString->bstrVal,buf,outlen);
return S_OK;
}

Here are the results of the test:

1. The string value is NOT returned to be VB client.

2. There is a severe memory leak (because the string passed in from the client
is not freed by VariantClear).

Are you just throwing out suggestions off the top of your head, or have you
actually implemented a COM routine that returns string value through a
parameter to a VB client?

For those following this thread, here is a routine that works correctly:

STDMETHODIMP CTest::ReturnVariant(VARIANT *outString, VARIANT *outStatus)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState())

char buf[100];
long outlen;

strcpy(buf,"Test output text");
outlen = strlen(buf);
if (outString->vt & VT_BYREF) {
*outString->pbstrVal = ::SysAllocStringLen(0,outlen);
mbstowcs(*outString->pbstrVal,buf,outlen);
} else {
outString->bstrVal = ::SysAllocStringLen(0,outlen);
mbstowcs(outString->bstrVal,buf,outlen);
}
return S_OK;
}

This routine (1) correctly returns the string parameter, and (2) it does not
leak memory.

Igor, you need to do some experimenting before recommending solutions.
--
Phil Sherrod
(phil.sherrod 'at' sandh.com)
http://www.dtreg.com (decision tree modeling)
http://www.nlreg.com (nonlinear regression)
Kim Gräsman
2004-07-28 07:46:47 UTC
Permalink
Hi Phil,
Post by Phil Sherrod
I tested the following routine which corresponds to
STDMETHODIMP CTest::ReturnVariant(VARIANT *outString, VARIANT *outStatus)
{
[snip]
}
1. The string value is NOT returned to be VB client.
2. There is a severe memory leak (because the string passed in from the client
is not freed by VariantClear).
FWIW, I've tested the respective routines, and I am indeed getting the same
result. I think it's kind of weird, but you're right.
Does anybody know if this behaviour is documented properly somewhere? I'm
guessing no.
--
Best regards,
Kim Gräsman
Vi2
2004-07-28 15:12:07 UTC
Permalink
Post by Phil Sherrod
You are absolutely wrong.
You are right with some assumptions, but not right at common case.

STDMETHODIMP CTest::ReturnVariant(VARIANT *outString, VARIANT *outStatus)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState())

if (outString->vt & VT_BYREF)
{

// The special VB's case - passing the variable BY REFERENCE
// You cannot change the variable's type, only its value.
//
// Dim s As String, v
// v = obj.ReturnVariant(s)

if( (outString->vt & VT_TYPEMASK) == VT_BSTR )
{
::SysReAllocString(outString->pbstrVal, L"xx11xx");
}

}
else
{
if( (outString->vt & VT_TYPEMASK) == VT_BSTR )
{
// Dim vs, v
// vs = "some string"
// v = obj.ReturnVariant(vs)

::SysReAllocString(&outString->bstrVal, L"xx22xx");
}
else
{
// Dim vs, v
// vs = 11 ' anything besides "some string" (but can be included)
// v = obj.ReturnVariant(vs)

VariantClear(outString);
outString->vt = VT_BSTR;
outString->bstrVal = ::SysAllocString(L"xx33xx");
}
}

return S_OK;
}
Phil Sherrod
2004-07-28 22:44:51 UTC
Permalink
Post by Vi2
You are right with some assumptions, but not right at common case.
I don't understand what your comment means.
--
Phil Sherrod
(phil.sherrod 'at' sandh.com)
http://www.dtreg.com (decision tree modeling)
http://www.nlreg.com (nonlinear regression)
Alexander Nickolov
2004-07-27 16:28:24 UTC
Permalink
I believe this is a quirk of VB - it creates a reference to its
internal variable and passed it in, even when a VARIANT
is passed. Yes, I'm sure - a VT_BYREF VARIANT should not
be modified, it should be treated as a mere pointer (which
it technically is).
--
=====================================
Alexander Nickolov
Microsoft MVP [VC], MCSD
email: ***@mvps.org
MVP VC FAQ: http://www.mvps.org/vcfaq
=====================================
Post by Igor Tandetnik
Post by Alexander Nickolov
Igor, that's not the way things work with VT_BYREF. One has
to modify the data referenced by the VARIANT and leave the
topmost VARIANT intact as the OP was saying. The VARIANT
itself is not the data, you can consider it as a pointer.
You sure? We are talking [in, out] VARIANT* parameter here. I'd expect
that I can safely ignore any [in] data (except that I need to clear it
with VariantClear before replacing), and put whatever I want in the
VARIANT - that's what [out] part is all about. Am I missing something
obvious?
Are you maybe thinking about something like [in, out] BSTR* parameter
that comes wrapped into VT_BSTR | VT_BYREF in IDispatch::Invoke? Here of
course you need to store into a BSTR*, not into the VARIANT itself.
--
With best wishes,
Igor Tandetnik
"For every complex problem, there is a solution that is simple, neat,
and wrong." H.L. Mencken
Igor Tandetnik
2004-07-27 16:39:22 UTC
Permalink
Post by Alexander Nickolov
I believe this is a quirk of VB - it creates a reference to its
internal variable and passed it in, even when a VARIANT
is passed. Yes, I'm sure - a VT_BYREF VARIANT should not
be modified, it should be treated as a mere pointer (which
it technically is).
What if it passes a wrong type? I need to return a string, but it passes
VT_I4 | VT_BYREF, for example? What do I do then?
--
With best wishes,
Igor Tandetnik

"For every complex problem, there is a solution that is simple, neat,
and wrong." H.L. Mencken
Alexander Nickolov
2004-07-27 16:57:49 UTC
Permalink
You return DISP_E_TYPEMISMATCH of course :).
Though if it is strongly typed in the original interface, VB
won't pass you a reference to another type.
--
=====================================
Alexander Nickolov
Microsoft MVP [VC], MCSD
email: ***@mvps.org
MVP VC FAQ: http://www.mvps.org/vcfaq
=====================================
Post by Igor Tandetnik
Post by Alexander Nickolov
I believe this is a quirk of VB - it creates a reference to its
internal variable and passed it in, even when a VARIANT
is passed. Yes, I'm sure - a VT_BYREF VARIANT should not
be modified, it should be treated as a mere pointer (which
it technically is).
What if it passes a wrong type? I need to return a string, but it passes
VT_I4 | VT_BYREF, for example? What do I do then?
--
With best wishes,
Igor Tandetnik
"For every complex problem, there is a solution that is simple, neat,
and wrong." H.L. Mencken
Igor Tandetnik
2004-07-27 17:13:26 UTC
Permalink
That's what I'm saying - it is not stronly typed in the original
interface. It is declared as [in, out] VARIANT*. Normally, this means
that I can freely change the type of the incoming variant, and return
different types from invocation to invocation, so there is no way for
the client to anticipate the returned type. Are you saying VB does not
support this?

How is the client supposed to guess the type I plan to return, so as to
avoid DISP_E_TYPEMISMATCH? What if the [in] data matters - the server
reads it and acts on it as opposed to simply discarding it - but [out]
data has to have a different type? All this is allowed by [in, out]
VARIANT* contract. So again, are you saying there's a deficiency in VB
whereby this technique is not supported?
--
With best wishes,
Igor Tandetnik

"For every complex problem, there is a solution that is simple, neat,
and wrong." H.L. Mencken
Post by Alexander Nickolov
You return DISP_E_TYPEMISMATCH of course :).
Though if it is strongly typed in the original interface, VB
won't pass you a reference to another type.
Post by Igor Tandetnik
Post by Alexander Nickolov
I believe this is a quirk of VB - it creates a reference to its
internal variable and passed it in, even when a VARIANT
is passed. Yes, I'm sure - a VT_BYREF VARIANT should not
be modified, it should be treated as a mere pointer (which
it technically is).
What if it passes a wrong type? I need to return a string, but it
passes VT_I4 | VT_BYREF, for example? What do I do then?
--
With best wishes,
Igor Tandetnik
"For every complex problem, there is a solution that is simple, neat,
and wrong." H.L. Mencken
Alexander Nickolov
2004-07-28 02:37:25 UTC
Permalink
It'd be a client error if it passes anything other than a VARIANT
then. In your object you'll get VT_BYREF | VT_VARIANT. Then
you modify the referenced VARIANT and the client's variable
(which this really is) is updated.
--
=====================================
Alexander Nickolov
Microsoft MVP [VC], MCSD
email: ***@mvps.org
MVP VC FAQ: http://www.mvps.org/vcfaq
=====================================
Post by Igor Tandetnik
That's what I'm saying - it is not stronly typed in the original
interface. It is declared as [in, out] VARIANT*. Normally, this means
that I can freely change the type of the incoming variant, and return
different types from invocation to invocation, so there is no way for
the client to anticipate the returned type. Are you saying VB does not
support this?
How is the client supposed to guess the type I plan to return, so as to
avoid DISP_E_TYPEMISMATCH? What if the [in] data matters - the server
reads it and acts on it as opposed to simply discarding it - but [out]
data has to have a different type? All this is allowed by [in, out]
VARIANT* contract. So again, are you saying there's a deficiency in VB
whereby this technique is not supported?
--
With best wishes,
Igor Tandetnik
"For every complex problem, there is a solution that is simple, neat,
and wrong." H.L. Mencken
Post by Alexander Nickolov
You return DISP_E_TYPEMISMATCH of course :).
Though if it is strongly typed in the original interface, VB
won't pass you a reference to another type.
Post by Igor Tandetnik
Post by Alexander Nickolov
I believe this is a quirk of VB - it creates a reference to its
internal variable and passed it in, even when a VARIANT
is passed. Yes, I'm sure - a VT_BYREF VARIANT should not
be modified, it should be treated as a mere pointer (which
it technically is).
What if it passes a wrong type? I need to return a string, but it
passes VT_I4 | VT_BYREF, for example? What do I do then?
--
With best wishes,
Igor Tandetnik
"For every complex problem, there is a solution that is simple, neat,
and wrong." H.L. Mencken
Igor Tandetnik
2004-07-28 14:39:45 UTC
Permalink
That would be illegal. If I call this method via IDispatch::Invoke, I
already have to wrap [in, out] VARIANT* parameter as VT_VARIANT |
VT_BYREF in DISPPARAMS array. What you suggest would then lead to double
indirection, which AFAIK is prohibited by Automation. Am I missing
something?

Besides, Phil says VB passes VT_BSTR | VT_BYREF. So, is VB in error here
then?

It also means that pure [out] should be treated differently than the
"out" part of [in, out]. Everywhere else, changing a parameter from
[out] to [in, out] simply means you are responsible for clearing the
value first, then you can proceed the same way as with [out]. You are
saying that in case of VARIANT*, there is a fundamental difference
between [out] and [in, out]. This is inconsistent with the behavior of
any other automation type.

I guess all I'm saying is, the parameter passing protocol made sense to
me up to now, and I thought I understood the logic and the rationale.
But this issue seems to be a hole in the otherwise nice and logical
picture, and I'm trying to figure out who's the guilty party - the spec,
or a specific client (VB) that does not follow the spec, or my faulty
understanding of the spec?
--
With best wishes,
Igor Tandetnik

"For every complex problem, there is a solution that is simple, neat,
and wrong." H.L. Mencken
Post by Alexander Nickolov
It'd be a client error if it passes anything other than a VARIANT
then. In your object you'll get VT_BYREF | VT_VARIANT. Then
you modify the referenced VARIANT and the client's variable
(which this really is) is updated.
Post by Igor Tandetnik
That's what I'm saying - it is not stronly typed in the original
interface. It is declared as [in, out] VARIANT*. Normally, this means
that I can freely change the type of the incoming variant, and return
different types from invocation to invocation, so there is no way for
the client to anticipate the returned type. Are you saying VB does
not support this?
How is the client supposed to guess the type I plan to return, so as
to avoid DISP_E_TYPEMISMATCH? What if the [in] data matters - the
server reads it and acts on it as opposed to simply discarding it -
but [out] data has to have a different type? All this is allowed by
[in, out] VARIANT* contract. So again, are you saying there's a
deficiency in VB whereby this technique is not supported?
--
With best wishes,
Igor Tandetnik
"For every complex problem, there is a solution that is simple, neat,
and wrong." H.L. Mencken
Post by Alexander Nickolov
You return DISP_E_TYPEMISMATCH of course :).
Though if it is strongly typed in the original interface, VB
won't pass you a reference to another type.
Post by Igor Tandetnik
Post by Alexander Nickolov
I believe this is a quirk of VB - it creates a reference to its
internal variable and passed it in, even when a VARIANT
is passed. Yes, I'm sure - a VT_BYREF VARIANT should not
be modified, it should be treated as a mere pointer (which
it technically is).
What if it passes a wrong type? I need to return a string, but it
passes VT_I4 | VT_BYREF, for example? What do I do then?
--
With best wishes,
Igor Tandetnik
"For every complex problem, there is a solution that is simple,
neat, and wrong." H.L. Mencken
Alexander Nickolov
2004-07-28 16:43:34 UTC
Permalink
Don't forget [out] is not supported in Automation/VB, so I'll
skip over that part. Indeed, the semantics of [in, out] and
[out, retval] is completely different. As for your comment
about VT_BSTR | VT_BYREF, note I said the client will be
in error, not VB. It's the client that has to get back a value of
a different type and you tell them so via DISP_E_TYPEMISMATCH.

Actually, the parameter passing makes sense even now if you
consider that in IDispatch::Invoke all references are passed via
VT_BYREF VARIANTs. [in, out] in Automation means
passing by reference. BTW, I suspect (but haven't experimented
with this) that DispInvoke may not be interpreting any VARIANT
in DISPPARAMS corresponding to an [in, out] VARIANT*
argument and passing its address straight on when invoking the
dual interface's method. If the VARIANT in DISPPARAMS
references anything but another VARIANT, it has no other
choice anyway.

The server logic is straightforward, but lengthy. You check for
VT_BYREF and in its presense interpret the referenced value
as best as you can and return your result there. If the VARIANT
doesn't have VT_BYREF, you return your value inside it (after
VariantClear). I think others in this thread posted plenty of code
demonstrating this. The trouble with accepting a VARIANT is
you explicitly declare you can deal with anything the caller can
throw at you...
--
=====================================
Alexander Nickolov
Microsoft MVP [VC], MCSD
email: ***@mvps.org
MVP VC FAQ: http://www.mvps.org/vcfaq
=====================================
Post by Igor Tandetnik
That would be illegal. If I call this method via IDispatch::Invoke, I
already have to wrap [in, out] VARIANT* parameter as VT_VARIANT |
VT_BYREF in DISPPARAMS array. What you suggest would then lead to double
indirection, which AFAIK is prohibited by Automation. Am I missing
something?
Besides, Phil says VB passes VT_BSTR | VT_BYREF. So, is VB in error here
then?
It also means that pure [out] should be treated differently than the
"out" part of [in, out]. Everywhere else, changing a parameter from
[out] to [in, out] simply means you are responsible for clearing the
value first, then you can proceed the same way as with [out]. You are
saying that in case of VARIANT*, there is a fundamental difference
between [out] and [in, out]. This is inconsistent with the behavior of
any other automation type.
I guess all I'm saying is, the parameter passing protocol made sense to
me up to now, and I thought I understood the logic and the rationale.
But this issue seems to be a hole in the otherwise nice and logical
picture, and I'm trying to figure out who's the guilty party - the spec,
or a specific client (VB) that does not follow the spec, or my faulty
understanding of the spec?
--
With best wishes,
Igor Tandetnik
"For every complex problem, there is a solution that is simple, neat,
and wrong." H.L. Mencken
Post by Alexander Nickolov
It'd be a client error if it passes anything other than a VARIANT
then. In your object you'll get VT_BYREF | VT_VARIANT. Then
you modify the referenced VARIANT and the client's variable
(which this really is) is updated.
Post by Igor Tandetnik
That's what I'm saying - it is not stronly typed in the original
interface. It is declared as [in, out] VARIANT*. Normally, this means
that I can freely change the type of the incoming variant, and return
different types from invocation to invocation, so there is no way for
the client to anticipate the returned type. Are you saying VB does
not support this?
How is the client supposed to guess the type I plan to return, so as
to avoid DISP_E_TYPEMISMATCH? What if the [in] data matters - the
server reads it and acts on it as opposed to simply discarding it -
but [out] data has to have a different type? All this is allowed by
[in, out] VARIANT* contract. So again, are you saying there's a
deficiency in VB whereby this technique is not supported?
--
With best wishes,
Igor Tandetnik
"For every complex problem, there is a solution that is simple, neat,
and wrong." H.L. Mencken
Post by Alexander Nickolov
You return DISP_E_TYPEMISMATCH of course :).
Though if it is strongly typed in the original interface, VB
won't pass you a reference to another type.
Post by Igor Tandetnik
Post by Alexander Nickolov
I believe this is a quirk of VB - it creates a reference to its
internal variable and passed it in, even when a VARIANT
is passed. Yes, I'm sure - a VT_BYREF VARIANT should not
be modified, it should be treated as a mere pointer (which
it technically is).
What if it passes a wrong type? I need to return a string, but it
passes VT_I4 | VT_BYREF, for example? What do I do then?
--
With best wishes,
Igor Tandetnik
"For every complex problem, there is a solution that is simple,
neat, and wrong." H.L. Mencken
Igor Tandetnik
2004-07-28 18:15:33 UTC
Permalink
This post might be inappropriate. Click to display it.
Alexander Nickolov
2004-07-29 04:17:27 UTC
Permalink
Inline.
--
=====================================
Alexander Nickolov
Microsoft MVP [VC], MCSD
email: ***@mvps.org
MVP VC FAQ: http://www.mvps.org/vcfaq
=====================================
Post by Igor Tandetnik
Post by Alexander Nickolov
Don't forget [out] is not supported in Automation/VB, so I'll
skip over that part. Indeed, the semantics of [in, out] and
[out, retval] is completely different. As for your comment
about VT_BSTR | VT_BYREF, note I said the client will be
in error, not VB. It's the client that has to get back a value of
a different type and you tell them so via DISP_E_TYPEMISMATCH.
I'm not sure I understand this part. VB _is_ the client in this case, or
more exactly, the client is written in VB. A VB program does not have
fine control of the type of VARIANT passed in - VB runtime is supposed
to take care of this. So, is VB runtime correct, or is it wrong, or is
the calling VB code in error? How do I write a calling code in VB to
request a specific type? How do I write a calling code in VB to indicate
that I can accept any type, whatever the server feels like returning (is
this even possible)?
I guess we refer to differnt things. I refer to VB as the runtime, while
you refer to the source of the VB client. In the client source, if you
pass a variable of any type other than Variant for a ByRef Variant,
you are in grave peril. I didn't even think it was allowed, but looks
like VB is silent... When you do pass a Variant variable, the server
gets VT_BYREF | VT_VARIANT which refers to the client's variable.
Post by Igor Tandetnik
Post by Alexander Nickolov
Actually, the parameter passing makes sense even now if you
consider that in IDispatch::Invoke all references are passed via
VT_BYREF VARIANTs. [in, out] in Automation means
passing by reference. BTW, I suspect (but haven't experimented
with this) that DispInvoke may not be interpreting any VARIANT
in DISPPARAMS corresponding to an [in, out] VARIANT*
argument and passing its address straight on when invoking the
dual interface's method. If the VARIANT in DISPPARAMS
references anything but another VARIANT, it has no other
choice anyway.
Ok, this is a possibility. So you are saying that [in, out] VARIANT* is
unique in that, for all other types, DispInvoke needs to parse a variant
from DISPPARAMS, but for this single type it just passes one as-is? This
would mean there is a discontinuity in the type system after all. If I
consider [in, out] VARIANT* as a special case that does not have to be
consistent with the rest of the model, things do start to click together
for me. I find it surprising that a special case exists though.
Post by Alexander Nickolov
The server logic is straightforward, but lengthy. You check for
VT_BYREF and in its presense interpret the referenced value
as best as you can and return your result there.
And you are forced to use the type indicated by the client for your
[out] data, right?
Correct. Hopefully, it is again a VARIANT...
Post by Igor Tandetnik
Post by Alexander Nickolov
If the VARIANT
doesn't have VT_BYREF, you return your value inside it (after
VariantClear).
And in this case, you can change the type of the variant and return data
that does not necessarily match the type of incoming data, right?
Of course.
Post by Igor Tandetnik
--
With best wishes,
Igor Tandetnik
"For every complex problem, there is a solution that is simple, neat,
and wrong." H.L. Mencken
Phil Sherrod
2004-07-28 03:01:57 UTC
Permalink
Post by Igor Tandetnik
That's what I'm saying - it is not stronly typed in the original
interface. It is declared as [in, out] VARIANT*. Normally, this means
that I can freely change the type of the incoming variant, and return
different types from invocation to invocation, so there is no way for
the client to anticipate the returned type. Are you saying VB does not
support this?
You should not return arbitrary type parameters. You should check the vt field
to see what type of parameter the client expects, and then transform the output
value into the type expected by the client (if possible). So if you are trying
to return a long int value and the client expects a double precision floating
point value, you should cast the type of your long into into a double float and
store it into the dblVal cell of the VARIANT structure. If it is not practical
to do such a conversion, then you can trigger an exception. But if the client
expects a string parameter, you definitely should not try to force the VARIANT
type to some other type.
--
Phil Sherrod
(phil.sherrod 'at' sandh.com)
http://www.dtreg.com (decision tree modeling)
http://www.nlreg.com (nonlinear regression)
Igor Tandetnik
2004-07-28 14:53:26 UTC
Permalink
Post by Phil Sherrod
You should not return arbitrary type parameters. You should check
the vt field to see what type of parameter the client expects, and
then transform the output value into the type expected by the client
(if possible). So if you are trying to return a long int value and
the client expects a double precision floating point value, you
should cast the type of your long into into a double float and store
it into the dblVal cell of the VARIANT structure. If it is not
practical to do such a conversion, then you can trigger an exception.
But if the client expects a string parameter, you definitely should
not try to force the VARIANT type to some other type.
So you are saying that in [in, out] VARIANT* parameter, vt field is a
hint to the server as to what type the client wants the data in, and is
not to be changed. Kind of like IDataObject::GetData, where the client
supplies the format it expects, and the server has to oblige or fail
trying. I don't see this view supported by the documentation, but then
the Automation docs are far from complete and unambigouos, and in the
contest between theory and reality, reality wins.

I wonder what happens if one passes an untyped variable this way -
something like this:

Dim v As Object
ReturnVariant v

What does VB pass to ReturnVariant in this case?
--
With best wishes,
Igor Tandetnik

"For every complex problem, there is a solution that is simple, neat,
and wrong." H.L. Mencken
Kim Gräsman
2004-07-28 15:40:07 UTC
Permalink
Hi Igor,
Post by Igor Tandetnik
I wonder what happens if one passes an untyped variable this way -
Dim v As Object
ReturnVariant v
What does VB pass to ReturnVariant in this case?
Object translates to IDispatch, as far as I know, but I assume you meant:

Dim v ' Implies Variant
ReturnVariant v

That's an interesting question - VT_EMPTY? VT_NULL? Oh, and if you can't
change it, that's pretty much useless.
--
Best regards,
Kim Gräsman
Alexander Nickolov
2004-07-29 04:18:22 UTC
Permalink
VT_BYREF | VT_VARIANT
--
=====================================
Alexander Nickolov
Microsoft MVP [VC], MCSD
email: ***@mvps.org
MVP VC FAQ: http://www.mvps.org/vcfaq
=====================================
Post by Kim Gräsman
Hi Igor,
Post by Igor Tandetnik
I wonder what happens if one passes an untyped variable this way -
Dim v As Object
ReturnVariant v
What does VB pass to ReturnVariant in this case?
Dim v ' Implies Variant
ReturnVariant v
That's an interesting question - VT_EMPTY? VT_NULL? Oh, and if you can't
change it, that's pretty much useless.
--
Best regards,
Kim Gräsman
Phil Sherrod
2004-07-28 02:57:20 UTC
Permalink
Post by Alexander Nickolov
I believe this is a quirk of VB - it creates a reference to its
internal variable and passed it in, even when a VARIANT
is passed. Yes, I'm sure - a VT_BYREF VARIANT should not
be modified, it should be treated as a mere pointer (which
it technically is).
I disagree. I believe the string pointed to by pbstrVal should be freed using
SysFreeString before allocating a new one using SysAllocString and storing its
pointer into pbstrVal.
--
Phil Sherrod
(phil.sherrod 'at' sandh.com)
http://www.dtreg.com (decision tree modeling)
http://www.nlreg.com (nonlinear regression)
Alexander Nickolov
2004-07-28 16:49:32 UTC
Permalink
That doesn't contradict what I said. Notice you are not going to
change the pointer inside the VARIANT, you'll overwrite the
location where it points to (it's a double pointer). Technically,
that doesn't change the VARIANT. Your comment about
storing the new pointer in pbstrVal is incorrect, you need to
store it in *pbstrVal. (Actually, your confusion stem from the
fact you think the VARIANT points to the string, but it doesn't.
It points to a pointer to the string - remember BSTR is a pointer.)
--
=====================================
Alexander Nickolov
Microsoft MVP [VC], MCSD
email: ***@mvps.org
MVP VC FAQ: http://www.mvps.org/vcfaq
=====================================
Post by Phil Sherrod
Post by Alexander Nickolov
I believe this is a quirk of VB - it creates a reference to its
internal variable and passed it in, even when a VARIANT
is passed. Yes, I'm sure - a VT_BYREF VARIANT should not
be modified, it should be treated as a mere pointer (which
it technically is).
I disagree. I believe the string pointed to by pbstrVal should be freed using
SysFreeString before allocating a new one using SysAllocString and storing its
pointer into pbstrVal.
--
Phil Sherrod
(phil.sherrod 'at' sandh.com)
http://www.dtreg.com (decision tree modeling)
http://www.nlreg.com (nonlinear regression)
Loading...