#c# #c #marshalling #c-strings #char-pointer
#c# #c #сортировка #c-строки #символ-указатель
Вопрос:
Я пытаюсь открыть символ *, который экспортируется из библиотеки Dll в моем проекте C #.
Поскольку кодов слишком много, я напишу короткий код, похожий на код, над которым я работаю.
C :
__declspec(dllexport) typedef struct Kid {
char* _name;
int _age;
int _grade;
} Kid;
Kid get_default_kid()
{
char name[] = { "Quinn" };
return Kid{
name, 2,7
};
}
extern "C" __declspec(dllexport) Kid get_default_kid();
C#:
public struct Kid
{
public string _name;
public int _age;
public int _grade;
}
private const string Dll2 = @"C:UsersMesourcereposTomx64ReleaseJerryDll.dll";
[DllImport(Dll2, CallingConvention = CallingConvention.Cdecl)]
private static extern Kid get_default_kid();
public static void Main(string[] args){
Kid defaultKid = get_default_kid();
Console.WriteLine(defaultKid._name);
Console.WriteLine(defaultKid._age);
Console.WriteLine(defaultKid._grade);
}
Этот код записывает некоторые случайные символы, такие как ‘♠’, в консоль вместо имени, которое экспортируется из dll.
Что я пробовал:
пытаюсь импортировать char*
as IntPtr
, а затем прочитать его с помощью:
Marshal.PtrToStringAnsi()/BSTR/Auto/Uni
и Marshal.ReadIntPtr
прежде чем пытаться прочитать его, используя строку выше.
С тех пор я пытался преобразовать строку в UTF-8 в C #.
много искал в Google.
Комментарии:
1. В реальном мире это очень , очень , очень сложно. Проблема в том, что «обычно» строковое имя на стороне C будет каким-то образом выделено (например, malloc ), поэтому вам понадобится соответствующее
FreeKid(Kid*)
… В этом примереQuinn
используется строковый литерал C, поэтому вы никогда не должны его освобождать … 🙂2.
name
inget_default_kid
— это локальная переменная, которая исчезнет при возврате функции.3. И есть проблема владения памятью: см. limbioliong.wordpress.com/2011/06/16 /…
4. @Manuel итак, это означает, что когда функция возвращает указатель на этот массив символов (строку), массив исчезнет. следовательно, когда мой проект C # пытается разыменовать этот указатель, там ничего нет. Я понимаю, большое вам спасибо.
5. Мануэль помог мне решить мою проблему. Было бы здорово, если бы кто-нибудь добавил ответ на этот вопрос о том, как импортировать char * из dll в c # в целом и о лучших практиках. Так что я и все, кому может понадобиться помощь, можем читать и учиться. Спасибо.
Ответ №1:
В общем, когда мы говорим о маршалировании строк (или другой выделенной памяти) между C / C и C #, наиболее важным и сложным вопросом является: как будет освобождена память? Самым простым решением, если это возможно, является экспорт со стороны C / C одного или нескольких методов освобождения. Здесь я привожу несколько примеров:
Обратите внимание, что вы не можете
public struct Kid
{
public string _name;
public int _age;
public int _grade;
}
Вы получите сообщение об ошибке. Маршалер .NET не хочет маршалировать неблокируемые структуры (so struct
s, которые имеют сложные типы, такие как string
s), которые являются возвращаемыми значениями. По этой причине я использую a IntPtr
, а затем вручную маршалирую char*
в a string
.
C :
extern "C"
{
const char* pstrInternal = "John";
typedef struct Kid
{
char* _name;
int _age;
int _grade;
};
// WRONG IDEA!!! HOW WILL THE "USER" KNOW IF THEY SHOULD
// DEALLOCATE OR NOT?
__declspec(dllexport) Kid get_default_kid_const()
{
// MUSTN'T DEALLOCATE!!!
return Kid { (char*)pstrInternal, 2,7 };
}
__declspec(dllexport) Kid get_a_kid()
{
// this string must be freed with free()
char* pstr = strdup(pstrInternal);
return Kid { pstr, 2,7 };
}
__declspec(dllexport) void free_memory(void* ptr)
{
free(ptr);
}
__declspec(dllexport) void free_kid(Kid* ptr)
{
if (ptr != NULL)
{
free(ptr->_name);
}
}
}
C#:
public struct Kid
{
public IntPtr _namePtr;
public int _age;
public int _grade;
public string _name { get => Marshal.PtrToStringAnsi(_namePtr); }
}
[DllImport("CPlusPlusSide.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern Kid get_default_kid_const();
[DllImport("CPlusPlusSide.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern Kid get_a_kid();
[DllImport("CPlusPlusSide.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void free_memory(IntPtr ptr);
[DllImport("CPlusPlusSide.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void free_kid(ref Kid kid);
public static void Main(string[] args)
{
Kid kid = get_default_kid_const();
Console.WriteLine($"{kid._name}, {kid._age}, {kid._grade}");
// MUSTN'T FREE this Kid!!!
Kid kid2 = get_a_kid();
Console.WriteLine($"{kid2._name}, {kid2._age}, {kid2._grade}");
free_memory(kid2._namePtr);
Kid kid3 = get_a_kid();
Console.WriteLine($"{kid3._name}, {kid3._age}, {kid3._grade}");
free_kid(ref kid3);
}
Имейте в виду, что существует целый мир боли (боли, вызванной случайными сбоями и утечками памяти) для тех, кто маршалирует строки между C / C и C #, не зная точно, как это работает.
Ответ №2:
Поведение маршалинга по умолчанию для строк — это широкие строки, например LPWSTR
. Вам нужно будет добавить директиву маршалинга, чтобы сообщить .NET marshaler, что то, что вы действительно предоставили, — это узкая строка (например LPSTR
, или char*
).
Подробнее читайте в MSDN.
На этой странице есть пример, близкий к тому, что вы хотите — в основном вы можете просто скопировать директивы маршалинга:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
struct StringInfoA
{
[MarshalAs(UnmanagedType.LPStr)] public string f1;
}
Так что ваш был бы:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct Kid
{
[MarshalAs(UnmanagedType.LPStr)]
public string _name;
public int _age;
public int _grade;
}
Комментарии:
1.
The default marshaling behavior for strings is wide strings
— как указано в самой документации, на которую вы ссылаетесь, маршалинг по умолчанию для строк в структурахUnmanagedType.LPStr
. Фактически, ни один тип маршалинга строк на этой странице не имеетLPWStr
опции по умолчанию.2. Я изменил код на это, и он выдает исключение:
Unhandled Exception: System.Runtime.InteropServices.MarshalDirectiveException: M ethod's type signature is not PInvoke compatible.