Как импортировать экспортированный символ * из C dll в проект C #

#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 in get_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.