Итак, продолжим:
Если вы внимательно посмотрите на предыдущую реализацию метода Invoke, вы заметите две функции, а именно CheckArgCount и IsVdalidTypeBSTR
Первая функция проверяет количество параметров переданных в процедуру и функцию и определена в базовом классе как
также данная функция заполняет информацию об ошибке в структуру ExcepInfo, которая в свою очередь возвращает Invoke.
Если вы внимательно посмотрите на предыдущую реализацию метода Invoke, вы заметите две функции, а именно CheckArgCount и IsVdalidTypeBSTR
Первая функция проверяет количество параметров переданных в процедуру и функцию и определена в базовом классе как
// Проверяем кол-во параметров function TBase.CheckArgCount(Count: Integer; Accepted: Integer; ExcepInfo : Pointer) : Boolean; var I: Integer; begin Result := FALSE; Result := Accepted = Count; if Assigned(ExcepInfo) and (not Result) then begin ZeroMemory(ExcepInfo, SizeOf(TExcepInfo)); TExcepInfo(ExcepInfo^).wCode := 1001; TExcepInfo(ExcepInfo^).bstrSource := SysAllocString(PWideChar('CheckArgCount')); TExcepInfo(ExcepInfo^).bstrDescription := SysAllocString(PWideChar('Неверное количество параметров!')); end; end;
также данная функция заполняет информацию об ошибке в структуру ExcepInfo, которая в свою очередь возвращает Invoke.
Проверка типов параметров
И вторая функция IsVdalidTypeBSTR является надстройкой над функцией проверки значения Variant на определенный тип (подробнее об Variant-типе посмотреть можно здесь
Как видите IsValidTypeStr представляет собой вызов IsValidType с параметром VarArrayOf([VT_BSTR, VT_VARIANT or VT_BYREF]))
Понятно для чего нужна проверка, если в процедуру или функцию вы ожидаете число а вам идет тип строка то конечно логика работы программы будет нарушена вплоть до возникновения ошибок. Что бы это-го не случилось мы и проверяем тип параметра заранее.
Однако почему мы проверяем не на совпадение с VT_BSTR, что казалось бы логичным, раз мы ждем строку, почему мы еще проверяем на тип VT_VARIANT or VT_BYREF.
Ответ на этот вопрос довольно прост, вспомним наш VBS-код:
Sub Main()
MyCLASS.MessageInformation("Проверка работы")
End Sub
Если бы мы проверяли параметр только на VT_BSTR то код бы отработал бы как и положено, однако если бы наш скрипт выглядел так:
Sub Main()
Dim Str
Str = "Проверка работы"
MyCLASS.MessageInformation(Str)
End Sub
Т.е.мы передовали бы переменную в наш класс, то проверка бы на VT_BSTR не прошла бы и мы бы получили ошибку 'Неверное количество параметров!'. И это было бы правильно т.к. переменные в Script Control передаются с флагом типа - VT_VARIANT or VT_BYREF
Поэтому это нужно учитывать – это первый подводный камень. Второй подводный камень смотрим ниже, если ж опять запустить наш пример с вторым VBS-кодом в версиях Delphi ниже XE, то в место текста "Проверка работы" мы увидим абракадабру. Это связано с тем, что MS Script Control работает с Unicode-строкой. А реализация поддержки PWideChar в версиях разная. И если в Delphi XE/XE2 код выдаст читаемое сообщение, то в D2010 будут крякозябры.
Те, кто использует XE/XE2 следующий раздел может спокойно пропустить, те кто сидит на версиях ниже могут использовать следующий обходной прием, а именно преобразование типа переменной Variant к конкретному типу
// Проверяем на соответствие типов function TBase.IsValidType(Argument: TVariantArg; TypesId : Variant): Boolean; var I : Integer; isOk : Boolean; Identificator : Integer; begin isOk := False; if VarIsArray(TypesId) then begin try VarArrayLock(TypesId); for I := VarArrayLowBound(TypesId, 1) to VarArrayHighBound(TypesId, 1) do begin Identificator := StrToInt( VarToStr(VarArrayGet(TypesId, [I])) ); isOk := (Argument.vt = Identificator); if isOk then Break; end; finally VarArrayUnLock(TypesId); end; end; if VarIsNumeric(TypesId) then begin Identificator := StrToInt(VarToStr(TypesId)); isOk := (Argument.vt = Identificator); end; Result := isOk; end; function TBase.IsVdalidTypeBSTR(Argument: TVariantArg): Boolean; begin Result := IsValidType(Argument, VarArrayOf([VT_BSTR, VT_VARIANT or VT_BYREF])); end;
Как видите IsValidTypeStr представляет собой вызов IsValidType с параметром VarArrayOf([VT_BSTR, VT_VARIANT or VT_BYREF]))
Понятно для чего нужна проверка, если в процедуру или функцию вы ожидаете число а вам идет тип строка то конечно логика работы программы будет нарушена вплоть до возникновения ошибок. Что бы это-го не случилось мы и проверяем тип параметра заранее.
Однако почему мы проверяем не на совпадение с VT_BSTR, что казалось бы логичным, раз мы ждем строку, почему мы еще проверяем на тип VT_VARIANT or VT_BYREF.
Ответ на этот вопрос довольно прост, вспомним наш VBS-код:
Sub Main()
MyCLASS.MessageInformation("Проверка работы")
End Sub
Если бы мы проверяли параметр только на VT_BSTR то код бы отработал бы как и положено, однако если бы наш скрипт выглядел так:
Sub Main()
Dim Str
Str = "Проверка работы"
MyCLASS.MessageInformation(Str)
End Sub
Т.е.мы передовали бы переменную в наш класс, то проверка бы на VT_BSTR не прошла бы и мы бы получили ошибку 'Неверное количество параметров!'. И это было бы правильно т.к. переменные в Script Control передаются с флагом типа - VT_VARIANT or VT_BYREF
Поэтому это нужно учитывать – это первый подводный камень. Второй подводный камень смотрим ниже, если ж опять запустить наш пример с вторым VBS-кодом в версиях Delphi ниже XE, то в место текста "Проверка работы" мы увидим абракадабру. Это связано с тем, что MS Script Control работает с Unicode-строкой. А реализация поддержки PWideChar в версиях разная. И если в Delphi XE/XE2 код выдаст читаемое сообщение, то в D2010 будут крякозябры.
Те, кто использует XE/XE2 следующий раздел может спокойно пропустить, те кто сидит на версиях ниже могут использовать следующий обходной прием, а именно преобразование типа переменной Variant к конкретному типу
Преобразование типов параметров
Для этого воспользуемся функцией VariantChangeType которая устанавливает определенный тип, итак наша реализация выглядеть будет так
Продолжение следует.
Далее: Создание функции ; Работа с var-параметрами в функции или процедуре, возврат параметров из функции ; Обработка ошибок IScriptError
case DispID of DISPID_MessageInformation : begin // Проверка на кол-во параметров if not CheckArgCount(P.cArgs, 1, ExcepInfo) then begin Result := DISP_E_EXCEPTION; Exit; end; // Аргументы в обратном порядке if IsVdalidTypeBSTR(P.rgvarg^[0]) then begin {Меняем тип данных на строку, если вдруг пришла переменная} VariantChangeTypeToBSTR(P.rgvarg^[0]); VariantChangeTypeToBSTR(P.rgvarg^[1]); ShowMessage('Информация ' + #10#13 + TDispParams(P).rgvarg^[0].bstrval); end else begin SetErrorCheckParamsType(ExcepInfo, fn_MESSAGEINFORMATION); Result := DISP_E_EXCEPTION; end; end; * * procedure TBase.VariantChangeTypeToBSTR(var Argument: TVariantArg); begin VariantChangeType(OleVariant(TDispParams(Argument)), OleVariant(TDispParams(Argument)), 0,VT_BSTR); end;Такой хитрый обход в версии D2010 устраняет проблему с крякозябрами. Может это не совсем и хорошее решение – но рабочее. Фактически мы приводим тип переменной Variant к строке.
Продолжение следует.
Далее: Создание функции ; Работа с var-параметрами в функции или процедуре, возврат параметров из функции ; Обработка ошибок IScriptError
Здравствуйте. А чем не устроил TObjectDispatch из ObjComAuto? Я замечал только ошибки при работе с Int64/UInt64, но они легко решаются заменой типов параметров на Variant. Ну и не забыть включить для класса RTTI - {$TYPEINFO ON}{$METHODIFNO ON}
ОтветитьУдалитьЗдравствуйте. Да в принципе всем устраивает, единственный минус только вот чем, т.к. TObjectDispatch связывается с существующим классом и с помощью RTTI обращается к методам класса, то параметры как бы привязываются жестко. Т.е. если метод класса имеет два параметра то при использовании он и будет принимать два. Или я не прав? Возможно я ошибаюсь
ОтветитьУдалитьПри ручной обработке можно варьировать кол-во параметров, например сделав метод который может обрабатывать N-кол-во параметров (вернее до ограничения по кол-ву параметров самого Script Control-а)
т.е
можно например сделать метод например Print
и вызывать его
Print("1");
Print("1","2");
Print("1","2",456,"строка");
т.е. передавать в метод произвольно кол-во разных параметров и в зависимости от типа их обрабатывать - этакая гибкость
Хотя да как класс упращающий работу TObjectDispatch неоценим.
{$TYPEINFO ON}{$METHODIFNO ON}
это как раз касательно TObjectDispatch т.к. он должен с помощью RTTI получать
информацию о методах и типах класса.
Вообщем дело вкуса
Забыл про произвольное число параметров, это действительно нужная вещь. Ну пусть, может мой комментарий поможет другим - во многих случаях TObjectDispatch может оказаться проще в использовании
ОтветитьУдалитьКомментарий к месту, я тут даже подумал для примера сделать заметку с TObjectDispatch, так сказать в продолжении темы
ОтветитьУдалить