2010-06-24 3 views
6

Ce n'est pas exactement une question simple parce que je viens de le résoudre, mais plus comme "suis-je le droit" type de question et un rappel pour ceux qui pourraient se coincer dans cela.désalignement de pile Delphi + com marshalling = mauvaise affectation

Il s'avère que Delphi n'aligne pas les variables sur la pile et qu'il n'y a pas de directives/options pour contrôler ce comportement. Le marshaller de COM par défaut sur mon XP SP3 semble exiger l'alignement de 4 bytes en marshaling cependant des enregistrements. Pire, quand il rencontre un pointeur non aligné, il ne renvoie pas d'erreur, oh non: il arrondit le pointeur à la limite de 4 octets la plus proche et continue ainsi. Par conséquent, si vous transmettez un enregistrement que vous avez attribué sur une pile dans la fonction COM-marshaled par référence, vous êtes vissé et vous ne le saurez même pas. Le problème peut être résolu en utilisant New/Dispose pour allouer des enregistrements, car les gestionnaires de mémoire ont tendance à aligner tout à 8 octets ou mieux, mais dieu, c'est ennuyeux, à la fois la partie de désalignement et les "trim-down-pointers" " partie.

Est-ce vraiment la raison, ou ai-je tort quelque part?

Mise à jour: Comment reproduire (Delphi 2007 pour 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. 

Ceci donne une adresse de résultat impaire, clairement non alignée sur quoi que ce soit. Si ce n'est pas le cas, essayez d'ajouter plus de variables octets à "a, b, c: byte" (et n'oubliez pas de simuler un peu de travail avec eux à la fin de la fonction).

La partie avec COM est plus facile à reproduire mais plus longue à expliquer. Créez une nouvelle application VCL appelée Sample Server, ajoutez un objet COM SampleObject implémentant ISampleObject, avec une bibliothèque de types, une instance unique à thread libre (assurez-vous de vérifier que ISampleObject est marqué comme Ole Automation dans la bibliothèque de types). Ouvrez la bibliothèque de types, déclarez un nouvel SampleRecord avec cinq champs __int64. Ajoutez une SampleFunction avec un seul paramètre SampleRecord * out à ISampleObject. Mettre en œuvre sampleFunction en TSampleObject par des valeurs fixes de retour:

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

Notez comment Delphi déclare SampleRecord comme « enregistrement emballé » dans le code d'en-tête bibliothèque de type généré automatiquement:

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

J'ai vérifié, et cela, au moins , a été corrigé dans Delphi 2010. Les enregistrements générés automatiquement ne sont pas compressés.

Enregistrez le serveur COM. Exécuter.

modifier maintenant la source ci-dessus (exemple 1) pour appeler ce serveur au lieu de simplement faire 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. 

Observons que, si l'adresse de rec est pas aligné, les valeurs des champs rec sont erronés (en particulier, bitshifted à 8, 16 ou 24 bits, parfois enveloppé à la prochaine valeur).

+0

Quelle version de Delphi, quel gestionnaire de mémoire? Pouvez-vous donner un exemple que nous pouvons essayer? –

+0

Delphi 2007, prêt à l'emploi FastMM4. Exemples ajoutés – himself

+1

Veuillez entrer un rapport dans Quality Central avec ce scénario de test. http://qc.embarcadero.com –

Répondre

8

Avez-vous un exemple de désalignement de la pile? Delphi devrait aligner tout à 4 frontières d'octets. Le seul cas que je peux penser à ce qui causerait un désalignement est si quelque part le long de la chaîne d'appel est un code assembleur qui a explicitement fait quelque chose à la pile pour le désaligner.

+0

Un champ dans un enregistrement condensé serait-il possible, ou un octet dans un tableau? –

+1

Ce serait, mais comment cela serait-il lié au stockage de la pile? –

+1

@Allen - Juste un rappel amical que les options d'alignement de variables et de code (en particulier pour permettre l'alignement des limites de 16 octets) seraient très désirables. – PhiS