【问题标题】:Is it safe to pass Delphi const string params across memory manager boundaries?跨内存管理器边界传递 Delphi const 字符串参数是否安全?
【发布时间】:2011-03-11 20:50:35
【问题描述】:

主题。我想使用字符串而不是 PChar,因为这样可以节省我很多的演员,但如果我只是这样做

procedure SomeExternalProc(s: string); external SOMEDLL_DLL;

然后在其他项目中使用非共享内存管理器实现它:

library SeparateDll;
procedure SomeExternalProc(s: string);
begin
  //a bla bla bla
  //code here code here
end;

我(正式)不保证 Delphi 不会出于任何原因决定更改字符串、修改其引用计数器、复制或唯一它,或其他任何事情。例如

var InternalString: string;

procedure SomeExternalProc(s: string);
begin
  InternalString := s;
end;

Delphi 递增 refcounter 并复制一个指针,就是这样。我希望 Delphi 复制数据。出于这个原因,将参数声明为“const”是否使其安全?如果没有,有没有办法做到这一点?将参数声明为 PChar 似乎不是一个解决方案,因为您每次都需要强制转换它:

procedure SomeExternalProc(s: Pchar); forward;
procedure LocalProc;
var local_s: string;
begin
  SomeExternalProc(local_s); //<<--- incompatible types: 'string' and 'PAnsiChar'
end;

【问题讨论】:

  • 为什么不想使用共享MM呢?只要您使用绑定到 Delphi 的字符串类型,它就不能是可用于其他语言的通用 DLL。那么为什么要避免分享MM呢?
  • String类型的兼容性很好,可以被其他语言解释为PChar。我不是在谈论返回字符串,当然,只是字符串参数。
  • 不要被字符串有一部分布局像 ASCIIZ 数组(或 PChar)这样的事实所迷惑。他们也有数据before 字符串,Delphi 也可以“触摸”该数据。传递一个 ASCIIZ 数组/PChar 假装它是一个字符串,并且您冒着在内存中的数据被 Delphi 代码更改之前的风险,认为它是一个字符串,因此那里有数据。
  • 谢谢,我知道。将 pchar 作为字符串传递是不安全的(抛开 Delphi 的自动转换),但将字符串作为 pchar 传递仍然是安全的。

标签: delphi string constants parameters


【解决方案1】:

只是添加一个事实:

Delphi 允许您简单地将 PChar 分配给字符串,因此在 DLL 端您不需要任何类型转换:

function MyDllFunction(_s: PChar): integer;
var
  s: string;
begin
  s := _s; // implicit conversion to string

  // now work with s instead of the _s parameter
end;

这也适用于将 PChar 作为参数传递给需要(按值)字符串的函数。

【讨论】:

    【解决方案2】:

    我建议使用其他内存管理器,例如 RecyclerMM 或 FastMM。它们不需要任何外部共享的 MM dll,并允许您安全地将字符串传递给 dll。作为奖励,您可能会在整个应用程序中获得不错的性能提升。

    FastMM 在 Delphi 2006 及更高版本中用作默认内存管理器。它也是搜索内存泄漏的好工具。

    【讨论】:

    • 谢谢,虽然我知道这一切。我没有使用,或者更准确地说,不是依赖共享 mm,因为我想要互操作性,而且我还认为依赖共享 mm 是不好的风格 - 会让你忘记正确的内存管理。
    • -1。这不是真的。 FastMM 确实需要您共享内存管理器才能安全地传递字符串。它带有一个名为 SimpleShareMem 的单元来完成此任务。如果不让两个源使用相同的堆,就无法安全地共享字符串管理。
    • 梅森,我在哪里说过这样的话?我写的是外部 DLL (borlandmm.dll)。是的,如果您希望在不使用 FastMM 的应用程序和使用 FastMM 的库之间共享内存,则需要 SimpleShareMem。但我说的是在应用程序和 dll 中都使用 FastMM。
    • 即使你对应用程序和 DLL 都使用 FastMM,如果你不把 SimpleShareMem 放进去,应用程序和 DLL 最终也会有自己的堆而不是共享。确实不需要外部共享的 MM DLL,但是你仍然需要在 EXE 和 DLL 中显式设置共享内存,否则它将无法工作。
    • AFAIK 完整的 FastMM 能够在没有 SimpleShareMem 的情况下共享 MM - 只需在 .inc 文件中启用正确的定义
    【解决方案3】:

    如果应用程序和 DLL 都是在同一个 Delphi 版本中编写的,则只需使用共享内存管理器(更多详细信息here)。

    如果一侧是用不同的语言编写的,那么除了使用 PChar 或 WideString 之外别无他法(WideString 由 COM 内存管理器管理)。

    或者你可以写一个包装函数:

    procedure MyExternalProc(const s: string);
    begin
      SomeExternalProc(PChar(s));
    end;
    

    【讨论】:

      【解决方案4】:

      这可能会起作用,只要您只使用在相同版本的 Delphi 中编译的代码中的 DLL。 string 的内部格式在不同版本之间会发生变化,您无法正式保证它不会再次发生变化。

      如果您想避免在使用它的任何地方强制转换,请尝试包装函数,如下所示:

      procedure SomeExternalProc(s: Pchar); external dllname;
      procedure MyExternalProc(s: string); inline;
      begin
        SomeExternalProc(PChar(local_s));
      end;
      

      然后在您的代码中,您调用MyExternalProc 而不是SomeExternalProc,每个人都很高兴。

      【讨论】:

      • 嘿,不到一分钟就击败了我 :)
      • 我已经这样做了一段时间了,但是编写了所有这些存根...(编辑:检查生成的代码是否为内联版本,毕竟没有地址引用)跨度>
      • 哦,这并不总是可能的,以接口为例 - 你不能扩展它们。
      • @himself:好点子。太糟糕了,你不能声明一个接口助手,就像你可以为类和记录一样。在这种情况下,它们真的会派上用场。
      • 现在找到了一个解决方案:声明两个具有相同 guid 和不同声明的接口:一个使用“const string”,另一个使用“pchar”。用 pchar 实现一个,通过字符串访问。肮脏的黑客,但总比没有好。
      猜你喜欢
      • 2011-08-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-09-11
      • 2021-03-16
      • 2019-12-03
      相关资源
      最近更新 更多