Строковый литерал C против строкового массива / указателя C с P / Invoke не работает правильно - PullRequest
1 голос
/ 15 марта 2012

У меня есть простая настройка ситуации:

Базовый код .NET:

[DllImport ... stuff... use cdecl]
public static extern void SetCallback(CallbackDelegate c);

[UnmanagedFunctionPointer ... use cdecl]
public delegate CallbackDelegate(MarshalAs(single byte string with null terminating character pointer) string c)    

public static CallbackDelegate theNotGCdDelegate = null;
public void Start() {
    theNotGCdDelegate = new CallbackDelegate(CallbackCalledHere);
    SetCallback(theNotGCdDelegate);
}

public void CallbackCalledHere(string text) {
    Debug.WriteLine(text);
}

Базовый код C (скомпилирован с MinGW):

__declspec(dllexport) void __cdecl SetCallback(void (__cdecl *TheCallback)(char* text)){
    // This does not work as expected:
    TheCallback("This is a string literal");

    // This works as expected:
    char pointerMessage[] = "This is also a string literal, but referenced by an array.";
    TheCallback(pointerMessage);
}

Итакмоя ситуация (и я обещаю, что я не придумываю это!) заключается в том, что вызов SetCallback из C со строковым литералом direct (т. е. TheCallback ("Hello World")) приводит к пустой строке (первый байт строкинулевой)..NET преобразует это в пустую строку (то есть, "").Я подтвердил, что это так, используя Marshal.ReadByte и сам Marshaling параметра как IntPtr.

Во втором случае (при объявлении указателя / массива и передаче этой переменной в TheCallback) яполучить строку идеально и как ожидалось!

Это не имеет смысла для меня теоретически.Обе строки должны быть помещены в раздел данных C DLL, и обе имеют указатели, которые никогда не должны двигаться!Обе ситуации должны передавать указатели на фактические данные.У меня нет предупреждений о компиляции.

Это может быть связано, но, возможно, нет: я работаю на 64-битной машине.Я на 100% уверен, что когда я компилирую 32-битную DLL в MinGW, я всегда вызываю эту DLL из 32-битного пространства процесса в моем приложении .NET (явно выводя 32-битную сборку).Аналогично, когда я компилирую 64-битную DLL, я всегда вызываю эту DLL из 64-битного пространства процесса в моем приложении .NET (явно выводя 64-битную сборку).

Я проверил, что правильный 32-битный флаготображается в диспетчере задач и отсутствует в 64-разрядной сборке.DLL находится только в каталоге bin и не является regsvr32'd.

Важно, что в 64-битной DLL эта проблема не возникает: обе строки правильно передаются в среду выполнения .NET.32-битная DLL имеет проблему, о которой я говорил выше.

Я компилирую / связываю 32-битную DLL с gcc / ld из MinGW (из http://mingw.org/). Я компилирую/ связывание 64-битной DLL с пакетом mingw-w64-bin_i686-mingw_20111220.zip из http://mingw -w64.sourceforge.net /

Может кто-нибудь объяснить, что здесь происходит? Или, может быть,вы бы предпочли другой компилятор или флаги компилятора?

EDIT

Я только что обнаружил, что GetFunctionPointerForDelegate работает только с функциями stdcall. Я думаю, что это важный момент.Я использовал этот вызов Marshal, но я полагаю, что теперь я буду полностью избегать его, поскольку обратный вызов должен быть cdecl. Я предполагаю, что нотации UnmanagedFunctionPointer должно быть достаточно для передачи указателя на функцию cdecl.

Ответы [ 2 ]

2 голосов
/ 18 марта 2012

Несмотря на отсутствие моего «реального кода», эта проблема была решена путем неиспользования ld при компиляции.Очевидно, мне не хватало некоторых флагов компоновщика, которые - при использовании gcc - были автоматически добавлены компилятором.Все мои проблемы исчезли, когда я перешел на использование только gcc.

Спасибо всем!

0 голосов
/ 15 марта 2012

В Linux gcc обычно помещает строковые литералы в раздел, доступный только для чтения (.rodata для формата ELF; не уверен, какой эквивалент будет для Windows).

Попробуйте скомпилировать с параметром -fwritable-stringsи посмотрите, имеет ли это значение.

Редактировать

Дважды проверил в моей системе Linux (Mingw не нужен).Без опции -fwritable-strings строковые литералы помещаются в раздел .rodata.С опцией они идут в .data.

...