2010-06-24 22 views
6

Esto no es exactamente una pregunta directa porque acabo de resolverlo, sino más bien como un tipo de pregunta "estoy haciendo lo correcto" y un recordatorio para aquellos que podrían quedarse atascados en eso.desalineamiento de la pila Delphi + clasificación combinada = clasificación errónea

Resulta que Delphi no alinea las variables en la pila y no hay directivas/opciones para controlar este comportamiento. El Marshaller COM predeterminado en mi XP SP3 parece requerir una alineación de 4 bytes cuando se combinan los registros. Peor aún, cuando encuentra un puntero desalineado, no devuelve un error, oh no: redondea el puntero al límite de 4 bytes más cercano y continúa así.

Por lo tanto, si pasa un registro que ha asignado en la pila a la función de referencia de COM por referencia, está jodido y ni siquiera lo sabrá.

El problema se puede resolver usando Nuevo/Eliminar para asignar registros, ya que los administradores de memoria tienden a alinear todo a 8 bytes o mejor, pero Dios, esto es molesto, tanto la parte de desalineación como los "recortar-apuntadores" "parte.

¿Es este realmente el motivo, o me equivoco?

Actualización: Cómo reproducir (Delphi 2007 para 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. 

Esto da una dirección de resultado impar, claramente no alineada en nada. Si no es así, intente agregar más variables de bytes a "a, b, c: byte" (y no olvide simular algún trabajo con ellas al final de la función).

La pieza con COM es más fácil de reproducir pero más larga de explicar. Cree una nueva aplicación VCL llamada Sample Server, agregue un objeto COM SampleObject que implemente ISampleObject, con una biblioteca de tipos, una única instancia de subproceso libre (asegúrese de comprobar que ISampleObject está marcado como Ole Automation en la biblioteca de tipos). Abra la biblioteca de tipos, declare un nuevo SampleRecord con cinco campos __int64. Agregue un SampleFunction con un solo parámetro SampleRecord * out a ISampleObject. Implementar sampleFunction en TSampleObject mediante la devolución de valores fijos:

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

Nota cómo Delphi declara SampleRecord como "registro lleno" en el código de cabecera biblioteca de tipos generada automáticamente:

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

He comprobado, y esto, por lo menos , se arregló en Delphi 2010. Los registros generados automáticamente no están empaquetados allí.

Registre el servidor COM. Ejecutarlo.

Ahora modificará el código anterior (muestra 1) para llamar a este servidor en vez de hacer writeln:

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. 

Observe que cuando la dirección de REC no está alineado, los valores de los campos rec están equivocados (en concreto, cambio de bits a 8, 16 o 24 bits, a veces envuelto en el siguiente valor).

+0

¿Qué versión de Delphi, qué Administrador de memoria? ¿Puedes darnos un ejemplo que podamos probar? –

+0

Delphi 2007, FastMM4 listo para usar. Ejemplos agregados – himself

+1

Ingrese un informe en Quality Central con este caso de prueba; http://qc.embarcadero.com –

Respuesta

8

¿Tiene un ejemplo de que la pila está desalineada? Delphi debe alinear todo a los límites de 4 bytes. El único caso en el que puedo pensar que provocaría la desalineación es si un lugar a lo largo de la cadena de llamadas es algún código de ensamblador que ha hecho explícitamente algo en la pila para desalinearlo.

+0

¿Sería posible un campo en un registro empaquetado, o un byte en, por ejemplo, una matriz? –

+1

Lo haría, pero ¿cómo se relacionaría eso con el almacenamiento de la pila? –

+1

@ Allen - Simplemente un recordatorio amistoso de que las opciones de alineación de código y variable (especialmente para permitir la alineación de límites de 16 bytes) serían muy deseables. – PhiS