2011-01-05 8 views
24

Ho la seguente funzione in una DLL C++funzione booleana C# DllImport con C++ non tornare correttamente

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

Dentro la mia classe C# ho la seguente:

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

Eppure, ogni volta che io chiamo il mio Funziona SEMPRE restituisce true, anche quando ho commentato la mia piccola funzione e l'ho resa falsa. Ho la sensazione che ci sia qualcosa di sbagliato nella mia convenzione di chiamata o in qualsiasi altro problema con P/Invocazione della mia DLL, probabilmente corrispondente alla stringa e al const char *, ma per ora sono completamente all'oscuro. Che cosa sto facendo di sbagliato? Perché restituisce vero invece di falso?

EDIT: ho capito questo non ha nulla a che fare con il const char * o una stringa, perché il problema persiste con una funzione vuota. Ho provato a cambiare la convenzione di chiamata tra Cdecl e StdCall e nessuno dei due funziona correttamente. Sono anche riuscito a eseguire il debug della mia DLL e viene chiamato correttamente e restituisce in effetti false, ma una volta tornato in C# in qualche modo è vero. Anche cambiare il CharSet non ha avuto alcun effetto. Mi sono assicurato di aver fornito il mio programma C# con la versione più recente e corretta della mia DLL ogni volta, quindi non dovrebbe esserci un problema. Ancora una volta, sono completamente all'oscuro del perché il risultato sia vero quando in effetti sto restituendo false.

EDIT2: SOReader mi ha fornito un suggerimento che corregge un altro tema importante, vedi il mio commento. Purtroppo, non risolve il problema di ritorno.

Edit3: ho concluso che cambiando il tipo di ritorno di Exist (bool) in (int) rende improvvisamente restituire il numero corretto (true = 1, false = 0). Ciò significherebbe che potrebbe esserci un problema tra il bool di C++ e il booster di C#. Posso continuare a usare int come bool, ma questo non spiegherebbe il problema originale. Forse qualcun altro può illuminarmi su questo? Forse ha a che fare con il fatto che sto usando x64 (anche se entrambi i pacchetti sono compilati come x86)

+0

La prima cosa da controllare è che la funzione è di fatto 'cdecl'. Se il tuo makefile passa 'Gz' o' Gr' al compilatore, la funzione sopra non è 'cdecl'. Aggiungi un '__cdecl' al tuo codice C, o abilita l'assistente di debug gestita' pInvokeStackImbalance'. –

+0

Non penso che collegherà se/Gr o/Gz sono specificati. Buon punto a proposito del Managed Debugging Assistant. –

+0

L'ho provato e __fastcall non si collegherà con/clr. Ma i collegamenti __stdcall (/ Gz) ma il punto di accesso Exist non viene trovato in fase di esecuzione poiché la firma della funzione è diversa. –

risposta

37

Ho trovato la soluzione per il tuo problema. La vostra dichiarazione deve essere preceduto con questo marshalling: [return:MarshalAs(UnmanagedType.I1)]

quindi tutto dovrebbe assomigliare a questo:

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

ho provato nel mio esempio molto semplice e ha funzionato!

MODIFICA
Perché questo succede? C definisce bool come 4 byte int (come alcuni di voi hanno detto) e C++ lo definisce come 1 byte. Il team C# ha deciso di utilizzare 4 byte bool come predefinito durante PInvoke perché la maggior parte della funzione API di sistema utilizza 4 byte come bool. Se si desidera modificare questo comportamento, è necessario farlo con il marshalling specificando che si desidera utilizzare il valore di 1 byte.

+0

È strano.L'ho provato in VS 2010 con i build a 32 bit della DLL e l'app C#, i build a 64 bit di entrambi e non hanno il problema descritto da Shammah. Quello che stai dicendo avrebbe senso in una macchina Big Endian, ma non nelle architetture Little Endian come i desktop basati su x86 che usiamo. Ricorda, il byte meno significativo, 1 o 0, in entrambi i casi per bool e 4 byte int sarebbe lo stesso. Probabilmente è il motivo per cui funziona perfettamente per me, immagino. –

+0

@SimonBrangwin: A meno che tu non voglia leggere l'assembly generato o sia tu stesso un compilatore, è praticamente impossibile capire cosa succede quando ottieni il tipo di ritorno sbagliato. Semplicemente non funziona. C'è un motivo per cui C++ e C dovrebbero entrambi contrassegnarlo come comportamento non definito. – Puppy

+0

Accetterei il tuo ragionamento: C bools essendo 4 byte e booster C++ essendo 1 byte, ma questo succede anche quando CallingConvention è impostato su ThisCall, che dovrebbe chiarire che si tratta di un metodo C++. Comunque, tu sei solo il messaggero, e dopo tutto, la tua soluzione ha risolto il mio problema, quindi grazie! – ulatekh

0

Ho testato il tuo codice e restituisce false per me. Quindi deve esserci qualcos'altro in corso.

Sei sicuro di voler ricompilare correttamente la DLL? Prova a eliminare la DLL e ad eseguire una ricostruzione.

A parte questo, tutto sembra andar bene. Per impostazione predefinita, il marshalling gestirà la stringa .NET su const char * senza doverla decorare con gli attributi Marshal, indipendentemente dal fatto che la DLL sia compilata come ANSI o Unicode.

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

2

Forse marshalling l'argomento della funzione potrebbe aiutare:

 
[MarshalAs(UnmanagedType.LPStr)] 

Ecco come la dichiarazione dovrebbe essere simile:

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

Durante il debug di questo ho scoperto che questo dovrebbe essere effettivamente utilizzato. Se la mia stringa era "my_name", solo "m" sarebbe stata passata come argomento senza il marshalling. Con il marshalling, l'intero "my_name" sarebbe passato. Purtroppo, questo non risolve il problema di ritorno :( – Shammah

+0

Ho recensito il mio progetto scritto alcuni anni fa e ho scoperto che ho usato PreserveSig quando bool sono stati restituiti. Prova questo e fammi sapere se funziona: [PreserveSig] bool foo(); – SOReader

+0

In realtà ho utilizzato solo le funzioni di interfaccia, non quelle statiche, quindi non so se il mio suggerimento funzionerà ( – SOReader

4

di C bool è in realtà int, come non esiste alcun tipo booleano nel linguaggio C originale. Ciò significa che se DLLImport di C# è progettato per l'interoperabilità con il codice C, allora si aspetteranno che C# bool corrisponda a C int.Mentre questo non spiega ancora perché il falso diventerebbe realtà, risolverlo dovrebbe risolvere il problema.

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

Questo dice che UnmanagedType.Bool è Win32 BOOL, che è un int.

2

Questo in realtà è causato dal fatto che EAX non viene completamente cancellato dal tipico codice C++ che restituisce un bool. È tipico che EAX contenga qualche valore falso quando si immette una funzione e che a return false il compilatore generi normalmente xor al, al. Ciò cancella solo l'LSB di EAX e causa il codice C# per interpretare il valore non zero risultante come true anziché false.

0

mando la variabile booleana, utilizzando il seguente sistema

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

In C#, faccio

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