Я использую Pinvoke для взаимодействия между Native (С++) кодом и управляемым (С#) кодом. То, что я хочу достичь, это получить текст из собственного кода в управляемый код. Для этого я стараюсь много чего, например, передавать string/stringbuilder по ref, используя [IN] и [OUT], Marshaling to LPSTR, возвращая строку из функции и т.д., Но ничего не работает в моем случае. Любая помощь с небольшим кодом будет высоко оценена.
Как вернуть текст из Native (С++) кода
Ответ 1
Я бы сделал это с BSTR
, так как это означает, что вам не нужно вызывать в native дважды в строку, один раз, чтобы получить длину, а затем один раз, чтобы получить содержимое.
С помощью BSTR
маршаллер позаботится об освобождении BSTR
от правильного менеджера памяти, чтобы вы могли безопасно передать его из своего кода на С++.
С++
#include <comutil.h>
BSTR GetSomeText()
{
return ::SysAllocString(L"Greetings from the native world!");
}
С#
[DllImport(@"test.dll", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.BStr)]
private static extern string GetSomeText();
Существует один незначительный недостаток BSTR
, а именно, что он несет полезную нагрузку UTF-16, но ваши исходные данные могут быть char*
.
Чтобы преодолеть это, вы можете завершить преобразование с char*
на BSTR
следующим образом:
BSTR ANSItoBSTR(const char* input)
{
BSTR result = NULL;
int lenA = lstrlenA(input);
int lenW = ::MultiByteToWideChar(CP_ACP, 0, input, lenA, NULL, 0);
if (lenW > 0)
{
result = ::SysAllocStringLen(0, lenW);
::MultiByteToWideChar(CP_ACP, 0, input, lenA, result, lenW);
}
return result;
}
Это самый трудный из них, и теперь легко добавить другие обертки для преобразования в BSTR
из LPWSTR
, std::string
, std::wstring
и т.д.
Ответ 2
Вот пример этого через С#. Я вызываю функцию Native GetWindowText
через С# с помощью pInvoking. GetWindowText
возвращает заголовок окна, чей handle
передан ему.
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern int GetWindowTextLength(IntPtr hWnd);
public static string GetText(IntPtr hWnd)
{
// Allocate correct string length first
int length = GetWindowTextLength(hWnd);
StringBuilder sb = new StringBuilder(length + 1);
GetWindowText(hWnd, sb, sb.Capacity);
return sb.ToString();
}
private void button1_Click(object sender, EventArgs e)
{
string str = GetText(this.Handle);
}
Ответ 3
Здесь есть тема, в которой обсуждалось строковое маршалинг.
Необходимо отметить параметр атрибутом
[MarshalAs(UnmanagedType.LPSTR)]
Ответ 4
Если вы возвращаете char *
и не хотите изменять код C/С++ для выделения памяти для возвращаемого значения (или вы не можете изменить этот код), вы можете изменить свой С# extern function-prototype, чтобы вернуть IntPtr
и выполнить маршалинг самостоятельно.
Например, здесь фрагмент взаимодействия, который я написал для Pocketsphinx:
[DllImport("sphinxbase.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr /* char const * */ jsgf_grammar_name(IntPtr /* jsgf_t * */ jsgf);
А вот get-accessor для класса С# JsgfGrammar
. m_pU
- это IntPtr
, указывающий на исходный объект jsgf_t
.
public string Name
{
get
{
IntPtr pU = jsgf_grammar_name(m_pU);
if (pU == IntPtr.Zero)
strName = null;
else
strName = Marshal.PtrToStringAnsi(pU);
return strName;
}
}
Изменение этого примера для других строковых форматов (например, Unicode) должно быть тривиальным.