【问题标题】:Passing Classic ASP VBScript Parameters ByRef to COM c++将经典 ASP VBScript 参数 ByRef 传递给 COM c++
【发布时间】:2010-11-01 07:02:00
【问题描述】:

这很简单。有一个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

我能做些什么来解决这个问题吗?谁能解释一下为什么会这样?

【问题讨论】:

    标签: c++ com vbscript parameters byref


    【解决方案1】:

    有两个问题:

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

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

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

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

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

    当您的GetReportAccessRights() 方法返回时,ATL 将[retval] 参数重新打包成一个新的VARIANT(技术上称为VARIANTARG)并将其返回给VBScript。

    现在,您也可以回传[ref] 值,但它们必须VARIANTs。 ATL 不会为您重新打包除 [retval] 之外的任何参数值,因此您必须为要返回给调用者的任何 [ref] 参数使用 VARIANT * 类型。当您这样做时,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] 参数最初是一个字符串,则该内存将被泄漏;如果它有一个对象,则该对象将永远不会被销毁。

    【讨论】:

      【解决方案2】:

      在这种情况下实现的另一个糟糕的解决方案是使用 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
      

      【讨论】:

        猜你喜欢
        • 2014-09-12
        • 2013-01-25
        • 1970-01-01
        • 2011-09-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多