Передача классических ASP VBScript параметров ByRef в COM c ++ - PullRequest
1 голос
/ 09 июня 2009

Все довольно просто. Есть функция c ++, которая использует параметры ByRef для одновременного возврата трех переменных.

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

Однако код VBScript ASP, похоже, не принимает новые значения для bShares, bRunOnly и bCopy при вызове функции c ++.

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 ]

4 голосов
/ 09 июня 2009

Есть две проблемы:

Во-первых, вы не можете получить значения, переданные обратно как [ref] параметры из VBScript, если они не имеют тип VARIANT в коде C ++.

VBScript использует технологию позднего связывания, называемую COM Automation, которая направляет каждый вызов метода к COM-объектам через один общий вызов метода: IDISPATCH:Invoke(...). (Visual Basic использует ту же технологию, когда вы уменьшаете значение переменной As Object и выполняете вызовы для нее)

Invoke() принимает строку, которая является именем метода, который вы вызываете, и массив параметров (плюс другие вещи, которые здесь не важны).

Ваш объект C ++ не должен беспокоиться об этом, потому что ATL поддерживает нечто, называемое Dual Interface, что сделает всю неприятную работу за вас. Когда ваш объект получает вызов IDISPATCH:Invoke(), ATL будет:

  • Найдите запрошенное имя метода и определите соответствующий метод в вашем классе (если он существует, в противном случае он выдаст ошибку обратно в VBScript).
  • При необходимости преобразуйте любые входные параметры из VARIANT (технически VARIANTARG, который почти идентичен) в соответствующий тип данных в соответствии с сигнатурой метода (и выдаст ошибку, если они не соответствуют вашим метод ожидает)
  • Вызовите метод GetReportAccessRights() с параметрами без упаковки.

Когда ваш метод GetReportAccessRights() возвращается, ATL перепаковывает параметр [retval] в новый VARIANT (технически 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 будет вызывать для вас)

Edit: Я забыл упомянуть кое-что еще: VBScript НЕ поддерживает [out] параметров; только [ref] параметры. НЕ объявляйте ваши параметры как [out] в C ++. Если ваш метод объявляет [out] параметры, VBScript будет действовать так, как если бы они были [ref] параметрами. Это приведет к утечке входящих значений параметров. Если один из аргументов [out] изначально имел строку, эта память будет утечка; если у него есть объект, этот объект никогда не будет уничтожен.

0 голосов
/ 09 июня 2009

Другое непристойное решение, которое было реализовано в этом случае, заключалось в использовании VB6 для переноса вызова функции c ++ и предоставления трех переменных, на которые есть ссылки, в качестве функций COM-объекта VB6.

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
...