2011-01-05 15 views
24

Mam następujące funkcję w C++ DLLC# DllImport z C++ funkcja logiczna nie powraca poprawnie

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

w moim C# klasy mam następujące:

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

Jednak kiedy mogę zadzwonić funkcja ZAWSZE zwraca true, nawet gdy skomentowałem moją małą funkcję i zwróciłem wartość false. Mam wrażenie, że coś jest nie tak z moją konwencją wywoływania lub z jakimkolwiek innym problemem z P/wywoływaniem mojej DLL, prawdopodobnie odpowiadającym ciągowi znaków i const char *, ale na razie jestem całkowicie nieświadomy. Co ja robię źle? Dlaczego zwraca true zamiast false?

EDIT: I zorientowali się, to nie ma nic wspólnego z const char * lub ciąg, ponieważ problem nie został usunięty z pustym funkcji. Próbowałem zmienić konwencji wywoływania między Cdecl i StdCall i nie działają poprawnie. Udało mi się również zdebugować moją bibliotekę DLL i jest ona poprawnie wywoływana i rzeczywiście zwraca false, ale po powrocie do C# jest jakoś prawdą. Zmiana CharSet również nie przyniosła efektu. Upewniłem się, że za każdym razem dostarczałem mój program C# z najnowszą i poprawną wersją mojej biblioteki DLL, więc nie powinno to stanowić problemu. Ponownie, jestem całkowicie nieświadomy, dlaczego wynik jest prawdziwy, gdy w rzeczywistości wracam fałszywy.

EDIT2: SOReader warunkiem mnie z propozycją, która naprawia inny ważny problem, patrz mój komentarz. Niestety, nie rozwiązuje problemu z powrotem.

Edit3: I doszli do wniosku, że zmiana typu zwracanej istnieje (bool) do (int) sprawia, że ​​nagle zwróci poprawną liczbę (true = 1, false = 0). Oznaczałoby to, że może wystąpić problem między boolem C++ a boolem C#. Mogę nadal używać int jako bool, ale to nadal nie wyjaśnia pierwotnego problemu. Może ktoś inny może mnie o tym poinformować? Być może ma to związek z faktem, że używam x64 (chociaż oba pliki są kompilowane jako x86)

+0

Pierwszą rzeczą do sprawdzenia jest to, że funkcja ta jest w rzeczywistości 'cdecl'. Jeśli twój plik makefile przekazuje kompilatorowi 'Gz' lub' Gr', to powyższa funkcja nie jest 'cdecl'. Dodaj '__cdecl' do twojego kodu C lub włącz" Managed Debugging Assistant "pInvokeStackImbalance'. –

+0

Nie sądzę, że będzie link, jeśli/Gr lub/Gz są określone. Dobry punkt na temat Managed Debugging Assistant. –

+0

Przetestowałem to i __fastcall nie połączy się z/clr. Jednak linki __stdcall (/ Gz), ale punkt wejścia Exist nie zostanie znaleziony w czasie wykonywania, ponieważ sygnatura funkcji jest inna. –

Odpowiedz

37

Znalazłem rozwiązanie Twojego problemu. Twoje zgłoszenie powinno być poprzedzone tym Organizowanie: [return:MarshalAs(UnmanagedType.I1)]

więc wszystko powinno wyglądać tak:

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

testowałem go w moim bardzo prosty przykład i to działa!

EDYTOWANIE
Dlaczego tak się dzieje? C definiuje bool jako 4 bajty int (jak niektórzy z was powiedzieli), a C++ definiuje je jako 1 bajt. Zespół C# zdecydował się na użycie 4-bajtowego bool jako domyślnego podczas PInvoke, ponieważ większość funkcji API systemu używa 4 bajtów wartości jako bool. Jeśli chcesz zmienić to zachowanie, musisz to zrobić z marshaling, określając, że chcesz użyć wartości 1-bajtowej.

+0

To dziwne.Przetestowałem to w VS 2010 z 32-bitowymi wersjami DLL i C#, i 64-bitowymi kompilacjami obu i nie mam problemu opisanego przez Shammah. To, co mówisz, miałoby sens w maszynie Big Endian, ale nie w architekturach Little Endian, takich jak komputery oparte na architekturze x86, których używamy. Pamiętaj, że najmniej znaczący bajt, 1 lub 0, w obu przypadkach bool i 4-bajtowy int byłyby takie same. Pewnie dlatego to działa idealnie dla mnie. –

+0

@SimonBrangwin: Jeśli nie chcesz czytać wygenerowanego zespołu lub sam jesteś twórcą kompilatora, nie można zrozumieć, co dzieje się, gdy otrzymasz błędny typ zwrotu. To po prostu nie działa. Istnieje powód, dla którego C++ i C oznaczą to jako niezdefiniowane zachowanie. – Puppy

+0

Przyjmowałbym twoje rozumowanie re: C bools ma 4 bajty, a boxy C++ to 1 bajt, ale dzieje się tak również, gdy CallingConvention jest ustawione na ThisCall, co powinno dać jasno do zrozumienia, że ​​jest to metoda C++. Mimo to, jesteś tylko posłańcem, a przecież twoje rozwiązanie naprawiło mój problem, więc dziękuję! – ulatekh

0

Przetestowałem twój kod i dla mnie zwraca on wartość false. Musi więc być coś jeszcze.

Czy jesteś pewien, że poprawnie rekompilujesz bibliotekę DLL? Spróbuj usunąć plik .DLL i wykonać przebudowę.

Poza tym wszystko wydaje się być w porządku, zakładając. Domyślnie zestawianie obsługuje ciąg .NET do postaci stałej * bez konieczności dekorowania go atrybutami marszałka, niezależnie od tego, czy biblioteka DLL jest skompilowana jako ANSI czy Unicode.

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

2

Może Organizowanie argument funkcji może pomóc:

 
[MarshalAs(UnmanagedType.LPStr)] 

Oto jak deklaracja powinna wyglądać następująco:

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

Podczas debugowania tego dowiedziałem się, że to powinno być rzeczywiście używane. Jeśli mój ciąg znaków byłby "moja_nazwa", tylko "m" zostałoby przekazane jako argument bez zestawiania. Dzięki zestawieniom, cała "moja_nazwa" zostanie zaliczona. Niestety, to nadal nie rozwiązuje problemu z odesłaniem :( – Shammah

+0

Przeglądałem mój projekt napisany kilka lat temu i odkryłem, że użyłem PreserveSig, gdy zostały zwrócone dźwięki. Spróbuj tego i daj mi znać, jeśli to działa: [PreserveSig] bool foo(); – SOReader

+0

Właściwie użyłem tylko funkcji interfejsu - nie statycznych, więc nie wiem, czy moja wskazówka zadziała, ( – SOReader

4

C na bool jest rzeczywiście int, jak nie ma typu boolowskiego w oryginalnym języku C. Oznacza to, że jeśli DLLImport C# jest zaprojektowany do współdziałania z kodem C, to będzie oczekiwać, że C# 's bool odpowiada C's int.Chociaż to nadal nie wyjaśnia, dlaczego fałsz stał się prawdą, naprawienie go powinno rozwiązać problem.

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

Ten mówi, że UnmanagedType.Bool jest Win32 BOOL, który jest int.

2

Jest to spowodowane faktem, że EAX nie został całkowicie usunięty przez typowy kod C++, który zwraca wartość bool. Typowe jest, że EAX zawiera pewną fałszywą wartość podczas wprowadzania funkcji, a do return false kompilator zwykle emituje xor al, al. Powoduje to wyczyszczenie tylko LSB EAX i powoduje, że kod C# interpretuje wynikową niezerową wartość jako true zamiast false.

0

wyślę zmienną logiczną, przy użyciu następującego systemu

__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; 
    } 
} 

w C#, robię

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()); 
    } 
}