将经典ASP VBScript参数ByRef传递给COM c ++

时间:2009-06-08 20:17:27

标签: c++ com vbscript parameters byref

这很简单。有一个c ++函数,它使用ByRef参数同时返回三个变量。

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

但是,在调用c ++函数时,VBScript ASP代码似乎没有获取bShares,bRunOnly和bCopy的新值。

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

我能做些什么来解决这个问题吗?任何人都可以解释为什么这种方式有效吗?

2 个答案:

答案 0 :(得分:4)

有两个问题:

首先,您无法从VBScript检索作为[ref]参数传回的值,除非它们在C ++代码中的类型为VARIANT

VBScript使用称为COM Automation的后期绑定技术,它通过单个泛型方法调用将每个方法调用路由到COM对象:IDISPATCH:Invoke(...)。 (当您对变量As Object进行调暗并对其进行调用时,Visual Basic使用相同的技术)

Invoke()接受一个字符串,它是您正在调用的方法的名称,以及一个参数数组(以及此处不重要的其他内容)。

您的C ++对象不必担心它,因为ATL支持称为双接口的东西,它将为您完成所有令人讨厌的工作。当您的对象收到对IDISPATCH:Invoke()的调用时,ATL将:

  • 查找请求的方法名称并在类中标识相应的方法(如果存在,否则会在VBScript中返回错误)。
  • 根据需要,将VARIANT(技术上VARIANTARG(几乎相同)的任何输入参数根据方法的签名转换为适当的数据类型(如果他们没有,则会抛出错误) t匹配你的方法所期望的)
  • 使用未打包的参数调用您的GetReportAccessRights()方法。

当您的GetReportAccessRights()方法返回时,ATL会将[retval]参数重新打包为新的VARIANT(techincally VARIANTARG)并将其返回给VBScript。

现在,您可以传回[ref]个值,但必须VARIANT。 ATL不会为您重新打包[retval]以外的任何参数值,因此您必须对要返回给调用者的任何VARIANT *参数使用[ref]类型。当你这样做时,ATL将保持参数不受干扰,VBScript将正确接收它。

为了使用变体,COM头为我们提供了方便的宏和常量,我将在这里使用(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); 

初始化它们;这将导致他们以前的值泄漏。

阅读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
}

或者甚至更好,你应该总是尝试转换自己,因为VBScript用户期望“True”和1的行为与VARIANT_TRUE相同。幸运的是,COM有一个很棒的实用程序API:

// 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);

要写入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);

现在,第二个问题出在您的示例VBScript代码中:

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

因为你作为参数传递CBool(something)等,你传回的是临时变量(CBool​​(...)的返回值),而不是实际的变量bAllShared等。即使是正确的C ++实现,返回的值将作为中间值被丢弃。

你需要调用这样的方法:

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

没错。你不需要“转换”这些值。无论你做什么,VBScript总是会传递VARIANT。不用担心,正如我上面所说,即使对于类型为bool等的输入参数,ATL也会为你调用CBool()

ATL调用CBool​​()?这不是VBScript函数吗?是的,但是CBool​​()是VariantChangeType()的简单包装器,这就是ATL将要求的你)

修改 我忘了提及别的东西:VBScript不支持[out]参数;仅[ref]个参数。不要在C ++中将参数声明为[out]。如果您的方法声明了[out]个参数,那么VBScript的行为就像它们是[ref]个参数一样。这将导致参数的传入值泄露。如果其中一个[out]参数最初是一个字符串,那么该内存将被泄露;如果它有一个对象,该对象永远不会被销毁。

答案 1 :(得分:0)

在这种情况下实现的另一个糟糕的解决方案是使用VB6来包装c ++函数调用并提供3个引用的变量作为VB6 COM对象的函数。

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