将传统ASP VBScript参数ByRef传递给COM C++
这很简单。有一个C++函数使用ByRef参数同时返回三个变量。将传统ASP VBScript参数ByRef传递给COM C++
STDMETHODIMP CReportManager::GetReportAccessRights(long lReportCode, VARIANT_BOOL *bShared, VARIANT_BOOL *bRunOnly, VARIANT_BOOL *bCopy)
然而,VBScript的ASP代码似乎并没有调用C++函数时,拿起新值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
有什么我可以解决这个问题吗?任何人都可以解释为什么这样工作?
有两个问题:
首先,你无法检索从VBScript传递回为[ref]
参数值,除非他们是在C++代码VARIANT
类型。
VBScript使用称为COM自动化的后期绑定技术,该技术通过单个通用方法调用将每个方法调用路由到COM对象:IDISPATCH:Invoke(...)
。 (Visual Basic使用相同的技术,当你暗淡了可变As Object
,并在其上调用)
Invoke()
需要一个字符串,它是要调用该方法的名称,参数(加上其他的东西的数组,它是不是这里很重要)。
您的C++对象不必担心它,因为ATL支持称为双接口的东西,它将为您执行所有讨厌的工作。当你的对象接听电话时IDISPATCH:Invoke()
,ATL将:
- 查找请求的方法名称,并识别您的类中的相应方法(如果它存在,否则会抛出一个错误回来的VBScript)。
- 根据方法的签名,根据需要将
VARIANT
(技术上VARIANTARG
,几乎相同)的任何输入参数转换为其适当的数据类型(并且如果它们与您的方法预期不匹配,则会发出错误) - 使用未打包的参数调用您的
GetReportAccessRights()
方法。
当您GetReportAccessRights()
方法返回时,ATL重新打包[retval]
参数到一个新的VARIANT
(techincally VARIANTARG
),并返回到VBScript中。现在
,您可以传回[ref]
值,但它们必须是VARIANT
秒。 ATL不会为您重新包装除[retval]
之外的任何参数值,因此您必须使用VARIANT *
类型的任何[ref]
参数,以便您返回给调用方。当你这样做时,ATL将保持参数不受干扰,VBScript将正确接收它。
要与变种工作,COM头为我们提供了很方便的宏和常量,我将在这里使用(VT_BOOL,V_VT(),V_BOOL(),失败()):
// 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用户期望“真”与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]参数最初是一个字符串,那么这个内存将被泄漏;如果它有一个对象,那个对象将永远不会被销毁。
在这种情况下实现的另一个糟糕的解决方案是使用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
真棒回复!谢谢。 – ssorrrell 2009-06-09 12:55:45