2009-09-20 6 views
2

Envisagez le scénario suivant:Manière élégante pour gérer ce problème de chaîne. (Numéro Unicode-PAnsiString)

type 
PStructureForSomeCDLL = ^TStructureForSomeCDLL; 
TStructureForSomeCDLL = record 
    pName: PAnsiChar; 
end 

function FillStructureForDLL: PStructureForSomeDLL; 
begin 
    New(Result); 
    // Result.pName := PAnsiChar(SomeObject.SomeString); // Old D7 code working all right 
    Result.pName := Utf8ToAnsi(UTF8Encode(SomeObject.SomeString)); // New problematic unicode version 
end; 

...code to pass FillStructureForDLL to DLL... 

Le problème dans la version unicode est que la conversion de chaîne impliquée retourne maintenant une nouvelle chaîne sur la pile et qui est remis en état à la fin de l'appel FillStructureForDLL, laissant la DLL avec des données corrompues. Dans l'ancien code D7, il n'y avait pas de fonction de conversion intermédiaire et donc pas de problème.

Ma solution actuelle est une fonction de conversion comme ci-dessous, qui est trop un IMO d'un piratage. Y a-t-il un moyen plus élégant d'atteindre le même résultat?

var gKeepStrings: array of AnsiString; 

{ Convert the given Unicode value S to ANSI and increase the ref. count 
    of it so that returned pointer stays valid } 
function ConvertToPAnsiChar(const S: string): PAnsiChar; 
var temp: AnsiString; 
begin 
    SetLength(gKeepStrings, Length(gKeepStrings) + 1); 
    temp := Utf8ToAnsi(UTF8Encode(S)); 
    gKeepStrings[High(gKeepStrings)] := temp; // keeps the resulting pointer valid 
              // by incresing the ref. count of temp. 
    Result := PAnsiChar(temp); 
end; 

Répondre

3

Une façon peut-être de résoudre le problème avant qu'il devient un problème, je veux dire d'adapter la classe de SomeObject pour maintenir une version ANSI encodée de SomeString (ANSISomeString?) Pour vous à côté du SomeString d'origine, en gardant la deux à l'étape dans un "setter" pour la propriété SomeString (en utilisant la même conversion ANSI UTF8> que vous faites déjà).

Dans les versions non-Unicode du compilateur, ANSISomeString doit simplement être une "copie" de la chaîne SomeString, qui ne sera bien sûr pas une copie, mais simplement un compte ref supplémentaire sur SomeString. Dans la version Unicode, il fait référence à un codage ANSI séparé avec la même "durée de vie" que la chaîne SomeString d'origine.

procedure TSomeObjectClass.SetSomeString(const aValue: String); 
begin 
    fSomeString := aValue; 

{$ifdef UNICODE} 
    fANSISomeString := Utf8ToAnsi(UTF8Encode(aValue)); 
{$else} 
    fANSISomeString := fSomeString; 
{$endif} 
end; 

Dans votre FillStructure ... fonction, il suffit de modifier votre code pour faire référence à la propriété ANSISomeString - c'est alors tout à fait indépendamment du fait que la compilation pour Unicode ou non.

function FillStructureForDLL: PStructureForSomeDLL; 
begin 
    New(Result); 
    result.pName := PANSIChar(SomeObject.ANSISomeString); 
end; 
+0

Bonne POV. Merci! –

2

Il existe au moins trois façons de procéder.

  1. Vous pouvez modifier la définition de classe de SomeObject utiliser un AnsiString au lieu d'une chaîne .
  2. Vous pouvez utiliser un système de conversion pour stocker des références, comme dans votre exemple.
  3. Vous pouvez initialiser result.pname avec GetMem et copiez le résultat de la conversion à result.pname^ avec Move. Rappelez-vous juste de FreeMem lorsque vous avez terminé.

Malheureusement, aucun d'entre eux n'est une solution parfaite. Alors jetez un oeil sur les options et décider lequel fonctionne le mieux pour vous.

+0

Vous ne pouvez pas initialiser 'pName' avec' New'; vous obtiendrez un pointeur vers un seul caractère. Utilisez 'GetMem' ou' StrNew' à la place. –

+0

... oups. Merci d'avoir attrapé ça. Fixé. –

+0

Idem. Pas de solution parfaite sauf de modifier les structures DLL. Merci pour les suggestions. –

2

Espérons que vous avez déjà du code dans votre application pour éliminer correctement hors de tous les enregistrements alloués dynamiquement que vous New() dans FillStructureForDLL(). Je considère ce code très douteux, mais supposons qu'il s'agit d'un code réduit pour démontrer le problème seulement. Quoi qu'il en soit, la DLL à laquelle vous passez l'instance d'enregistrement ne se soucie pas de la taille du morceau de mémoire, elle ne recevra qu'un pointeur de toute façon.Donc, vous êtes libre d'augmenter la taille du dossier pour faire place à la chaîne Pascal qui est maintenant une instance temporaire sur la pile dans la version Unicode:

type 
    PStructureForSomeCDLL = ^TStructureForSomeCDLL; 
    TStructureForSomeCDLL = record 
    pName: PAnsiChar; 
    // ... other parts of the record 
    pNameBuffer: string; 
    end; 

Et la fonction:

function FillStructureForDLL: PStructureForSomeDLL; 
begin 
    New(Result); 
    // there may be a bug here, can't test on the Mac... idea should be clear 
    Result.pNameBuffer := Utf8ToAnsi(UTF8Encode(SomeObject.SomeString)); 
    Result.pName := Result.pNameBuffer; 
end; 

BTW: Vous n'auriez même pas ce problème si l'enregistrement passé à la DLL était une variable de pile dans la procédure ou la fonction qui appelle la fonction DLL. Dans ce cas, les tampons de chaîne temporaires ne seront nécessaires dans la version Unicode que si plus d'un PAnsiChar doit être passé (les appels de conversion réutiliseraient sinon la chaîne temporaire). Envisagez de changer le code en conséquence.

Edit:

Vous écrivez dans un commentaire:

Ce serait la meilleure solution si la modification des structures de DLL sont une option. Êtes-vous sûr de ne pas pouvoir utiliser cette solution?

Etes-vous sûr de ne pas pouvoir utiliser cette solution? Le point est que du POV de la DLL la structure n'est pas du tout modifiée. Peut-être que je ne me suis pas fait clair, mais la DLL ne sera pas si une structure passée à elle est exactement ce que c'est déclaré être. Il sera passé un pointeur vers la structure, et ce pointeur doit pointer vers un bloc de mémoire au moins aussi grand que la structure, et doit avoir la même disposition de la mémoire. Cependant, il peut s'agir d'un bloc de mémoire plus grand que par rapport à la structure d'origine et contenant des données supplémentaires.

Ceci est effectivement utilisé dans beaucoup d'endroits dans l'API Windows. Avez-vous déjà demandé pourquoi il y a des structures dans l'API Windows qui contiennent en premier lieu une valeur ordinale donnant la taille de la structure? C'est la clé de l'évolution de l'API tout en préservant la rétrocompatibilité. Chaque fois que de nouvelles informations sont nécessaires pour que la fonction API fonctionne, il suffit d'ajouter à la structure existante et de déclarer une nouvelle version de la structure. Notez que la disposition de la mémoire des anciennes versions de la structure est conservée. Les anciens clients de la DLL peuvent toujours appeler la nouvelle fonction, qui utilisera le membre size de la structure pour déterminer la version de l'API appelée.

Dans votre cas, il n'existe aucune version différente de la structure en ce qui concerne la DLL. Cependant, vous êtes libre de déclarer qu'il est plus grand pour votre application qu'il ne l'est réellement, à condition que la disposition de la mémoire de la structure réelle soit préservée et que des données supplémentaires ne soient ajoutées que pour . Le seul cas où cela ne fonctionnerait pas est quand la dernière partie de la structure était un enregistrement avec une taille variable, un peu comme la structure de Windows BITMAP - un en-tête fixe et des données dynamiques. Cependant, votre enregistrement semble avoir une longueur fixe.

+0

Cela serait la meilleure solution si la modification des structures DLL était une option. Je vous remercie. –

-1

PChar (AnsiString (SomeObject.SomeString)) ne fonctionnerait-il pas?

+0

Non.Ceci: "Le problème dans la version Unicode est que la conversion de chaîne impliquée renvoie maintenant une nouvelle chaîne sur la pile et qu'elle est récupérée à la fin de l'appel FillStructureForDLL, laissant la DLL avec des données corrompues." s'appliquerait aussi bien. – mghie

Questions connexes