Как маршалировать wstring * в С#?

У меня есть функция на С++, _GetFileList, которую я пытаюсь вернуть для возврата списка/массива строк в код С#. Я не женат на wstring * или любом другом типе, именно так это сделал пример, который я нашел, но я пытаюсь заставить его возвращать список/массив, а не только одну строку. Как я могу маршалировать это, чтобы иметь несколько строк на стороне С#?

Код С++:

extern "C" __declspec(dllexport) wstring * __cdecl _GetFileList(int &size){
    std::list<wstring> myList;
    //Code that populates the list correctly
    size = myList.size();
    wstring * arr = new wstring[size];
    for( int i=0;i<size;i++){
        arr[i] = myList.front();
        myList.pop_front();
    }
    return arr;
}

Я вызываю это из С# следующим образом:

[DllImport(@"LinuxDirectory.Interface.dll")]
private static extern IntPtr _GetFileList(ref int size);

public bool GetFileList() {
    int size = 0;
    IntPtr ptr = _GetFileList( ref size );
    //WHAT DO I DO HERE TO GET THE LIST OF STRINGS INTO SOMETHING I CAN READ?
}

Я пытался использовать Marshal.PtrToStringUni, Marshal.PtrToStringAnsi, а также структуру, но не смог правильно понять синтаксис.


person WWZee    schedule 09.05.2017    source источник
comment
@ Дэн Нет, я посмотрю на это   -  person WWZee    schedule 09.05.2017
comment
stackoverflow.com/ вопросы/7051097/   -  person pm100    schedule 09.05.2017
comment
@ pm100: Хороший связанный вопрос, но это не дубликат, поскольку на самом деле он имеет (к сожалению) std::wstring в сигнатуре функции, а не просто используется внутри.   -  person Ben Voigt    schedule 09.05.2017


Ответы (1)


Вы не знаете. P/Invoke может работать только с сигнатурами функций, совместимыми с C, что означает, что указатели должны быть на обычные данные, а не на полные классы C++.

Если вы можете переписать код C++, измените его так, чтобы он вызывал SysAllocString и возвращал BSTR, который представляет собой строковый тип, предоставляемый ОС, предназначенный для обмена данными между компонентами, написанными на разных языках. P/invoke уже имеет всю необходимую магию для получения BSTR, просто используйте тип C# string в объявлении p/invoke.

Если вы не можете изменить подпись C++, вам придется ее обернуть. Либо используйте C++/CLI, который может работать как с классами C++, так и с .NET, или используйте стандартный C++ и создайте и верните BSTR, копируя данные из std::wstring.


Для нескольких строк вы можете использовать предоставленный ОС тип массива (SAFEARRAY), который может содержать внутри BSTR и опять же не зависит от языка. Или может быть проще просто объединить и разделить, используя соответствующий символ-разделитель (не появляющийся естественным образом в данных).

Здесь есть действительно полезная информация: https://msdn.microsoft.com/en-us/magazine/mt795188.aspx

Запустив .NET Framework (реализация Microsoft в Windows), вы можете использовать вспомогательные классы:

#include <windows.h>
#include <atlbase.h>
#include <atlsafe.h>
#include <string>

extern "C" __declspec(dllexport) LPSAFEARRAY GetFileList()
{
    std::vector<std::wstring> myList;
    //Code that populates the list correctly
    size = myList.size();

    CComSafeArray<BSTR> sa(size);
    int i=0;
    for( auto& item : myList ) {
        CComBSTR bstr(item.size(), &item[0]);
        sa.SetAt(i++, bstr.Detach()); // transfer ownership to array
    }
    return sa.Detach(); // transfer ownership to caller
}

На стороне C# p/invoke обрабатывает все это:

[DllImport("NativeDll.dll"),
 return:MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_BSTR)]
public static extern string[] GetFileList();

Ваш вопрос предполагает, что вы можете быть на Linux. К сожалению, документация, которую я нашел, говорит, что p/invoke Mono вообще не знает, что делать с массивами переменного размера. Таким образом, вы застряли, делая все вручную, включая освобождение (код в вашем вопросе пропускает new wstring[size] - С# абсолютно не знает, как вызвать оператор С++ delete[]). Посмотрите на Mono.Unix.UnixMarshal.PtrToStringArray.

person Ben Voigt    schedule 09.05.2017
comment
У меня тоже была идея с одной строкой, которую мне прямо сказали, что я не должен/не могу делать - person WWZee; 09.05.2017
comment
Я не понимаю, как использовать BSTR, я никогда не видел этого раньше. Не могли бы вы предоставить и пример, пожалуйста? - person WWZee; 09.05.2017
comment
Я работаю с Уэйном и могу сказать, что он уже использует C++/CLI. - person DCShannon; 10.05.2017
comment
@DCShannon: Что ж, это намного упрощает задачу. В классе C++/CLI вы можете вернуть cli::array<System::String^>^, который является просто именем C++ для string[] C# (SAFEARRAY будет автоматически преобразован, это уже тип C#), а System::String имеет конструктор, который принимает размер (wstr.size()) и указатель (&wstr[0]). - person Ben Voigt; 10.05.2017
comment
@BenVoigt Большое спасибо за помощь. Может быть опечатка, но я не смог заставить его работать так, как у вас. Мне нужно было поместить части DllImport и return в два отдельных блока [ ]. - person WWZee; 10.05.2017