Question

It's pretty simple. There's a c++ function that uses ByRef parameters to return three variables at the same time.

STDMETHODIMP CReportManager::GetReportAccessRights(long lReportCode, VARIANT_BOOL *bShared, VARIANT_BOOL *bRunOnly, VARIANT_BOOL *bCopy)

However, the VBScript ASP code doesn't seem to pick up the new values for bShares, bRunOnly, and bCopy when calling the c++ function.

dim bAllShared, bAllCopy, bAllRunOnly
bAllShared = true
bAllCopy = true
bAllRunOnly = true
m_oReportManager.GetReportAccessRights CLng(m_lRptCod), CBool(bAllShared), CBool(bAllRunOnly), CBool(bAllCopy)
'bAllShared always equals true

Is there anything I can do to fix this? Can anyone explain why this works this way?

Was it helpful?

Solution

There are two problems:

First, you cannot retrieve values passed back as [ref] parameters from VBScript, unless they are of type VARIANT in the C++ code.

VBScript uses a late-binding technology called COM Automation, which routes every method call to COM objects through a single generic method call: IDISPATCH:Invoke(...). (Visual Basic uses the same technology when you Dim a variable As Object and make calls on it)

Invoke() takes a string which is the name of the method you are calling, and an array of parameters (plus other stuff that is not important here).

Your C++ object doesn't have to worry about it because ATL supports something called Dual Interface, which will do all the nasty work for you. When your object receives a call to IDISPATCH:Invoke(), ATL will:

  • Look up the requested method name and Identify the corresponding method in your class (if it exists, otherwise it will throw an error back at VBScript).
  • Translate any input parameters, as needed, from VARIANT (technically VARIANTARG, which is almost identical) to their appropriate data type according to the method's signature (and will throw an error if they don't match what your method expects)
  • Call your GetReportAccessRights() method, with the unpackaged parameters.

When your GetReportAccessRights() method returns, ATL repackages the [retval] parameter into a new VARIANT (techincally VARIANTARG) and returns that to VBScript.

Now, you can pass back [ref] values as well, but they have to be VARIANTs. ATL will not repackage any parameter value other than the [retval] for you, so you have to use a type of VARIANT * for any [ref] argument that you want to return back to the caller. When you do, ATL will leave the parameter undisturbed and VBScript will receive it back correctly.

To work with variants, the COM headers provides us with convenience macros and constants, which I'll use here (VT_BOOL, V_VT(), V_BOOL(), FAILED()):

// I usually initialize to Empty at the top of the method,
// before anything can go wrong.
VariantInit(bAllShared);

// My bad -- ignore the above. It applies to [out] parameters only.
// Because bAllShared is passed as a [ref] variable,
// calling VariantInit() on them would leak any preexisting value.
// Instead, read the incoming value from the variable (optional),
// then "clear" them before storing new values (mandatory):

// This API figures out what's in the variable and releases it if needed
// * Do nothing on ints, bools, etc.
// * Call pObj->Release() if an Object
// * Call SysFreeString() if a BSTR
// etc
VariantClear(bAllShared); 

Initialize them; that would cause their previous values to leak.

To read a VARIANT:

// Always check that the value is of the proper type
if (V_VT(bAllShared) == VT_BOOL ) {
    // good
    bool myArg = (V_BOOL(bAllShared) == VARIANT_TRUE);
} else {
    // error, bad input
}

Or even better, you should always try to convert yourself, because VBScript users expect "True" and 1 to behave the same as a VARIANT_TRUE. Fortunately, COM has an awesome utility API for that:

// This is exactly the same thing that VBScript does internally
// when you call CBool(...)
VARIANT v;
VariantInit(&v);
if( FAILED(VariantChangeType(&v, &bAllShared, 0, VT_BOOL) )
{
    // error, can't convert
} 
bool myArg = (V_BOOL(v) == VARIANT_TRUE);

To write to a VARIANT:

// Internal working value
bool isShared;
...

// set the Variant's type to VARIANT_BOOL
V_VT(bAllShared)   = VT_BOOL;

// set the value
V_BOOL(bAllShared) = (isShared ? VARIANT_TRUE : VARIANT_FALSE);

Now, the second problem is in your sample VBScript code:

m_oReportManager.GetReportAccessRights _
    CLng(m_lRptCod), CBool(bAllShared), CBool(bAllRunOnly), CBool(bAllCopy)

Because you are passing as arguments CBool(something), etc, you are passing back temporary variables (the return value of CBool(...)), not the actual variable bAllShared, etc. Even with the correct C++ implementation, the returned values will be discarded as intermediate values.

You need to call the method like this:

m_oReportManager.GetReportAccessRights _
    CLng(m_lRptCod), bAllShared, bAllRunOnly, bAllCopy

That's right. you don't need to "convert" the values. VBScript will always pass a VARIANT no matter what you do. Don't worry, as I said above, even for input parameters of type bool, etc, ATL will make the call to CBool() for you.

(ATL calls CBool()? Isn't that a VBScript function? Yes, but CBool() is a simple wrapper around VariantChangeType(), and that is what ATL will call for you)

Edit: I forgot to mention something else: VBScript does NOT support [out] parameters; only [ref] parameters. Do NOT declare your parameters as [out] in C++. If your method declares [out] parameters, VBScript will act like they were [ref] parameters. That will cause the incoming values of the parameters to be leaked. If one of the [out] arguments had originally a string, that memory will be leaked; if it had an object, that object will never be destroyed.

OTHER TIPS

Another crummy solution, which was implemented in this case was to use VB6 to wrap the c++ function call and provide the 3 referenced variables as functions of the VB6 COM object.

Option Explicit
Private bSharedaccess As Boolean
Private bRunOnlyaccess As Boolean
Private bCopyaccess As Boolean

Public Sub Initialize(ByVal oSession As Starbridge.Session, ByVal lReportID As Long)

    bSharedaccess = True
    bRunOnlyaccess = False
    bCopyaccess = True
    Call oSession.ReportManager.GetReportAccessRights(lReportID, bSharedaccess, bRunOnlyaccess, bCopyaccess)

End Sub

Public Function GetSharedAccess()
    GetSharedAccess = bSharedaccess
End Function

Public Function GetRunOnlyAccess()
    GetRunOnlyAccess = bRunOnlyaccess
End Function

Public Function GetCopyAccess()
    GetCopyAccess = bCopyaccess
End Function
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top