【问题标题】:Writing Delphi/FreePascal DLL to be called from gcc application编写要从 gcc 应用程序调用的 Delphi/FreePascal DLL
【发布时间】:2011-05-24 03:45:47
【问题描述】:

我需要将我的部分 Win32 Delphi 应用程序提供给另一家公司的 Linux gcc 程序。

吞吐量和部署要求使任何类型的远程服务都不合适,因此我正在考虑使用 FreePascal 构建 gcc 应用可以调用的 .SO(Linux 等效的 DLL)。

自从我使用 C/C++ 并且从未在 Linux 上使用过很长时间了,所以我有点不确定如何最好地构建 DLL/SO 接口以与 gcc 调用者兼容。

这是我的数据结构的表示

TFoo = record
  x, y : double;
  a : smallint;
  b : string;
end;

TBar = record
  a : double;
  b : longint;
  c : string;
end;

TFooBar = record
  foo : array of TFoo;
  bar : array of TBar;
end;

procedure Process(const inFooBar : TFooBar);

要通过 FreePascal 使此 Process 方法在外部可用。所以我需要如何修改这些声明?我在想一些类似的事情

TFoo = record
  x, y : double;
  a : smallint;
  b : PChar;
end;

TBar = record
  a : double;
  b : longint;
  c : PChar;
end;

TFooBar = record
  foo : ^TFoo;
  foo_count : longint;
  bar : ^TBar;
  bar_count : longint;
end;

procedure Process(const inFooBar : TFooBar);

我在正确的轨道上吗?我不必完全正确,其他公司的程序员很可能会纠正我的错误。我只是不想让他们看到我发给他们的东西时笑得太厉害。

【问题讨论】:

    标签: delphi dll gcc freepascal shared-libraries


    【解决方案1】:

    使用PAnsiChar 代替PChar,使用integer 代替longint/smallint(因为您使用对齐的记录,所以使用 smallint 字段没有任何好处)。

    定义一些指针类型,如PFoo 和PBar,这将比^TFoo 写得更好,例如

    如果你需要访问一些数组,你可以定义TFooArray和TBarArray,如下面的代码。

    不要忘记任何函数/过程的 cdecl 或 stdcall 调用约定。

    type
    TFoo = record
      x, y : double;
      a : integer;
      b : PAnsiChar;
    end;
    
    TBar = record
      a : double;
      b : integer;
      c : PAnsiChar;
    end;
    
    TFooArray = array[0..maxInt div sizeof(TFoo)-1] of TFoo;
    TBarArray = array[0..maxInt div sizeof(TBar)-1] of TBar;
    
    PBar = ^TBar;
    PFoo = ^TFoo;
    PFooArray = ^TFooArray;
    PBarArray = ^TBarArray;
    
    TFooBar = record
      foo : PFooArray;
      foo_count : integer;
      bar : PBarArray;
      bar_count : integer;
    end;
    
    procedure Process(const inFooBar : TFooBar); cdecl; external 'ProcessGCCLibrary.so';
    

    你的进程是只读的吗?

    如果C部分需要添加一些项目,你必须提供一些内存重新分配方法到外部库,至少映射reallocmem()。

    请注意,Delphi 动态数组可以很容易地映射到 C 兼容结构中,如下所示:

    type
      TFooDynArray: array of TFoo;
      TBarDynArray: array of TBar;
    
    procedure CallProcess(const aFoo: TFooDynArray; const aBar: TBarDynArray);
    var tmp: TFooBar;
    begin
      tmp.foo := pointer(aFoo);
      tmp.foo_count := length(aFoo);
      tmp.bar := pointer(aBar);
      tmp.bar_count := length(aBar);
      Process(tmp);
    end;
    

    这可以使您的 Delphi 代码更具可读性。如果您想使用类似TList 的方法对此类动态记录数组进行高级访问,请查看我们的TDynArray wrapper。

    由于AnsiString 类型映射了PAnsiChar,从二进制的角度来看,您甚至可以这样定义您的记录:

    type
    TFoo = record
      x, y : double;
      a : integer;
      b : AnsiString;
    end;
    
    TBar = record
      a : double;
      b : integer;
      c : AnsiString;
    end;
    

    当映射到 gcc 应用程序时,它将被读取为常规的*char。

    在这里使用AnsiString,您将不需要处理Delphi 代码中的内存分配。使用关联的动态数组,它可以使您的 Delphi 代码更易于维护。请注意,我们的 TDynArray 包装器将按预期处理记录中的嵌套 AnsiString,即使对于最高级别的方法(例如二进制序列化或散列)也是如此。

    【讨论】:

    • 感谢您提供如此详细的答案。不过,我将使用 FreePascal 而不是 Delphi 进行编译,您知道您对 AnsiString 和动态数组的建议是否也同样适用于 FreePascal?
    • @LachlanG 是的,FPC 和 Delphi 的动态数组和字符串实现是一样的。所以你可以使用我的建议。在 FPC 中,您只有 AnsiString(即 string=AnsiString 与早期的 Delphi 版本一样)。
    • @A. Bouchez:您对AnsiString 的使用是危险的。当然AnsiStrings 是空终止的,所以可以转换为PAnsiChars,除了它们还有一个负偏移量的引用计数 - 即,如果使用您推荐的结构将数据传递给 DLL 并且指针被解释在 Delphi 代码中作为真正的AnsiString,可能会造成很多麻烦。更安全地制作一个PAnsiChar,而且只有那个。 (另外,为什么是 ANSI?现在是 2011 年。将其设为 Unicode 字符串。)一个好的通用准则是托管类型(如字符串)不应跨越语言或 DLL/EXE 边界。
    • @David 看看 RTL 的内部结构。如果 dll 仅读取 *char 数组,则此用法根本不危险。正如我所写的,为了编写缓冲区,您需要一个自定义的reallocmem 从 C 调用。那是我的观点。这是非常安全的,甚至在调用 Windows API 时VCL 也会一直这样做。为什么要使用 Ansi?只是因为Linux下的FPC(OP的目标编译器)只处理这种字符串。而且 Linux 不是基于 UCS2,而是 UTF-32 AFAIK。您可以像大多数 Linux 库一样安全地使用 UTF-8 编码。
    • @A. Bouchez:“如果 DLL 只读取 *char 数组”是我的观点,也许我没有很好地表达。假设它传递一个要写入的结构,例如?我只能重复一遍,作为一般准则,像字符串这样的托管类型不应该跨越这种边界。 (它还避免了字符串引用计数,因此您可以想象在一侧释放字符串而另一侧的“字符串”不再有效的合理场景。)
    【解决方案2】:

    要确保记录打包/对齐/填充符合 GCC 的预期,请将 {$packrecords c} 添加到您的 Pascal 源中。请注意,此指令特定于 Free Pascal 编译器,Delphi 不支持它。

    【讨论】:

      【解决方案3】:

      看起来不错。想法:

      • 您的数组(声明为指针和计数)将完全手动管理 - 这对您来说没问题吗?

      • 你应该改变:

        procedure Process(const inFooBar : TFooBar);
        

        包含与 C 兼容的调用约定。我不确定 FreePascal 和 GCC 都支持什么,但像 cdecl 这样的东西应该可以正常工作。

      • 您还应该(为了安全起见)指定结构对齐/打包。

      • PChar 应该是明确的窄或宽(现在宽是正常的。)

      【讨论】:

        猜你喜欢
        • 2021-04-14
        • 1970-01-01
        • 2011-09-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多