Я не могу получить входной параметр DLL PInvoke для C ++ из C # для использования в качестве выхода с IntPtr - PullRequest
0 голосов
/ 02 октября 2018

У меня есть функция в C ++ DLL, которая принимает один ввод.Я пытаюсь использовать этот вход в качестве вывода для вызова C #.

Вот моя функция C ++:

MYAPI int testStuff3(unsigned char* str)
{
    printf("%s\n", str);
    str = (unsigned char*)malloc(9);
    str[0] = 'G';
    str[1] = 'o';
    str[2] = 'o';
    str[3] = 'd';
    str[4] = 'b';
    str[5] = 'y';
    str[6] = 'e';
    str[7] = '!';
    str[8] = '\0';
    return 1;
}

Вот код C #:

public class Program
{
    [DllImport("NativeLib.dll")]
    private static extern int testStuff3([In, Out] IntPtr str);

    static void Main(string[] args)
    {
        IntPtr junk3 = IntPtr.Zero;
        int ret = testStuff3(junk3);
        Byte[] stuff3 = new byte[9];
        Marshal.Copy(junk3, stuff3, 0, 9);
    }
}

Когда вызывается Marshal.Copy, он выдает ошибку о том, что источник (junk3) не может быть нулевым.

Это не будет работать, отправляя нулевой указатель на C ++ DLL из C # и имеяDLL выделяет память и хранит что-то внутри и возвращает это вызывающей стороне?Я хочу сохранить это IntPtr, а не StringBuilder, потому что данные не обязательно будут строкой в ​​конечном коде.Просто массив без знака в C ++, и я хочу, чтобы IntPtr указывал на него.

Я пробовал различные варианты [In, Out], [Out], out и ref для передачи IntPtr.

Ответы [ 3 ]

0 голосов
/ 02 октября 2018

Ваша функция C ++ не изменяет переданную строку.Он выделяет новый с помощью malloc, сохраняет его в локальной переменной, забывая переданное значение, а затем возвращает утечку памяти.

Если по какой-то причине вы хотите выполнить ручную сортировку, вы, вероятно, захотите что-то подобное (предполагая, чтоэто для Windows):

MYAPI BOOL __stdcall testStuff3( char** pp )
{
    if( nullptr == pp )
        return FALSE;   // null pointer
    if( nullptr != *pp )
    {   // Print & release an old string
        printf( "%s\n", *pp );
        CoTaskMemFree( *pp );
        *pp = nullptr;
    }
    // Allocate a new one
    const char* const str = CoTaskMemAlloc( 9 );
    if( nullptr == str ) return FALSE;
    strncpy( str, "Goodbye!", 9 );
    *pp = str;
    return TRUE;
}

C #:

public class Program
{
    [DllImport( "NativeLib.dll" )]
    private static extern bool testStuff3( [In, Out] ref IntPtr str );

    static void Main( string[] args )
    {
        IntPtr ptr = IntPtr.Zero;
        if( testStuff3( ref ptr ) )
        {
            Console.WriteLine( Marshal.PtrToStringAnsi( ptr ) );
            Marshal.FreeCoTaskMem( ptr );
        }
    }
}

Однако я не рекомендую делать это, если у вас нет веских причин.В большинстве случаев автоматическая сортировка лучше.Для C # -> C ++ это тривиально просто: const char* или const wchar_t* в C ++, string (с правильными атрибутами) в C #.Для C ++ -> C # вы можете выделить StringBuilder в C #, передать char* или wchar_t* в C ++ и длину буфера в другом аргументе.

0 голосов
/ 02 октября 2018

Спасибо за помощь!

Вот чем я закончил.

Функция C ++:

MYAPI int testStuff4(wchar_t* str)
{
    unsigned char* stuff = (unsigned char*)malloc(10);
    stuff[0] = 'G';
    stuff[1] = 'o';
    stuff[2] = 'o';
    stuff[3] = 'd';
    stuff[4] = 'b';
    stuff[5] = 'y';
    stuff[6] = 'e';
    stuff[7] = '!';
    stuff[8] = '\0';

    mbstowcs(str, (const char*)stuff, 1024);
    free(stuff);

    return 1;
}

Функция C #:

public class Program
{
    [DllImport("NativeLib.dll")]
    private static extern int testStuff4(IntPtr str);

    static void Main(string[] args)
    {
        IntPtr junk4 = Marshal.AllocHGlobal(1024);
        int ret = testStuff4(junk4);
        string junkString = Marshal.PtrToStringUni(junk4);
        Console.WriteLine(junkString);
        Marshal.FreeHGlobal(junk4);
    }
}
0 голосов
/ 02 октября 2018

Никогда не позволяйте выделению памяти пересекать границу DLL.В этом и заключается безумие и / или Спарта.

(Для педантичных: вы можете выделить память, а затем передать указатель, если вы либо передаете владение обратно, чтобы освободить его, либо гарантируете, что тот же распределительиспользуется как часть контракта, но по возможности этого следует избегать.)

Обычно для использования строкового выходного параметра вы должны передать StringBuilder в качестве аргумента, установив его емкость на максимальную ожидаемую длину,Затем в нативном коде вы просто заполняете этот существующий буфер.

См. Пример в разделе «Строковые буферы фиксированной длины» здесь .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...