2011-01-05 1 views
24

я следующая функция в C++ DLLC# DllImport с C++ функции булева не возвращает правильно

extern "C" __declspec(dllexport) bool Exist(const char* name) 
{ 
//if (g_Queues.find(name) != g_Queues.end()) 
// return true; 
//else 
// return false; 
return false; 
} 

Внутри мой C# класса У меня есть следующий:

[DllImport("Whisper.dll", EntryPoint="Exist", CallingConvention=CallingConvention.Cdecl)] 
     public static extern bool Exist(string name); 

Тем не менее, всякий раз, когда я называю функция ВСЕГДА возвращает true, даже когда я прокомментировал свою маленькую функцию и сделал ее возвратом false. Я чувствую, что что-то не так с моим соглашением о вызове или с какой-либо другой проблемой с P/Invoking моей DLL, вероятно, соответствует строке и const char *, но пока я совершенно не знаю. Что я делаю не так? Почему он возвращает true вместо false?

EDIT: Я понял, что это не имеет ничего общего с константным полукокса * или струны, потому что проблема не устраняется с пустой функцией. Я попытался изменить соглашение о вызовах между Cdecl и StdCall и не работает правильно. Мне также удалось отладить мою DLL, и она называется правильно и действительно возвращает false, но как только она возвращается на C#, это как-то правда. Изменение CharSet также не повлияло. Я убедился, что каждый раз предоставлял свою программу C# с последней и правильной версией моей DLL, так что это тоже не проблема. Опять же, я совершенно не знаю, почему результат верен, когда я фактически возвращаю ложь.

EDIT2: SOReader предоставил мне с предложением, которое фиксирует еще один важный вопрос, см мой комментарий. К сожалению, это не устраняет проблему возврата.

EDIT3: я пришел к выводу, что изменение типа возвращаемого Exist (BOOL) в (Int) вдруг делает возвращает правильное число (истина = 1, ложь = 0). Это означало бы, что может возникнуть проблема между bool C++ и bool C#. Я могу продолжать использовать int как bool, но это все равно не объяснит исходную проблему. Может, кто-то еще может просветить меня на этом? Возможно, это связано с тем фактом, что я использую x64 (хотя оба объекта скомпилированы как x86)

+0

Первое, что нужно проверить, что функция на самом деле 'cdecl'. Если ваш make-файл передает компилятору 'Gz' или' Gr', то функция выше не 'cdecl'. Добавьте '__cdecl' в свой C-код или включите помощник Managed Debugging Assistant' pInvokeStackImbalance'. –

+0

Я не думаю, что это будет ссылка, если указаны/Gr или/Gz. Хороший вопрос об управляемом помощнике отладки. –

+0

Я протестировал его, и __fastcall не будет связываться с/clr. Но ссылки __stdcall (/ Gz), но тогда точка входа Exist не встречается во время выполнения, поскольку сигнатура функции отличается. –

ответ

37

Я нашел решение проблемы. Ваше заявление должно предшествовать этому маршалинге: [return:MarshalAs(UnmanagedType.I1)]

так все должно выглядеть следующим образом:

[DllImport("Whisper.dll", EntryPoint="Exist", CallingConvention=CallingConvention.Cdecl)] 
[return:MarshalAs(UnmanagedType.I1)] 
public static extern bool Exist([MarshalAs(UnmanagedType.LPStr)] string name); 

Я проверил это в моем очень простом примере, и это сработало!

EDIT
Почему это происходит? C определяет bool как 4 байта int (как некоторые из вас сказали), а C++ определяет его как 1 байт. Команда C# решила использовать 4 байта bool по умолчанию во время PInvoke, потому что большая часть функции системного API использует 4 байта в качестве bool. Если вы хотите изменить это поведение, вам нужно сделать это с помощью маршалинга, указав, что вы хотите использовать 1 байтовое значение.

+0

Это странно.Я тестировал его в VS 2010 с 32-битными сборками DLL и C#, а также с 64-битными сборками обоих и не имею проблемы, описанной Шаммой. То, что вы говорите, имело бы смысл в машине большого конца, но не в архитектурах Little Endian, таких как настольные компьютеры на базе x86, которые мы используем. Помните, что младший байт, 1 или 0, в обоих случаях для bool и 4 байта int будет одинаковым. Вероятно, поэтому он отлично работает для меня, я думаю. –

+0

@SimonBrangwin: Если вы не хотите читать сгенерированную сборку или сами являетесь поставщиком компилятора, то почти невозможно понять, что происходит, когда вы неправильно получаете тип возвращаемого значения. Это просто не работает. Есть причина, по которой C++ и C будут отмечать это как неопределенное поведение. – Puppy

+0

Я бы согласился с вашими рассуждениями re: C bools - 4 байта, а C++ bools - 1 байт, но это также происходит, когда для CallingConvention установлено значение ThisCall, что должно дать понять, что это метод C++. Тем не менее, вы просто посланник, и в конце концов, ваше решение устранило мою проблему, так что спасибо! – ulatekh

0

Я проверил ваш код, и он возвращает false для меня. Так что должно быть что-то еще.

Вы действительно правильно скопировали DLL? Попробуйте удалить .DLL и выполнить перестройку.

Помимо этого все кажется прекрасным. По умолчанию маршаллинг будет обрабатывать строку .NET для const char * без необходимости ее декорировать атрибутами маршала, независимо от того, скомпилирована ли DLL как ANSI или Unicode.

См http://msdn.microsoft.com/en-us/library/s9ts558h.aspx#cpcondefaultmarshalingforstringsanchor5

2

Возможно сортировочной аргумент функции может помочь:

 
[MarshalAs(UnmanagedType.LPStr)] 

Вот как декларация должна выглядеть следующим образом:

 
[DllImport("Whisper.dll", EntryPoint="Exist", CallingConvention=CallingConvention.Cdecl)] 
     public static extern bool Exist([MarshalAs(UnmanagedType.LPStr)] string name); 
+0

Отправляя это, я узнал, что это действительно нужно использовать. Если моя строка была «my_name», только «m» передается как аргумент без сортировки. При сортировке все «my_name» будет передано. К сожалению, это еще не исправляет проблему с возвратом :( – Shammah

+0

Я просмотрел свой проект несколько лет назад, и я обнаружил, что использовал PreserveSig при возвращении bools. Попробуйте это и сообщите мне, если это работает: [PreserveSig] bool foo(); – SOReader

+0

На самом деле я использовал только функции интерфейса - не статические, поэтому я не знаю, будет ли мой отзыв работать ( – SOReader

4

Кассиопеян bool на самом деле int, а на исходном языке C нет булевского типа. Это означает, что если DLLImport C# предназначен для взаимодействия с кодом C, тогда они ожидают, что C# bool соответствует int.Хотя это все еще не объясняет, почему ложь станет истинной, ее исправление должно устранить проблему.

http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.unmanagedtype.aspx

Это говорит о том, что UnmanagedType.Bool является Win32 BOOL, который является int.

2

Это фактически вызвано тем, что EAX не полностью очищается от обычного кода на C++, который возвращает bool. Для EAX типично содержать некоторое фиктивное значение при вводе функции, а для return false компилятор обычно выдавал xor al, al. Это очищает только LSB EAX и вызывает код C# для интерпретации полученного ненулевого значения как true вместо false.

0

Я посылаю логическую переменную, используя следующую систему

__declspec(dllexport) const bool* Read(Reader* instance) { 
    try { 
     bool result = instance->Read(); 
     bool* value = (bool*)::CoTaskMemAlloc(sizeof(bool)); 
     *value = result; 
     return value; 
    } catch (std::exception exp) { 
     RegistryException(exp); 
     return nullptr; 
    } 
} 

В C#, я

DllImport(WrapperConst.dllName)] 
public static extern IntPtr Read(IntPtr instance); 

public bool Read() { 
    IntPtr intPtr = ReaderWrapper.Read(instance)); 
    if(intPtr != IntPtr.Zero) { 
     byte b = Marshal.ReadByte(intPtr); 
     Marshal.FreeHGlobal(intPtr); 
     return b != 0; 
    } else { 
     throw new Exception(GetLastException()); 
    } 
} 
Смежные вопросы