Ошибка несоответствия типов при доступе к массиву, возвращаемому ссылкой из COM + (VBScript)

У меня есть сборка .NET 3.5, которая запускается как компонент сервера COM +, и я хочу вызвать метод этого класса из VBScript (классическая страница ASP).

Это схема метода;

public bool FillArray(ref string[] arrayToFill)
{
    ...
}

Мой VBScript выглядит следующим образом;

Dim myComponent, result, myArray

Set myComponent = Server.CreateObject("MyComponentProgID")
result = myComponent.FillArray(myArray)

Response.Write("IsArray = " & IsArray(myArray) & "<br/>")
Response.Write("UBound = " & UBound(myArray) & "<br/>")
Response.Write("TypeName = " & TypeName(myArray) & "<br/>")
Response.Write("Element 1 = " & myArray(1))

Это приводит к следующей ошибке (вызванной строкой, в которой я вызываю FillArray);

Тип ошибки: среда выполнения Microsoft VBScript (0x800A0005) Недействительный вызов процедуры или аргумент: «FillArray»

При запуске OLEView IDL выглядит следующим образом;

HRESULT FillArray(
                [in, out] SAFEARRAY(BSTR)* arrayToFill, 
                [out, retval] VARIANT_BOOL* pRetVal);

Я попытался изменить подпись моего метода на следующее:

public bool FillArray(ref object[] arrayToFill)

Что привело к следующему IDL;

HRESULT FillArray(
                [in, out] SAFEARRAY(VARIANT)* arrayToFill, 
                [out, retval] VARIANT_BOOL* pRetVal);

Но все та же ошибка «Неверный вызов процедуры или аргумент FillArray».

Наконец, я попытался изменить сигнатуру моего метода на это;

public bool FillArray(ref object arrayToFill)

Что дало следующий IDL;

HRESULT FillArray(
                [in, out] VARIANT* arrayToFill, 
                [out, retval] VARIANT_BOOL* pRetVal);

Теперь это дает новую ошибку;

Среда выполнения Microsoft VBScript (0x800A000D) Несоответствие типов

Эта ошибка теперь запускается только в последней строке, то есть когда я пытаюсь получить доступ к элементу массива. Если я закомментирую последнюю строку, то получу следующий результат:

IsArray = True

UBound = 39

TypeName = String ()

Итак, видимо, вариант распознается как массив и правильного типа. Кроме того, UBound возвращает правильное количество элементов, но я не могу получить доступ ни к одному из элементов по неизвестной причине.

Кто-нибудь знает, что может быть причиной этого? Я сам провел небольшое исследование и наткнулся на следующую ссылку;

http://connect.microsoft.com/VisualStudio/feedback/details/331632/marshaler-bug-with-vbscript-arrays

Я не на 100% уверен, что это та же проблема, поскольку я не объявляю свои массивы таким же образом в моем коде VBScript. Я искренне надеюсь, что это не та же проблема, поскольку у меня нет возможности перейти на .NET 4.0.


person Christopher McAtackney    schedule 18.11.2010    source источник


Ответы (4)


Я сам справился с этим.

Оказывается, VBScript не обрабатывает массивы, не относящиеся к типу Variant. Итак, в моем коде C # я пробовал это;

public bool FillArray(ref object arrayToFill)
{
    string[] tmpArrayToFill = (string[])arrayToFill;
    ...
    arrayToFill = (object[])tmpArrayToFill
}

... относится к дальнейшим вызовам, которые передаются по arrayToFill по ссылке.

К сожалению, это привело к точно такой же ошибке.

На путь решения этой проблемы меня поставило то, что функция VBScript TypeName () ВСЕ ЕЩЕ видела тип как String (). Мне было любопытно, что происходит в коде .NET, поэтому я написал небольшой тест;

string[] stringArray = new string[1];
Console.WriteLine(stringArray.GetType());

object[] objectArray = (object[])stringArray;
Console.WriteLine(objectArray.GetType());

Что произвело следующее:

System.String []

System.String []

Для меня это было новостью - я не понимал, что так будет. Я счел разумным ожидать Object [] для второго типа.

В любом случае, несмотря на это, я написал небольшой метод расширения для создания нового объекта []

public static Array ToObjectArray(this Array input)
{
    if (input != null)
    {
        object[] objArray = new object[input.Length];
        input.CopyTo(objArray, 0);
        return objArray;
    }
    else
    {
        return null;
    } 
 }

Это только грубая первая попытка - будут добавлены более надежная обработка ошибок и поддержка массивов с зазубринами.

Итак, мой код теперь выглядит так:

public bool FillArray(ref object arrayToFill)
{
    string[] tmpArrayToFill = (string[])arrayToFill;
    ...
    arrayToFill = tmpArrayToFill.ToObjectArray();
}

И теперь в VBScript TypeName возвращает Variant (), и я могу получить доступ к массиву, как и ожидалось.

Надеюсь, это поможет любому, кто столкнется с этой проблемой в будущем.

person Christopher McAtackney    schedule 23.11.2010
comment
Спасибо за ваше обновление! Думаю, мне потребовалось время, чтобы понять. Я разместил свой вариант VB.Net ниже. - person Joe Niland; 29.11.2011

Трудно понять, почему интерпретатор скриптов отказывается индексировать массив. Это может иметь какое-то отношение к нулю нижней границы массива, в VBScript нет оператора Option Base. Создание массива, который не начинается с нуля в .NET, технически возможно с помощью Array.CreateInstance (), одна из его перегрузок позволяет создавать массив с ненулевыми нижними границами. Я упомяну класс VariantWrapper, но не думаю, что это актуально. Сделать массив возвращаемым значением - это еще один способ решения проблемы.

person Hans Passant    schedule 18.11.2010
comment
Я принял ваше предложение об использовании Array.CreateInstance () и использовал его для преобразования моего массива с нулевым индексом в массив с одним индексом перед тем, как передать его обратно. К сожалению, я все еще получаю то же самое сообщение об ошибке. Я отлаживал свой компонент COM +, чтобы следить за типом, который он передает обратно, и при нулевом индексе это был String [], но при единичном индексе это был String [*]. Кроме того, это урезанный пример некоторого устаревшего кода, который фактически передает четыре таких массива по ссылке, поэтому, к сожалению, сделать массив возвращаемым значением на самом деле невозможно. - person Christopher McAtackney; 18.11.2010
comment
Что ж, выстрел стоил. Назначьте вознаграждение, если вы не хотите звонить в службу поддержки Microsoft. - person Hans Passant; 18.11.2010
comment
Будет так, как только 48-часовой лимит истечет :) - person Christopher McAtackney; 18.11.2010

Это то, что я сделал, основываясь на ответе К. МакАтакни:

Private _lastErrors As List(Of Object)
''' <summary>
''' Expose error array to COM object consumer
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks>Type is Object() because VBScript requires a variant type.</remarks>
Public Property LastErrors As Object()

    Get
        If _lastErrors Is Nothing Then
            _lastErrors = New List(Of Object)
        End If

        Return _lastErrors.ToArray
    End Get
    Private Set(value As Object())
        If _lastErrors Is Nothing Then
            _lastErrors = New List(Of Object)
        End If

        _lastErrors.AddRange(value)
    End Set
End Property
person Joe Niland    schedule 29.11.2011

У меня была аналогичная проблема с передачей массива двойников из программы VB 6.0 на COM-сервер, написанный на VB.net (2010).

Объявление функции VB.net как:

Public Function GetSpatialResults(ByRef Results() As Double) As Long

даст мне ошибку несоответствия типа, где:

Public Function GetSpatialResults(ByRef Results As Object) As Long
    Results(1) = 1
    Results(2) = 2
    Results(3) = 3
    GetSpatialResults = 0

Работает нормально, однако мне пришлось использовать индексы массива, начинающиеся с 1, а не с 0.

person Max    schedule 09.09.2011