我想构建一个导出返回字符串的函数的DLL。这个DLL应该与其他编程语言一起使用!! 我已经找到了各种令人讨厌的解决方案/黑客,最好的方法是让我的函数返回Pchar,然后调用同一个DLL中包含的另一个函数(让我们称之为ReleaseMemory)来释放为PChar保留的内存。
无论如何,最近我发现了FastShareMem库。它说它可以完全按照我想要的方式完成调用ReleaseMemory。另一方面,FastMM似乎做同样的AS LONG,因为DLL和应用程序都使用FastMM作为内存管理器。这会立即杀死使用FastMM作为我的通用DLL的内存管理器的机会。正确?
====================
FastShareMem(http://www.codexterity.com/fastsharemem.htm),Delphi 7,Windows XP 32位,Windows 7 64位
答案 0 :(得分:8)
如果您返回Delphi string
,那么您的DLL将无法与其他编程语言一起使用,因为没有其他编程语言使用Delphi的字符串类型。如果类型不相同,则分配内存的方式无关紧要。如果您正在处理文本,请遵循Windows API的模型并使用普通的旧字符指针。
你找到的解决方案 - 返回一个指针,然后为你的DLL提供另一个函数来释放内存 - 不是一个黑客,并不是很讨厌。这是一个非常普通的解决方案,当他们看到它时,没有人会使用你的DLL。 FormatMessage
API函数使用类似的模型:它为您分配一个字符串,并指定它分配的字符串必须使用LocalFree
释放。
只要你保持一致并且你的DLL的消费者可以使用它,你使用什么内存管理器并不重要。一种方法是指定用于分配和释放字符串的Windows API函数,例如LocalAlloc
和LocalFree
,或SysAllocString
和SysFreeString
。另一种方法是永远不分配任何东西 - 如果调用者需要你返回一个字符串,调用者提供缓冲区并告诉你它有多大。如果缓冲区太小,则返回所需的大小,以便调用者可以重新分配缓冲区并重新调用该函数。有关示例,请参阅GetLongPathName
。
FastSharemem对how Delphi's memory manager works提供了很长的解释,然后它表示只需在程序中使用该单元即可避免所有麻烦。但请记住我上面所说的内容:DLL的使用者需要能够使用您使用的相同内存管理器。当您的DLL的使用者不是用Delphi编写时,它就不能使用FastSharemem单元。 FastSharemem在一个同质的Delphi环境中运行良好,但在混合环境中使用时,它会遇到与任何其他内存管理器相同的陷阱。
答案 1 :(得分:6)
您正在混合两种不同的方案:
在第一种情况下,除非你混合Delphi版本或做一些奇怪的事情,内存管理器是相同的,编译器也是如此。因此,有共享内存管理器的方法,然后编译器能够正确处理分配/解除分配。这就是FastMM和FastShareMem都可以工作的情况 - 只有这一个。
在第二种情况下,应用程序和DLL将使用不同的内存管理器,可能是非常不同的内存管理器,通常无法共享一个。在这种情况下,最好的方法是永远不要返回在DLL内部分配的PChar,即使你提供了一个释放函数,因为你不能确定调用语言将在以后与你的PChar一起做什么,如果调用者有机会在编译器/解释器之前调用正确的释放例程。 COM在你说的方式上有所作为,但它通过自己的内存管理器强制执行内存分配/释放,因此它是安全的。你不能用普通的DLL强制执行它,因此它不安全。
最好的方法是让调用语言通过一个足够大的缓冲区,然后在那里写下你的PChar。当然,您需要有一些方法告诉调用者缓冲区的大小。这就是Windows本身的工作方式,并且有很好的理由让他们做出这样的选择。
答案 2 :(得分:3)
我是FastSharemem的作者,我想贡献我2美分的价值。 Rob是对的,FastSharemem假设这些模块都是用Delphi编写的。在不同语言的模块之间传递数据可能很棘手,特别是对于像字符串这样的动态数据。
这就是为什么在处理复杂的数据结构时,Windows API通常很难处理,这也是Microsoft的COM(OLE)提供自己的内存管理功能和特殊类型的原因之一。目标是从不同来源编译的模块之间的二进制兼容性。
因此,由于Windows之前已经完成过,您可以使用Windows执行此操作的两种方法之一。之一:
1)公开C风格的API(PChars等)并详细说明API。您可以公开客户端需要调用的内存分配例程,也可以让客户端进行分配。 Windows API在不同时间执行。客户端可能还需要一个SDK来方便地与您的模块通信,并记住统一使用stdcall调用约定。
或者,
2)使用COM类型并传入和传出数据。 Delphi具有出色的,几乎透明的COM支持。例如,对于字符串,您可以使用COM的BSTR(Delphi中的WideString)。
希望这有帮助。
答案 3 :(得分:2)
基本上是这样的。单独编译的每一段代码(DLL或EXE)都包含自己的代码,该代码从系统分配内存并对其进行管理,称为内存管理器。简单来说,当初始化那段编码时,它会从系统中分配一大块内存。稍后,当它执行GetMem或分配字符串,数组等时,内存管理器会标记所使用的大块的部分内容。当你FreeMem / deallocate它们时,它们被标记为未使用。
现在假设你有EXE和DLL,都有自己的内存管理器。 EXE调用DLL程序,DLL分配一个字符串(PChar),从而标记它所使用的宏存储器块的一部分。然后它返回指向EXE的指针,EXE使用它然后决定释放。 EXE给出了它自己的内存管理器的指针,并要求释放它,但它甚至不是来自EXE的内存块! EXE的内存管理器不知道如何“释放”别人的记忆。
这就是为什么你需要调用DllReleaseString(),从而将借用的内存指针返回给DLL并让DLL自己的内部内存管理器释放它。
现在,共享内存管理器的作用是,它们相互连接。您的DLL中的内存管理器和EXE中的内存管理器知道如何相互通信,当您将DLL的内存指针指向EXE的内存管理器时,它会理解它来自DLL并让DLL内存管理器释放它。当然,只有当两个DLL和EXE内存管理器都是用相同的内存管理器代码构建时才有可能(否则它们就不会相互识别!)。如果您的DLL内存管理器是共享的,并且您的EXE内存管理器是其他内容,DLL内存管理器将无法“请求”EXE释放内存,并且EXE内存管理器甚至不会尝试(它不共享)。 / p>
因此,如果您希望您的DLL是通用的,则不能依赖内存管理器相互通信。您的DLL可能与依赖于不同内存管理器的EXE或DLL一起使用,可能完全用不同的语言编写。只有当您控制项目的所有部分并且可以在任何地方明确设置同一个管理器时,才能共享内存管理器。