2013-04-20 18 views
7

Attualmente sto sperimentando alcune cose molto strane. Quando passo una struct da C++ a una DLL Delphi come parametro, tutto funziona correttamente. Tuttavia, non appena voglio ricevere un record come risultato ottengo valori errati o eccezioni. Ho disattivato l'allineamento del record in modo che il loro passaggio potesse funzionare! Ecco il codice!Passaggio record come risultato di una funzione da Delphi DLL a C++

Delphi DLL:

TSimpleRecord = packed record 
    Nr1 : Integer; 
    Nr2 : Integer; 
end; 

//... 

function TTest() : TSimpleRecord; cdecl; 
begin 
    Result.Nr1 := 1; 
    Result.Nr2 := 201; 
    ShowMessage(IntToStr(SizeOf(Result))); 
end; 

C++ chiamata:

#pragma pack(1) 
struct TSimpleRecord 
{ 
    int Nr1; 
    int Nr2; 
}; 

//... 

    typedef TSimpleRecord (__cdecl TestFunc)(void); 
    TestFunc* Function; 
    HINSTANCE hInstLibrary = LoadLibrary("Reactions.dll"); 
    if (hInstLibrary) 
    { 
     Function = (TestFunc*)GetProcAddress(hInstLibrary, "TTest"); 
     if (Function) 
     { 
      TSimpleRecord Result = {0}; 
      Result = Function(); 
      printf("%d - %d - %d", sizeof(Result), Result.Nr1, Result.Nr2); 
      cin.get(); 
     } 
    } 

Io ho idea del perché questo record passando come parametro opere, ma non come risultato di una funzione !?

Qualcuno può aiutarmi `

Grazie

PS:? Come ho già detto, sia C++ e Delphi mostrano che il record è di 8 byte di grandi dimensioni.

+0

Si sta effettivamente impostando il valore di ritorno nella funzione Delphi? – Angew

+0

Result.Nr1: = 1; Result.Nr2: = 201; Questo dovrebbe fare il lavoro – Henry

+0

Spiacente, quando ho interagito con Delphi per ultimo, 'Result' utilizza ancora l'identificatore di funzione, IIRR. – Angew

risposta

5

Alcuni compilatori restituiscono i tipi struct (eventualmente in base alla dimensione) nei registri, altri aggiungeranno un parametro aggiuntivo nascosto in cui il risultato deve essere memorizzato. Sfortunatamente, sembra che tu abbia a che fare con due compilatori che non sono d'accordo su come restituirli.

Si dovrebbe essere in grado di evitare il problema utilizzando in modo esplicito un parametro out.

procedure TTest(out Result: TSimpleRecord); cdecl; 
begin 
    Result.Nr1 := 1; 
    Result.Nr2 := 201; 
end; 

Non dimenticare di aggiornare il codice C++ di conseguenza.

Rudy Velthuis has written about this:

Questo mi ha mostrato che la struct ABCVar stato restituito nei registri EDX: EAX (EDX con i primi 32 bit, e EAX con quelle inferiori). Questo non è ciò che Delphi fa con i record, nemmeno con dischi di queste dimensioni. Delphi tratta tali tipi di ritorno come parametri extra var, e non restituisce nulla (quindi la funzione è in realtà una procedura).

[...]

L'unico tipo che Delphi ritorna come EDX: EAX combinazione è Int64.

che suggerisce che un modo alternativo per evitare il problema è

function TTest() : Int64; cdecl; 
begin 
    TSimpleRecord(Result).Nr1 := 1; 
    TSimpleRecord(Result).Nr2 := 201; 
end; 

noti che Delphi consente tale tipo punning anche in situazioni in cui il comportamento sarebbe indefinito in C++.

+0

Grazie, esattamente quello che stavo cercando! Anche se l'esempio che usa Int64 non funziona per me – Henry

+0

@Henry In che modo non funziona? Ti dà un messaggio di errore, o ottieni ancora un comportamento scorretto? Chiedo perché ho verificato che il risultato sia restituito nei registri (come previsto) in un'applicazione standalone. Sono contento che l'altro modo per evitare il problema funzioni, comunque :) – hvd

+0

Anche se sono d'accordo che il trucco Int64 dovrebbe funzionare. È così che leggo tutti i documenti. –

1

Delphi non segue la piattaforma standard ABI per i valori di ritorno. L'ABI standard passa i valori di ritorno al chiamante in base al valore. Delphi tratta il valore restituito come un parametro extra var implicito, passato dopo tutti gli altri parametri. Lo documentation descrive le regole.

È possibile modificare il codice chiamante per abbinarlo. Passa un riferimento extra al parametro struct nella tua funzione C++.

typedef void (__cdecl TestFunc)(TSimpleRecord&);  

Se avete intenzione di fare questo sul lato C++, si sarebbe meglio fare la stessa modifica sul lato Delphi per chiarezza.

Poiché Delphi non segue gli standard della piattaforma per i valori di ritorno, suggerisco di limitarsi a tipi compatibili con altri strumenti. Ciò significa valori interi fino a 32 bit, puntatori e valori in virgola mobile.

Come regola generale, non imballare i record. Se lo fai, avrai un allineamento errato che influisce sulle prestazioni. Per la cronaca della domanda, non ci sarà comunque riempimento poiché entrambi i campi hanno le stesse dimensioni.

Problemi correlati