Delphi DLL(在XE中)必须处理TStringList(D2007,Ansi)

时间:2015-06-30 21:59:36

标签: delphi dll unicode ansi tstringlist

DLL最初是在D2007中编写的,需要一个快速,恐慌的TStringList调用(是的,它是其中一个“我肯定会后悔”;尽管所有对DLL的调用都是由几个模块完成的,都是由Delphi代码编写,我错误地假定/希望在XE出现时向后兼容。)

所以现在我将DLL移动到XE5(以及Unicode),并且必须保持调用以实现兼容性。最糟糕的情况是我只是为XE编写一个新的DLL,同时保留旧版本的旧版本,但我觉得XE无法解析/覆盖到{ANSI} TStringList参数。但是我的Delphi幕后知识并不健全,而且几次尝试都没有成功。

这是DLL调用 - 它需要一个文件路径列表,在这个精简代码中,只需将每个字符串添加到内部列表(即所有DLL都使用参数,一个只读参考) :

function ViewFileList ( lstPaths: TStringList): Integer; Export; Stdcall;
begin
      for iCount := 0 to lstPaths.Count - 1 do
         lstInternal.Add(lstPaths.strings[iCount]);
end;

我发现当我在XE5中编译它时,lstPaths.Count是正确的,因此基本结构对齐。但是字符串是垃圾。似乎不匹配是双重的:(a)字符串内容自然被解释为每个字符两个字节; (b)没有元素大小(位置-10)和代码页(位置-12;所以是,垃圾字符串)。我也隐约知道幕后内存管理,尽管我只做只读访问。但是实际的字符串指针本身应该是正确的(??),因此有没有办法强制通过我的方式?

因此,无论我是否拥有任何该权利,是否有任何解决方案?提前致谢。

2 个答案:

答案 0 :(得分:2)

你可能还没有意识到你的代码一直都是错的。通常,不支持跨模块边界传递Delphi对象。只要你不调用虚方法,只要你不进行内存分配,只要你在双方使用相同的编译器,就可以使它工作得很好。可能有很多其他原因。使用运行时包(两侧也需要相同的编译器),或使用互操作安全类型(整数,浮点数,空终止字符数组,指针,记录和互操作安全类型数组等)。

这里真的没有简单的解决方案。它应该永远不会起作用,如果它确实那么你就是非常不走运。运气不好,因为更好的结果会导致你做得不好而失败。

也许您可以做的最好的事情是制作适配器DLL。架构从下到上依次为:

  • 底部的原始Delphi 2007 DLL,伪造导出需要提供D2007字符串列表。
  • 新的适配器Delphi 2007 DLL在中间。它调用伪造导出,并能够提供D2007字符串列表。适配器DLL公开了一个不需要Delphi对象通过模块边界的正确接口。
  • 顶部的新XE5可执行文件。这与适配器对话,但使用有效的互操作类型。

答案 1 :(得分:1)

David和Jerry已经告诉过你应该做什么 - 在跨越模块边界传递互操作数据时,重新编写DLL到做正确的事情 。但是,要回答您的实际问题:

  

实际的字符串指针本身应该是正确的(??),因此有没有办法强制通过我的方式?

     

因此,无论我是否拥有任何该权利,是否有任何解决方案?

您可以尝试以下操作。这是危险的,但是 应该有效,如果此时重写不是您的选择:

// the ASSUMPTION here is that the caller has been compiled in D2007 or earlier,
// and thus is passing an AnsiString-based TStringList object.  When this DLL is
// compiled in Delphi 2009 or later, TStringList is UnicodeString-based instead,
// so we have to re-interpret the data a little.
//
// The basic structure of TStringList itself should be the same, just the string
// content is different.  For backwards compatibility, the refcnt and length
// fields of the StrRec record found in every AnsiString/UnicodeString payload
// are still at the same offsets. Delphi 2009 added some new fields, but we can
// ignore those here.
//
// Of course, XE is the version that removed the RTL support code for the {$STRINGCHECKS}
// compiler directive, which handled all of these details in Delphi 2009 and 2010
// when users were first migrating to Unicode.  But in XE, we'll have to deal with
// it manually.
//
// These assumptions may change in future versions, but lets deal with that if/when
// the time comes...

function ViewFileList ( lstPaths: TStringList): Integer; Export; Stdcall;
{$IFDEF UNICODE}
var
  tmp: AnsiString;
{$ENDIF}
begin
  for iCount := 0 to lstPaths.Count - 1 do
  begin
    {$IFDEF UNICODE}

    // the DLL is being compiled in Delphi 2009 or later...
    //
    // the Length(String) function simply returns the value of the string's
    // StrRec.length field, which fortunately is in the same location in
    // both pre-2009 AnsiString and 2009+ AnsiString/UnicodeString, and in
    // this case will reflect the number of AnsiChar elements in the source
    // AnsiString.  We cannot simply typecast a "UnicodeString" directly to
    // a PAnsiChar, nor can we typecast a PWideChar to a PAnsiChar, but we
    // can typecast a string to a Pointer first and then cast that to a
    // PAnsiChar.  This code is assuming that it can safely get a pointer to
    // the source AnsiString's underlying character data to make a local
    // copy of it that can then be added to the internal list normally.
    //
    // Where this MIGHT fail is if the source AnsiString contains a reference
    // to a string literal (StrRec.refcnt=-1) for its character data, in
    // which case the RTL will try to copy the character data when assigning
    // the source string to a variable, such as the one the compiler is
    // likely to generate for itself to receive the TStringList.Strings[]
    // property value before it can be casted to a Pointer.  If that happens,
    // this is likely to crash when the RTL tries to copy too many bytes from
    // the source AnsiString!  You can use the StringRefCount() function to
    // detect that condition and do something else, if needed.
    //
    // But, if the source AnsiString is a normal allocated string (the usual
    // case), then this should work OK.  Even with the compiler-generated
    // variable in play, the compiler should simply bump the reference count
    // of the source AnsiString, without affecting the underlying character
    // data, just long enough for this code to copy the data and release the
    // reference count...
    //
    SetString(tmp, PAnsiChar(Pointer(lstPaths.strings[iCount])), Length(lstPaths.strings[iCount]) * SizeOf(AnsiChar));
    lstInternal.Add(tmp);

    {$ELSE}

    // the DLL is being compiled in Delphi 2007 or earlier, so just add the
    // source AnsiString as-is and let the RTL do its work normally...
    //
    lstInternal.Add(lstPaths.strings[iCount]);

    {$ENDIF}
  end;
end;
相关问题