Несоответствие стека Delphi + com marshalling = неправильное marshalling - PullRequest
7 голосов
/ 24 июня 2010

Это не совсем прямой вопрос, потому что я только что решил его, а скорее как вопрос типа "я правильно понял" и напоминание для тех, кто может застрять в этом.

Оказывается, Delphi не выравнивает переменные в стеке, и нет никаких директив / опций для управления этим поведением. Маршаллер COM по умолчанию на моем XP SP3 требует 4-байтового выравнивания при маршалинге записей. Хуже того, когда он встречает невыровненный указатель, он не возвращает ошибку, о нет: он округляет указатель до ближайшей 4-байтовой границы и продолжает так.

Поэтому, если вы передадите запись, которую вы распределили в стеке, в функцию COM-маршалирования по ссылке, вы облажались и даже не узнаете.

Проблема может быть решена с помощью New / Dispose для выделения записей, поскольку менеджеры памяти имеют тенденцию выравнивать все на 8 байтах или лучше, но, боже, это раздражает, как часть смещения, так и «указатели обрезки» часть.

Это действительно причина, или я где-то ошибаюсь?

Обновление : Как воспроизвести (Delphi 2007 для Win32).

uses SysUtils;

type
  TRec = packed record
    a, b, c, d, e: int64;
  end;

  TDummy = class
  protected
    procedure Proc(param1: integer);
  end;

procedure TDummy.Proc(param1: integer);
var a, b, c: byte;
  rec: TRec;
begin
  a := 5;
  b := 9;
  c := 100;

  rec.a := param1;
  rec.b := a;
  rec.c := b;
  rec.d := c;
  writeln(IntToHex(integer(@rec), 8));
  readln;
end;

var Obj: TDummy;
begin
  obj := TDummy.Create;
  try
    obj.Proc(0);
  finally
    FreeAndNil(obj);
  end;
end.

Это дает странный адрес результата, явно не выровненный по чему-либо. Если этого не произойдет, попробуйте добавить больше байтовых переменных в «a, b, c: byte» (и не забудьте смоделировать некоторую работу с ними в конце функции).

Часть с COM легче воспроизвести, но дольше объяснить. Создайте новое приложение VCL с именем Sample Server, добавьте COM-объект SampleObject, реализующий ISampleObject, с библиотекой типов, с однопотоковым одиночным экземпляром (убедитесь, что ISampleObject помечен как Ole Automation в библиотеке типов). Откройте библиотеку типов, объявите новый SampleRecord с пятью полями __int64. Добавьте функцию SampleFunction с одним параметром SampleRecord * out в ISampleObject. Реализуйте SampleFunction в TSampleObject, возвращая фиксированные значения:

function TSampleObject.SampleFunction(out rec: SampleRecord): HResult;
begin
  rec.a := 1291;
  rec.b := 742310;
  //...
  Result := S_OK;
end;

Обратите внимание, как Delphi объявляет SampleRecord как «упакованную запись» в автоматически сгенерированном коде заголовка библиотеки типов:

SampleRecord = packed record
  a: Int64;
  b: Int64;
  //...
end;

Я проверил, и это, по крайней мере, было исправлено в Delphi 2010. Автоматически сгенерированные записи там не упаковываются.

Зарегистрировать COM-сервер. Запустите его.

Теперь измените приведенный выше источник (пример 1), чтобы он вызывал этот сервер, а не просто писал:

uses SysUtils, Windows, ActiveX, SampleServer_TLB;

procedure TDummy.Proc(param1: integer);
var a, b, c: byte;
  rec: SampleRecord;
  Server: ISampleObject;
begin
  a := 5;
  b := 9;
  c := 100;

  rec.a := param1;
  rec.b := a;
  rec.c := b;
  rec.d := c;
  Server := CoSampleObject.Create;
  hr := Server.SampleFunction(rec);
  writeln('@: 'IntToHex(integer(@rec), 8)+', rec.a='+IntToStr(rec.a));
  readln;
end;

var Obj: TDummy;
begin
  CoInitializeEx(nil, COINIT_MULTITHREADED);
  obj := TDummy.Create;
  try
    obj.Proc(0);
  finally
    FreeAndNil(obj);
    CoUninitialize();
  end;
end.

Заметьте, что когда адрес записи не выровнен, значения полей записи являются неправильными (в частности, битовое смещение до 8, 16 или 24 бит, иногда переносится в следующее значение).

1 Ответ

8 голосов
/ 24 июня 2010

У вас есть пример смещения стека?Delphi должен выравнивать все по 4-байтовым границам.Единственный случай, о котором я могу подумать, может привести к смещению, если в цепочке вызовов есть какой-нибудь ассемблерный код, который явно что-то сделал со стеком, чтобы выровнять его.

...