2013-03-24 6 views
7

Non ho idea di quale sia il problema.PInvokeStackImbalance non causato da CallingConvention

Ho un sacco di p/invocare chiamate che funzionano senza inconvenienti ... tranne questo.

Sono riuscito a ridurre il mio problema al seguente codice di esempio.

Se rimuovo uno dei membri della struct (sia il doppio che l'int), funziona correttamente.

Sto assumendo che il problema sia in qualche modo correlato al layout della struct - ma quando faccio una sizeof() in C e uno Marshal.SizeOf() in C#, entrambi restituiscono lo stesso valore ... quindi se la dimensione della struct è la stessa in C# e C, quale potrebbe essere il problema?

Mi manca ovviamente qualcosa di semplice qui.

SampleDLLCode.c

#pragma pack(1) 

typedef struct SampleStruct { 
    double structValueOne; 
    int structValueTwo; 
} SampleStruct; 

__declspec(dllexport) SampleStruct __cdecl SampleMethod(void); 
SampleStruct SampleMethod(void) { 
    return (SampleStruct) { 1, 2 }; 
} 

costruire Script

gcc -std=c99 -pedantic -O0 -c -o SampleDLLCode.o SampleDLLCode.c 
gcc -shared --out-implib -o SampleDLL.dll SampleDLLCode.o 

C# Codice

using System; 
using System.Runtime.InteropServices; 

namespace SampleApplication 
{ 
    [StructLayout(LayoutKind.Sequential, Pack=1)] 
    public struct SampleStruct { 
     public double structValueOne; 
     public int structValueTwo; 
    } 

    class Program 
    { 
     [DllImport("SampleDLL.dll", CallingConvention = CallingConvention.Cdecl)] 
     public static extern SampleStruct SampleMethod(); 

     static void Main(string[] args) 
     { 
      SampleStruct sample = SampleMethod(); 
     } 
    } 
} 
+0

Niente salti fuori ... per il gusto di farlo, provare a scambiare l'ordine dei campi nella struct? (entrambi i lati, naturalmente) – JerKimball

+0

@JerKimball Grazie per la ricerca. Scambiare l'ordine dei campi nella struttura non ha alcun effetto. Ricevo ancora l'avviso MDA PInvokeStackImbalance. – Steve

+0

Potrebbe trattarsi di un problema tra GCC vs .NET e il doppio? Restituire una struttura con cdecl può portare a problemi dipendenti dal compilatore. Controlla questo http://stackoverflow.com/questions/13786192/methods-type-signature-is-not-pinvoke-compatible-while-calling-dll-method –

risposta

9

Prima di tutto le Mi congratulo con te per una domanda molto ben fatta. È stato un piacere, per una volta, ricevere tutto il codice necessario per riprodurre il problema.

Il problema è dovuto agli ABI leggermente diversi utilizzati da gcc e dagli strumenti Microsoft per i valori di ritorno delle funzioni. Per i valori di ritorno che possono essere inseriti nei registri, ad esempio i valori di ritorno int non ci sono differenze. Ma dal momento che la tua struttura è troppo grande per adattarsi in un unico registro e ci sono differenze tra le API in quella situazione.

Per valori di ritorno maggiori, il chiamante passa un puntatore nascosto alla funzione. Questo puntatore nascosto viene inserito nello stack dal chiamante. La funzione scrive il valore di ritorno sull'indirizzo di memoria specificato da quel puntatore nascosto. La differenza negli ABI è in chi apre quel puntatore nascosto fuori dallo stack. Gli strumenti Microsoft utilizzano un ABI che richiede al chiamante di inserire il puntatore nascosto, ma l'ABI predefinito di gcc chiede al destinatario di farlo.

Ora che gcc è quasi infinitamente configurabile, è disponibile un interruttore che consente di controllare l'ABI. E puoi fare in modo che gcc usi le stesse regole degli strumenti Microsoft. Ciò richiede lo callee_pop_aggregate_return function attribute.

modificare il codice C per essere come questo:

__declspec(dllexport) SampleStruct __cdecl SampleMethod(void) 
    __attribute__((callee_pop_aggregate_return(0))); 
    // specifies that caller is responsible for popping the hidden pointer 

SampleStruct SampleMethod(void) { 
    return (SampleStruct) { 1, 2 }; 
} 
+2

abbia risposto bene - e indagato! – JerKimball

+0

Ti amo. Grazie. Sei il mio eroe! – Steve

+0

David, rapida domanda di follow-up; È sicuro usare l'attributo callee_pop_aggregate_return su TUTTE le mie funzioni esportate per la mia build di Windows - o dovrei usarlo SOLO quando è effettivamente applicabile? Chiedo perché sarà più facile limitarsi ad aggiungere sempre una macro del precompilatore alla fine delle mie dichiarazioni, piuttosto che determinare dove lo avrò bisogno in particolare, quindi se è sicuro, sarà più facile. – Steve