Использование массива строк VB6 в С#

У меня есть (устаревший) код VB6, который я хочу использовать из кода С#.

Это несколько похоже на этот вопрос, но это относится к передаче массива из VB6, использующего С# dll. Моя проблема противоположна.

В VB есть интерфейс в одной DLL, а реализация в другой.

Интерфейс:

[
  odl,
  uuid(339D3BCB-A11F-4fba-B492-FEBDBC540D6F),
  version(1.0),
  dual,
  nonextensible,
  oleautomation,
      helpstring("Extended Post Interface.")        
]
interface IMyInterface : IDispatch {

    [id(...),helpstring("String array of errors.")]
    HRESULT GetErrors([out, retval] SAFEARRAY(BSTR)* );
};

Реализация (фрагмент) в cMyImplementationClass:

Private Function IMyInterface_GetErrors() As String()

    If mbCacheErrors Then
        IMyInterface_GetErrors = msErrors
    End If

End Function

Я обернул эти 2 dll с помощью tlbimp.exe и попытался вызвать функцию из С#.

public void UseFoo()
{
    cMyImplementationClass foo;
    ...
    var result = foo.GetErrors();
    ...
}

Вызов foo.GetErrors() вызывает SafeArrayRankMismatchException. Я думаю, что это указывает на проблему маршалинга, описанную в разделе "Безопасные массивы" здесь.

Кажется, что рекомендуется использовать параметр /sysarray tlbimp.exe или вручную отредактировать произведенный IL, который я пробовал.

Оригинальный IL выглядит следующим образом:

.method public hidebysig newslot virtual 
    instance string[] 
    marshal( safearray bstr) 
    GetErrors() runtime managed internalcall
{
  .override [My.Interfaces]My.Interface.IMyInterface::GetErrors
} // end of method cImplementationClass::GetErrors

В то время как обновленная версия:

.method public hidebysig newslot virtual 
    instance class [mscorlib]System.Array 
    marshal( safearray) 
    GetErrors() runtime managed internalcall
{
  .override [My.Interfaces]My.Interface.IMyInterface::GetErrors
} // end of method cImplementationClass::GetErrors

Я сделал идентичные изменения сигнатуры функций как в интерфейсе, так и в реализации. Этот процесс описан здесь. Однако он не указывает возвращаемое значение в функции (он использует ссылку "in" ), а также не использует интерфейс. Когда я запускаю свой код и звоню с С#, я получаю сообщение об ошибке

Метод не найден: 'System.Array MyDll.cImplementationClass.GetErrors()'.

Кажется, что что-то не так в IL, что я редактировал, хотя я не знаю, куда идти отсюда.

Как я могу использовать эту функцию из С# без изменения кода VB6?

- Edit-- Переопределение "msErrors", которое инициализирует закрытый массив, который возвращается.

ReDim Preserve msErrors(1 To mlErrorCount)

Если я правильно понял, то "1" означает, что массив индексируется из 1 вместо 0, что является причиной исключения, которое я вижу, чтобы получить бросок.

Ответ 1

Я выполнил все ваши шаги, за исключением использования TlbImp.exe. Вместо этого я напрямую добавил библиотеки DLL в ссылку на проект С#. Сделав это, я получаю IL, который является крестом между обоими образцами, которые вы даете:

.method public hidebysig newslot abstract virtual 
        instance class [mscorlib]System.Array 
        marshal( safearray bstr) 
        GetErrors() runtime managed internalcall
{
  .custom instance void [mscorlib]System.Runtime.InteropServices.DispIdAttribute::.ctor(int32) = ( 01 00 00 00 03 60 00 00 )                         // .....`..
} // end of method _IMyInterface::GetErrors

Я сделал тот же код, что и вы, и по существу вы назначаете переменную типа Array. Хотя среда CLR поддерживает массивы с нижними границами, отличными от 0, AFAIK, ни один язык, даже VB.NET, не поддерживает его на языке.

Мой тестовый код:

cMyImplementationClass myImpClass = new cMyImplementationClass();
IMyInterface myInterface = myImpClass as IMyInterface;

myImpClass.CacheErrors = true;

// Retrieve the error strings into the Array variable.
Array test = myInterface.GetErrors();

// You can access elements using the GetValue() method, which honours the array original bounds.
MessageBox.Show(test.GetValue(1) as string);

// Alternatively, if you want to treat this like a standard 1D C# array, you will first have to copy this into a string[].
string[] testCopy = new string[test.GetLength(0)];
test.CopyTo(testCopy, 0);
MessageBox.Show(testCopy[0]);