AnsiString 从 Delphi 2009 应用程序中的 Delphi 2007 DLL 返回值
我有一个用 D2007 编译的 DLL,它具有返回 AnsiStrings 的函数。
我的应用程序是在 D2009 编译的。 当它调用 AnsiString 函数时,它会返回垃圾。
我创建了一个小测试app/dll来实验,发现如果app和dll都使用相同版本的Delphi(2007或2009)编译,则没有问题。 但是当一个是 2009 年编译的,另一个是 2007 年编译的时,我得到的是垃圾。
我尝试在这两个项目中包含最新版本的 FastMM,但即使这样,2009 年的应用程序也无法从 2007 年的 dll 中读取 AnsiStrings。
你知道这里出了什么问题吗? 有办法解决这个问题吗?
I have a DLL compiled with D2007 that has functions that return AnsiStrings.
My application is compiled in D2009. When it calls the AnsiString functions, it gets back garbage.
I created a little test app/dll to experiment and discovered that if both app and dll are compiled with the same version of Delphi (either 2007 or 2009), there is no problem. But when one is compiled in 2009 and the other 2007, I get garbage.
I've tried including the latest version of FastMM in both projects, but even then the 2009 app cannot read AnsiStrings from the 2007 dll.
Any ideas of what is going wrong here? Is there a way to work around this?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
AnsiStrings 的内部结构在 Delphi 2007 和 Delphi 2009 之间发生了变化。(不要沮丧;这种可能性从第一天起就存在。)Delphi 2009 字符串维护一个数字,指示其数据所在的代码页。
我建议您这样做地球上所有其他 DLL 都会做的事情,并传递函数可以填充的字符缓冲区。 调用者应该传递一个缓冲区指针和一个指示缓冲区大小的数字。 (确保您清楚是以字节为单位还是以字符为单位测量大小。)DLL 函数填充缓冲区,写入的内容不超过给定的大小,并对终止空字符进行计数。
如果调用者不知道缓冲区应该有多少字节,那么您有两个选择:
使 DLL 在输入缓冲区指针为空时进行特殊操作。 在这种情况下,让它返回所需的大小,以便调用者可以分配那么多空间并再次调用该函数。
让 DLL 为自己分配空间,并使用预定的方法供调用者稍后释放缓冲区。 DLL 可以导出一个函数来释放它已分配的缓冲区,也可以指定一些相互可用的 API 函数供调用者使用,例如
GlobalFree
。 您的 DLL 必须使用相应的分配 API,例如 GlobalAlloc。 (不要使用 Delphi 的内置内存分配函数,如GetMem
或New
;不能保证调用者的内存管理器知道如何调用Free< /code> 或
Dispose
,即使它是用相同的语言编写的,即使它是用相同的 Delphi 版本编写的。)此外,编写只能由单一语言使用的 DLL 是自私的。 按照与 Windows API 相同的风格编写 DLL,这样就不会出错。
The internal structure of AnsiStrings changed between Delphi 2007 and Delphi 2009. (Don't get upset; that possibility has been present since day 1.) A Delphi 2009 string maintains a number indicating what code page its data is in.
I recommend you do what every other DLL on Earth does and pass character buffers that the function can fill. The caller should pass a buffer pointer and a number indicating the size of the buffer. (Make sure you're clear about whether you're measuring the size in bytes or characters.) The DLL function fills the buffer, writing no more than the given size, counting the terminating null character.
If the caller doesn't know how many bytes the buffer should be, then you have two options:
Make the DLL behave specially when the input buffer pointer is null. In that case, have it return the required size so that the caller can allocate that much space and call the function a second time.
Have the DLL allocate space for itself, with a predetermined method available for the caller to free the buffer later. The DLL can either export a function for freeing buffers that it has allocated, or you can specify some mutually available API function for the caller to use, such as
GlobalFree
. Your DLL must use the corresponding allocation API, such asGlobalAlloc
. (Don't use Delphi's built-in memory-allocation functions likeGetMem
orNew
; there's no guarantee that the caller's memory manager will know how to callFree
orDispose
, even if it's written in the same language, even if it's written with the same Delphi version.)Besides, it's selfish to write a DLL that can only be used by a single language. Write your DLLs in the same style as the Windows API, and you can't go wrong.
好吧,所以还没有尝试过,所以在这个问题上加上了一个很大的免责声明。
在帮助查看器中,查看主题(RAD Stufio 中的 Unicode)ms-help://embarcadero.rs2009/devcommon/unicodeinide_xml.html
将 Delphi 2007 字符串返回到 Delphi 2009,您应该遇到两个问题。
首先是 Rob 提到的代码页。 您可以通过声明另一个 AnsiString 并在新 AnsiString 上调用 StringCodePage 来设置此值。 然后通过调用 SetCodePage 将其分配给旧的 AnsiString。 这应该可行,但如果不行,仍然还有希望。
第二个问题是元素大小,这将是完全疯狂的事情。 它应该是 1,所以将其设为 1。这里的问题是没有可以依赖的 SetElementSize 函数。
试试这个:
应该可以了!
现在,如果 StringCodePage/SetCodePage 不起作用,您可以执行与上面相同的操作,更改我们获取地址的行以扣除 12,而不是 10。
它上面写满了 hack,这就是我喜欢它的原因。
您最终需要移植这些 DLL,但这使得移植更易于管理。
最后一句话 - 根据您返回 AnsiString 的方式(函数结果、输出参数等),您可能需要首先将字符串分配给不同的 AnsiString 变量,以确保不会出现内存被覆盖的问题。
OK, so haven't tried it, so a big fat disclaimer slapped on this one.
In the help viewer, look at the topic (Unicode in RAD Stufio) ms-help://embarcadero.rs2009/devcommon/unicodeinide_xml.html
Returning the Delphi 2007 string to Delphi 2009, you should get two problems.
First, the code page mentioned by Rob. You can set this by declaring another AnsiString and calling StringCodePage on the new AnsiString. Then assign that to the old AnsiString by calling SetCodePage. That should work, but if it doesn't there is hope still.
The second problem is the element size which will be something completely mad. It should be 1, so make it 1. The issue here is that there is no SetElementSize function to lean on.
Try this:
That should do it!
Now if the StringCodePage/SetCodePage thing didn't work, you can do the same as above, changing the line where we get the address to deduct 12, instead of 10.
It has hack scribbled all over it, that's why I love it.
You are going to need to port those DLLs eventually, but this makes the port more manageable.
One final word - depending on how you return the AnsiString (function result, output parameter, etc) you may need to first assign the string to a different AnsiString variable just to make sure there is no trouble with memory being overwritten.
您可能只需要将 DLL 转换为 2009。根据 Embarcadero 的说法,转换为 2009 非常“简单”,并且不会花费您任何时间。
You'll likely just need to convert the DLL to 2009. According to Embarcadero, the conversion to 2009 is 'easy' and should take you no time at all.
您的 DLL 一开始就不应该返回 AnsiString 值。 首先,正确工作的唯一方法是 DLL 和 EXE 都使用 ShareMem 单元编译,即使如此,也只有它们使用相同的 Delphi 版本编译。 AFAIK,D2007 的内存管理器与 D2009 的内存管理器(或任何其他跨版本使用的内存管理器)不兼容。
Your DLL should not be returning AnsiString values to begin with. The only way that would work correctly in the first place is if both DLL and EXE were compiled with the ShareMem unit, and even then only if they are compiled with the same Delphi version. D2007's memory manager is not compatible with D2009's memory manager (or any other cross-version use of memory managers), AFAIK.
我同意 Rob 和 Remy 的观点:普通 Dll 应该返回 PAnsiChar
AnsiStrings 的。
如果使用 D2009 编译 DLL 工作正常,为什么不停止编译它
用D2007开始编译然后用D2009一劳永逸?
I agree with Rob and Remy here: common Dlls should return PAnsiChar instead
of AnsiStrings.
If the DLL works OK compiled with D2009, why simply doesn't stop compiling it
with D2007 and start compiling it with D2009 once and for all?
就像这里的快速解决方案一样:如果字符串中从 dll 传回的实际数据不超过 255 个字符,您可以更改 in-dll 和接口声明以使用 ShortString,无论 2007/2009 版本如何,它都可以工作。 由于您在 2007 年就已经使用 AnsiString,没有代码页标识符,因此 unicode 不会给您带来任何麻烦。
如果你这样做,你需要做的就是更改如下声明:(
以及分别在dll中:
function MyStringReturningFunction : ShortString;
)当然输入/输出参数也是如此:
应该比改变很多代码。 但要小心,正如我所说,您的数据不得超过 255 个字符,因为这是 ShortString 可以容纳的最大大小。
Just as a quick solution here: if your actual data that you pass back from dll in the string does not exceed 255 chars, you can change both the in-dll and interface declerations to use ShortString, which will work regardless of 2007/2009 version. Since you're using AnsiString already on 2007 without a codepage identifier, unicode wont give you any trouble.
if you go this way, all you need to do is change the declarations like:
(and in dll:
function MyStringReturningFunction : ShortString;
respectively)Same goes for input/output parameters of course:
Should be easier than changing a lot of code. But be careful, as I said, your data must not exceed 255 characters since that is the maximum size a ShortString can hold.