Передача записи как результата функции из библиотеки DLL Delphi в C++

Я испытываю очень странные вещи прямо сейчас. Когда я передаю структуру из C++ в Delphi DLL в качестве параметра, все работает нормально. Однако, как только я хочу получить запись в результате, я получаю неправильные значения или исключения. Я отключил выравнивание записи, чтобы передача их должна работать! Вот код!

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++ вызов :

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

Я понятия не имею, почему передача этой записи как параметр работает, но не в результате функция!?

может кто-нибудь помочь мне?`

спасибо

PS: Как я уже сказал, как C++, так и Delphi показывают, что запись составляет 8 байт.

2 ответов


некоторые компиляторы вернется struct типы (возможно, в зависимости от размера) в регистрах, другие добавят скрытый дополнительный параметр, где должен храниться результат. К сожалению, похоже, что вы имеете дело с двумя компиляторами, которые не согласны с тем, как их вернуть.

вы должны быть в состоянии избежать проблемы явно с помощью out параметр вместо этого.

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

не забудьте обновить код C++ соответственно.

Руди Velthuis написал об этом:

это показало мне, что структура ABCVar была возвращена в регистрах EDX:EAX (EDX с верхними 32 битами и EAX с нижними). Это не то, что Delphi делает с записями вообще, даже с записями такого размера. Delphi рассматривает такие возвращаемые типы как дополнительные параметры var и ничего не возвращает (поэтому функция фактически является процедурой).

[...]

единственный тип, который Delphi возвращает как EDX:eax комбинация Int64.

что предполагает, что альтернативным способом избежать проблемы является

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

обратите внимание, что Delphi позволяет такой тип каламбура даже в ситуациях, когда поведение будет неопределенным в C++.


Delphi не следует стандарту платформы ABI для возвращаемых значений. Стандартный ABI передает вызывающему объекту возвращаемые значения по значению. Delphi рассматривает возвращаемое значение как неявный дополнительный параметр var, передаваемый после всех других параметров. The документация описывает правила.

вы можете изменить код вызова, чтобы соответствовать этому. Передайте дополнительную ссылку на параметр struct в функции C++.

typedef void (__cdecl TestFunc)(TSimpleRecord&);     

Если вы собираетесь сделать это на C++ с другой стороны, вам было бы лучше сделать то же самое изменение на стороне Delphi для ясности.

поскольку Delphi не соответствует стандартам платформы для возвращаемых значений, я предлагаю вам ограничить себя типами, совместимыми с другими инструментами. Это означает интегральные значения до 32 бит, указатели и значения с плавающей запятой.

Как правило, не упаковывать записи. Если вы это сделаете, у вас будет неправильное выравнивание, которое влияет на производительность. Для записи в вопросе, будет в любом случае не заполняйте, так как оба поля имеют одинаковый размер.