Это не совсем прямой вопрос, потому что я только что решил его, а скорее как вопрос типа "я правильно понял" и напоминание для тех, кто может застрять в этом.
Оказывается, 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 бит, иногда переносится в следующее значение).