3

我想构建一个导出返回字符串的函数的 DLL。这个 DLL 应该可以与其他编程语言一起使用!!我已经找到了各种讨厌的解决方案/黑客,最好的一个是让我的函数返回 Pchar,然后调用包含在同一个 DLL 中的另一个函数(我们称之为 ReleaseMemory)来释放为 PChar 保留的内存。

无论如何,最近我发现了 FastShareMem 库。它说它可以做我想做的事,而无需调用 ReleaseMemory。另一方面,FastMM 似乎与 LONG 一样,因为 DLL 和应用程序都使用 FastMM 作为内存管理器。这立即扼杀了使用 FastMM 作为我的通用 DLL 的内存管理器的机会。对?

=====================

FastShareMem ( http://www.codexterity.com/fastsharemem.htm)、Delphi 7、Windows XP 32 位、Windows 7 64 位

4

4 回答 4

8

如果您返回 Delphi string,那么您的 DLL 将无法与其他编程语言一起使用,因为没有其他编程语言使用 Delphi 的字符串类型。如果类型不同,您如何分配内存都没有关系。如果您使用文本,请遵循 Windows API 的模型并使用普通的旧字符指针。

你找到的解决方案——返回一个指针,然后为你的 DLL 提供另一个函数来释放内存——不是一个 hack,而且一点也不讨厌。这是一个非常普通的解决方案,使用您的 DLL 的任何人在看到它时都会不屑一顾。API 函数使用类似的FormatMessage模型:它为您分配一个字符串,并指定它分配的字符串必须用LocalFree.

只要您是一致的并且您的 DLL 的使用者可以使用它,您使用什么内存管理器并不重要。一种方法是指定用于分配和释放字符串的 Windows API 函数,例如LocalAllocand LocalFree、 orSysAllocStringSysFreeString。另一种方法是根本不分配任何东西——如果调用者需要你返回一个字符串,调用者会提供缓冲区并告诉你它有多大。如果缓冲区太小,则返回所需的大小,以便调用者可以重新分配缓冲区并重新调用函数。有关这方面的示例,请参阅GetLongPathName

FastSharemem 对Delphi 的内存管理器如何工作提供了很长的解释,然后它说您可以通过在程序中简单地使用该单元来避免所有麻烦。但请记住我上面所说的:DLL 的使用者需要能够使用与您相同的内存管理器。如果你的 DLL 的使用者不是用 Delphi 编写的,那么它就不能使用 FastSharemem 单元。FastSharemem 在同构的 Delphi 环境中很好,但在混合环境中使用时,它与任何其他内存管理器一样会遇到所有相同的缺陷。

于 2010-08-11T05:42:37.260 回答
6

您正在混合两种不同的场景:

  1. 使用 Delphi DLL 的 Delphi 应用程序
  2. 任何使用 Delphi DLL 的应用程序

在第一种情况下,除非您混合使用 Delphi 版本或做一些奇怪的事情,否则内存管理器是相同的,编译器也是如此。因此,有多种方法可以共享内存管理器,然后编译器就能够正确处理分配/解除分配。这就是 FastMM 和 FastShareMem 都适用的场景——而且只有这个场景。

在第二种情况下,应用程序和 DLL 将使用不同的内存管理器,可能非常不同,而且通常没有办法共享一个。在这种情况下,最好的方法是永远不要返回在 DLL 中分配的 PChar,即使您提供了一个释放函数,因为您无法确定调用语言稍后会单独使用您的 PChar 做什么,以及如果调用者有机会在编译器/解释器之前调用正确的释放例程。COM 以您所说的方式工作,但它通过自己的内存管理器强制执行内存分配/释放,因此它是安全的。您不能使用普通的 DLL 强制执行它,因此它是不安全的。

最好的方法是让调用语言给你一个足够大的缓冲区,然后在那里写你的 PChar 。当然,您需要有一些方法来告诉调用者缓冲区的大小。这就是 Windows 本身的工作方式,他们做出这个选择是有充分理由的。

于 2010-08-11T16:16:43.033 回答
3

我是 FastSharemem 的作者,我想贡献我 2 美分的价值。Rob 是对的,FastSharemem 假设所有的模块都是用 Delphi 编写的。在不同语言的模块之间传递数据可能很棘手,尤其是对于像字符串这样的动态数据。

这就是为什么在处理复杂的数据结构时使用 Windows API 经常会很痛苦,这也是微软的 COM (OLE) 提供自己的内存管理功能和特殊类型的原因之一;目标是从不同源编译的模块之间的二进制兼容性。

因此,由于 Windows 之前已经这样做了,您可以使用 Windows 执行此操作的两种方式之一。任何一个:

1) 公开 C 风格的 API(PChars 等)并详细指定 API。您可以公开客户端需要调用的内存分配例程,或者让客户端进行分配。Windows API 在不同的时间同时执行这两种操作。客户端可能还需要一个 SDK 来方便地与您的模块通信,并记住统一使用 stdcall 调用约定。

或者,

2)使用COM类型和传入和传出数据。Delphi 具有出色的、几乎透明的 COM 支持。例如,对于字符串,您可以使用 COM 的 BSTR(Delphi 中的 WideString)。

希望这可以帮助。

于 2010-08-12T10:27:37.647 回答
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 内存管理器甚至不会尝试(它不是共享的)。

因此,如果您希望您的 DLL 是通用的,则不能依赖内存管理器相互通信。您的 DLL 可能与依赖于不同内存管理器的 EXE 或 DLL 一起使用,可能完全用不同的语言编写。只有当您控制项目的所有部分并且可以在任何地方明确设置一个相同的管理器时,才有可能拥有共享内存管理器。

于 2010-08-11T07:16:09.290 回答