Возврат содержимого 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
объект не уничтожается при возврате функции.