代码之家  ›  专栏  ›  技术社区  ›  utku_karatas

处理这个字符串问题的优雅方法。(Unicode PansiString问题)

  •  2
  • utku_karatas  · 技术社区  · 15 年前

    考虑以下情况:

    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...
    

    Unicode版本中的问题是所涉及的字符串转换现在返回堆栈上的一个新字符串,该字符串在FillStructureFordll调用结束时被回收,从而使DLL中保留损坏的数据。在旧的D7代码中,没有中间转换函数,因此没有问题。

    我目前的解决方案是像下面这样的一个转换器函数,这是一个太多的黑客。有没有更优雅的方法来达到同样的结果?

    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;
    
    4 回复  |  直到 15 年前
        1
  •  3
  •   Deltics    15 年前

    一种方法可能是先解决问题 变成 一个问题,我的意思是调整someObject的类以维护someString(ansisomeString?)的ANSI编码版本。对于您来说,除了原始somestring之外,还可以将这两个值保持在somestring属性的“setter”中(使用您已经进行的相同的utf8>ansi转换)。

    在非Unicode版本的编译器中,ansisomeString只不过是somestring字符串的“副本”,当然这不是一个副本,只是somestring上的附加引用计数。在Unicode版本中,它引用了与原始somestring具有相同“生存期”的单独的ANSI编码。

    procedure TSomeObjectClass.SetSomeString(const aValue: String);
    begin
      fSomeString := aValue;
    
    {$ifdef UNICODE}
      fANSISomeString := Utf8ToAnsi(UTF8Encode(aValue));
    {$else}
      fANSISomeString := fSomeString;
    {$endif}
    end;
    

    在你的填充结构中…函数,只需将代码更改为引用ansisomeString属性-这完全独立于是否为Unicode编译。

    function FillStructureForDLL: PStructureForSomeDLL;
    begin
      New(Result);
      result.pName := PANSIChar(SomeObject.ANSISomeString);
    end;
    
        2
  •  2
  •   Mason Wheeler    15 年前

    至少有三种方法可以做到这一点。

    1. 你可以改变某个对象的类 使用ansisting的定义 而不是 一串 .
    2. 你可以 使用转换系统保持 引用,如您的示例中所示。
    3. 您可以初始化 result.pname 使用getmem并复制 转换为 result.pname^ 具有 Move .别忘了免费的 完成后。

    不幸的是,它们都不是完美的解决方案。所以看看这些选项,决定哪一个最适合你。

        3
  •  2
  •   mghie    15 年前

    希望您的应用程序中已经有了代码,可以正确地处理所有动态分配的记录。 New() 在里面 FillStructureForDLL() . 我认为这段代码非常可疑,但让我们假设这是一段简化的代码,仅用于演示问题。不管怎样,传递记录实例的dll不关心内存块有多大,它只会得到指向它的指针。因此,您可以自由地增加记录的大小,以便为pascal字符串(现在是unicode版本中堆栈上的临时实例)提供位置:

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

    功能:

    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;
    

    顺便说一句:如果传递给dll的记录是调用dll函数的过程或函数中的堆栈变量,您甚至不会遇到这个问题。在这种情况下,只有在多个unicode版本中才需要临时字符串缓冲区 PAnsiChar 必须传递(否则转换调用将重用临时字符串)。考虑相应地更改代码。

    编辑:

    你在评论中写道:

    如果可以选择修改dll结构,这将是最好的解决方案。

    您确定不能使用此解决方案吗?重点是,从DLL的POV来看,结构根本没有被修改。也许我没说清楚,但DLL会 关心传递给它的结构是否正是它的 宣布 成为。它将被传递一个指向该结构的指针,并且该指针需要指向一个至少与该结构一样大的内存块,并且需要具有相同的内存布局。但是,它可以是一个内存块, 更大的 而不是原始结构,并包含附加数据。

    这实际上在WindowsAPI中的很多地方都使用。你有没有想过为什么WindowsAPI中的结构首先包含一个给定结构大小的序数值?它是API发展的关键,同时保持向后兼容性。每当API函数需要新的信息工作时,它只是 附加的 并声明该结构的新版本。请注意,结构的旧版本的内存布局是保留的。DLL的旧客户端仍然可以调用新函数,该函数将使用结构的大小成员来确定调用哪个API版本。

    在您的情况下,就DLL而言,不存在结构的不同版本。但是,如果保留了实际结构的内存布局,并且只保留了额外的数据,那么您可以自由地为应用程序声明比实际大小更大的数据。 附加的 .唯一不起作用的情况是,结构的最后一部分是一个大小不同的记录,类似于窗口。 BITMAP 结构-固定的头和动态数据。但是,您的记录似乎有固定的长度。

        4
  •  -1
  •   Remko    15 年前

    pchar(ansisting(someobject.somestring))不会工作吗?