Инициализация C # IntPtr для приема данных из неуправляемой C ++ DLL? - PullRequest
0 голосов
/ 28 марта 2019

У меня есть экспортированная функция в неуправляемом коде C ++, который ожидает указатель на BStr, где он будет записывать некоторые текстовые данные (не более 258 байт)

extern "C" __declspec(dllexport)
int CppFunc(BSTR *data)
{ ... }

Я хочу эти данные в виде строки.

Это работает

[DllImport( ... CallingConvention = CallingConvention.Cdecl)]
public static extern int CppFunc([MarshalAs(UnmanagedType.BStr)] ref string data);

но это создает утечку памяти.

Я предполагаю, что мне следует создать и передать IntPtr, затем маршал вывести Bstr в виде строки и освободить IntPtr:

IntPtr p = Marshal.AllocHGlobal(512);
CppFunction(p);
string data = Marshal.PtrToStringBSTR(p);
Marshal.FreeHGlobal(p) ;

Проблема в том, что с этим кодом я получаю исключение System.AccessViolationException при вызове в Marshal.PtrToStringBSTR (p).

Что я делаю не так?!

1 Ответ

2 голосов
/ 28 марта 2019

Первая строка примечаний для Marshal.PtrToStringBSTR:

Вызывайте этот метод только для строк, которые были выделены с помощью неуправляемых функций SysAllocString и SysAllocStringLen.

Откуда, вероятно, произошел ваш сбой.

Добавьте к этому ваша функция C ++ ожидает BSTR* (фактически указатель на указатель на первый символ данных в строке), но вы передаете ему указатель на данные.

Помните, что BSTR имеет специальную структуру: он начинается с длины в 4 байта, затем с данных, затем с нулевым значением.Указатель указывает на первый символ data .Так что Marshal.PtrToStringBSTR смотрит назад от указателя, чтобы найти длину строки - но это не память, которая была выделена Marshal.AllocHGlobal.


Может случиться так, что ваша функция C ++ делает что-то вроде *data = ....AllocSysString(); - то есть она никогда не читает полученную строку, а вместо этого назначает указатель на строку, которую она выделяет.

В этом случае вы, вероятно, захотите что-то вроде:

[DllImport( ... CallingConvention = CallingConvention.Cdecl)]
public static extern int CppFunc(out IntPtr data);

...

CppFunc(out IntPtr p);
string data = Marshal.PtrToStringBSTR(p);
Marshal.FreeBSTR(p) ;

Здесь мы передаем указатель на указатель.Функция C ++ повторно назначает указатель для указания на первый символ данных в BSTR, и мы используем его для десериализации BSTR, а затем освобождаем его (используя метод, который знает, как освобождать BSTR).


Если это не так, неясно, почему ваша функция C ++ принимает BSTR* (в отличие от BSTR), и что она делает с ней.Я думаю, нам нужно понять, прежде чем можно будет сказать что-то еще.

Если ваша функция C ++ взяла вместо BSTR (помните, что BSTR сама по себе является указателем), то вы должны использоватьStringBuilder (с определенной начальной емкостью) - уровень маршаллинга превращает его в указатель, в который может записываться код C ++, и затем вы можете превратить StringBuilder в строку.

[DllImport( ... CallingConvention = CallingConvention.Cdecl)]
public static extern int CppFunc([MarshalAs(UnmanagedType.BStr)] StringBuilder data);

...

var data = new StringBuilder(512); 
CppFunction(data);
string result = data.ToString();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...