Возврат содержимого std::wstring из C++ в C#

У меня есть неуправляемая C++ DLL, которую я обернул простым интерфейсом C, чтобы я мог вызывать PInvoke из C#. Вот пример метода в оболочке C:

const wchar_t* getMyString()
{
    // Assume that someWideString is a std::wstring that will remain
    // in memory for the life of the incoming calls.
    return someWideString.c_str();
}

Вот моя установка C# DLLImport.

[DllImport( "my.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl )]
private static extern string GetMyString();

Однако строка неправильно распределяется, часто облажая первый символ, а иногда и наоборот, показывая кучу китайских символов. Я зарегистрировал вывод от реализации на стороне C, чтобы подтвердить, что std::wstring сформирован правильно.

Я также попытался изменить DLLImport, чтобы он возвращал IntPtr и конвертировал с помощью упакованного метода, используя Marshal.PtrToStringUni, и он имел тот же результат.

[DllImport( "my.dll", CallingConvention = CallingConvention.Cdecl )]
private static extern IntPtr GetMyString();
public string GetMyStringMarshal()
{
    return Marshal.PtrToStringUni( GetMyString() );
}

Есть идеи?

Обновить с ответом

Итак, как упоминалось ниже, это не проблема моих привязок, а время жизни моего wchar_t*. Мое письменное предположение было неверным, someWideString фактически копировалось во время моих обращений к остальной части приложения. Поэтому он существовал только в стеке и был выпущен до того, как мой код C# смог завершить его маршалинг.

Правильное решение - либо передать указатель на мой метод, как описано в shf301, либо убедиться, что моя ссылка на wchar_t * не будет перемещена / перераспределена / уничтожена, пока мой интерфейс C# не успел ее скопировать.

Возврат std::wstring вниз на мой уровень C в виде "const &std::wstring" означает, что мой вызов c_str() вернет ссылку, которая не будет немедленно освобождена из-за пределов моего метода C.

Затем вызывающий код C# должен использовать Marshal.PtrToStringUni() для копирования данных из ссылки в управляемую строку.

2 ответа

Решение

Вам придется переписать вашу функцию getMyString по причинам, указанным в ответе Ханса Пассанта.

Вам нужно, чтобы код C# передавал буфер в ваш код C++. Таким образом, ваш код (хорошо, CLR Marshaller) контролирует время жизни буфера, и вы не попадете в неопределенное поведение.

Ниже приведена реализация:

C++

void getMyString(wchar_t *str, int len)
{
    wcscpy_s(str, len, someWideString.c_str());
}

C#

[DllImport( "my.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode )]
private static extern void GetMyString(StringBuffer str, int len);
public string GetMyStringMarshal()
{
    StringBuffer buffer = new StringBuffer(255);
    GetMyString(buffer, buffer.Capacity);
    return buffer.ToString();
}

Вам необходимо указать атрибут MarshalAs для возвращаемого значения:

    [DllImport( "my.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
    [return : MarshalAs(UnmanagedType.LPWStr)]
    private static extern string GetMyString();

Убедитесь, что функция действительно cdecl и что wstring объект не уничтожается при возврате функции.

Другие вопросы по тегам