Delphi DLL(在 XE 中)必须处理 TStringList(D2007、Ansi)
Delphi DLL (in XE) must handle TStringList (D2007, Ansi)
DLL 最初是在 D2007 中编写的,需要快速、紧急的 TStringList 调用(是的,这是其中一个“我肯定会后悔”;尽管由多个模块对 DLL 进行的所有调用,都是用 Delphi 代码做的,当 XE 出来的时候我错误地 presumed/hoped 向后兼容)。
所以现在我要将 DLL 移动到 XE5(因此是 Unicode)并且必须保持兼容性调用。最坏的情况是我只是简单地为 XE 编写一个新的 DLL,同时保留旧的作为遗留的,但我觉得没有理由 XE 不能 deconstruct/overrride 到一个 {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;所以是的,垃圾字符串)。我也隐约知道幕后的内存管理,尽管我只进行只读访问。但是实际的字符串指针本身应该是正确的(??),因此有没有办法强制我通过?
那么,不管我有没有那个权利,有什么解决办法吗?提前致谢。
您可能还没有意识到您的代码一直都是错误的。通常,不支持跨模块边界传递 Delphi 对象。只要您非常了解实现,只要您不调用虚拟方法,只要您不进行内存分配,只要您在两边使用相同的编译器,就可以让它工作,并且可能还有很多其他原因。要么使用运行时包(双方也需要相同的编译器),要么使用互操作安全类型(整数、浮点数、空终止字符数组、指针、记录和互操作安全类型数组等)
这里确实没有简单的解决方案。它一开始就不应该起作用,如果它起作用了,那你就很不走运了。不幸的是,更好的结果本来是失败,导致你正确地做这件事。
也许您能做的最好的事情就是制作一个适配器 DLL。架构是这样的,从下到上:
- 原始 Delphi 2007 DLL 在底部,伪造导出需要提供 D2007 字符串列表。
- 新适配器Delphi 2007 DLL 在中间。它调用虚假导出,并能够提供 D2007 字符串列表。适配器 DLL 公开了一个适当的接口,不需要 Delphi 对象跨模块边界传递。
- 顶部有新的 XE5 可执行文件。这会与适配器对话,但会使用有效的互操作类型。
David 和 Jerry 已经告诉过你应该做什么 - 重新编写 DLL 以做正确的事跨模块边界传递互操作安全数据。但是,要回答您的实际问题:
the actual string pointers themselves should be correct (??) and thus is there a way to coerce my way through?
So, regardless of whether I have any of that right, is there any solution?
您可以尝试以下方法。它 危险 ,但它 应该 工作,如果此时重写不是您的选择:
// 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;
DLL 最初是在 D2007 中编写的,需要快速、紧急的 TStringList 调用(是的,这是其中一个“我肯定会后悔”;尽管由多个模块对 DLL 进行的所有调用,都是用 Delphi 代码做的,当 XE 出来的时候我错误地 presumed/hoped 向后兼容)。
所以现在我要将 DLL 移动到 XE5(因此是 Unicode)并且必须保持兼容性调用。最坏的情况是我只是简单地为 XE 编写一个新的 DLL,同时保留旧的作为遗留的,但我觉得没有理由 XE 不能 deconstruct/overrride 到一个 {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;所以是的,垃圾字符串)。我也隐约知道幕后的内存管理,尽管我只进行只读访问。但是实际的字符串指针本身应该是正确的(??),因此有没有办法强制我通过?
那么,不管我有没有那个权利,有什么解决办法吗?提前致谢。
您可能还没有意识到您的代码一直都是错误的。通常,不支持跨模块边界传递 Delphi 对象。只要您非常了解实现,只要您不调用虚拟方法,只要您不进行内存分配,只要您在两边使用相同的编译器,就可以让它工作,并且可能还有很多其他原因。要么使用运行时包(双方也需要相同的编译器),要么使用互操作安全类型(整数、浮点数、空终止字符数组、指针、记录和互操作安全类型数组等)
这里确实没有简单的解决方案。它一开始就不应该起作用,如果它起作用了,那你就很不走运了。不幸的是,更好的结果本来是失败,导致你正确地做这件事。
也许您能做的最好的事情就是制作一个适配器 DLL。架构是这样的,从下到上:
- 原始 Delphi 2007 DLL 在底部,伪造导出需要提供 D2007 字符串列表。
- 新适配器Delphi 2007 DLL 在中间。它调用虚假导出,并能够提供 D2007 字符串列表。适配器 DLL 公开了一个适当的接口,不需要 Delphi 对象跨模块边界传递。
- 顶部有新的 XE5 可执行文件。这会与适配器对话,但会使用有效的互操作类型。
David 和 Jerry 已经告诉过你应该做什么 - 重新编写 DLL 以做正确的事跨模块边界传递互操作安全数据。但是,要回答您的实际问题:
the actual string pointers themselves should be correct (??) and thus is there a way to coerce my way through?
So, regardless of whether I have any of that right, is there any solution?
您可以尝试以下方法。它 危险 ,但它 应该 工作,如果此时重写不是您的选择:
// 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;