【问题标题】:Delphi DLL class exceptionDelphi DLL 类异常
【发布时间】:2017-09-18 23:54:39
【问题描述】:

我需要编写一个 DLL,但这是我第一次(总是有一个),我找到了阅读文档的解决方案。我最终得到了这段代码:

library DLLFrazioni;

uses
  System.SysUtils,
  System.Classes,
  Fractions in 'Fractions.pas';

{$R *.res}

function getFraction(a: integer; b: integer): PChar; stdcall; overload;
var f: TFraction;
begin

 f := TFraction.Create(a, b);
 try
  Result := PChar(f.getFraction);
 except
  Result := PChar('NaN');
 end;

end;

function getFraction(a: PChar): PChar; stdcall; overload;
var f: TFraction;
begin

 f := TFraction.Create(a);
 try
  Result := PChar(f.getFraction);
 except
  Result := PChar('NaN');
 end;

end;

exports
 getFraction(a: integer; b: integer),
 getFraction(a: Pchar);

begin
end.

在 Fraction.pas 中有一个名为 TFraction 的类,它有这个实现(如果需要):

type
 TFraction = class
  private
   number: double;
   num, den: integer;
   fraction: string;
   function hcf(x: integer; y: integer): integer;
  public
   //input num+den -> result is a fraction num/den
   constructor Create(numerator: integer; denominator: integer); overload;
   //input string 'num/den' -> result is a reduced num/den
   constructor Create(value: PChar); overload;
   function getFraction: string;
 end;

这里的一切都很简单。


我必须能够用 Delphi 和 C++ (Visual Studio) 加载这个 dll,但我怀疑我没有用 google 解决。如您所见,我已经声明了另一个包含该类的单元,因此我可以将两者分开。

我在 delphi DLL 中像往常一样使用 stdcall。我有以下问题:

  1. 我必须创建一个对象(f: TFraction),因为我需要从getFraction 获取返回结果。我必须用通常的 try-finally 语句来包围它吗?我认为 try-except 更适合,因为我想在运行时避免异常。
  2. 如果我删除了 try-except 当然会发生异常。在这种情况下,当我从我的 Delphi/C++ 程序中调用该函数时,我可以处理它。但这安全吗?我可以允许 dll 引发异常吗?

【问题讨论】:

    标签: delphi dll


    【解决方案1】:

    您的代码可以编译并运行,但正如 Rob 所说,您应该用结果填充缓冲区。通常,当您处理 DLL(如您的情况)时,它们必须使用不同的语言加载,因此最好返回简单类型,如整数或双精度。

    这段代码会给你一个想法:

    //STATUS: 0 = ok | 1 = error
    function getFraction(a, b: integer; Buffer: PChar): integer; stdcall;
    var f: TFraction;
        l: PChar;
    begin
    
     f := TFraction.Create(a, b);
     try
    
      l := PChar(f.getFraction);
      try
       StrLCopy(Buffer, l, SizeOf(l));
       Result := 0; //ok
      except
       //something to report an error
       Result := 1;
      end;
    
     finally
      f.Free;
     end;
    
    end; 
    

    这里我返回一个表示操作成功的整数(但也可以是一个布尔值)。在这种情况下,您会发现拥有try ... except 很有用。

    结果存储在缓冲区中,我使用了合适的函数StrLCopy。这里我使用了SizeOf 运算符来获取MaxLen 的值。取而代之的是,您可以在函数中使用一个名为 bufLen 的参数。

    function getFraction(a, b: integer; Buffer: PChar; bufLen: integer): integer; stdcall;
    var f: TFraction;
        l: PChar;
    begin
    
     f := TFraction.Create(a, b);
     try
    
      l := PChar(f.getFraction);
      try
       StrLCopy(Buffer, l, bufLen);
       Result := 0; //ok
      except
       //something to report an error
       Result := 1;
      end;
    
     finally
      f.Free;
     end;
    
    end;
    

    在第二个版本中,您拥有更多控制权,并且可以确定缓冲区的尺寸。请注意,StrLCopy 将仅使用 bufLen 指示的空间量。如果l: PChar 的大小大于bufLen,超出的部分将不会被复制。在主程序中可以使用如下函数:

      SetLength(A, 10);
      k := getFraction(10,25, PChar(A));
    
      if k = 0 then
       Writeln(a)
      else
       Writeln('fail');
    

    此代码是控制台应用程序,A 是string。您必须声明一个字符串变量并将其转换为 PChar,因为SetLength 接受通过引用(var 关键字)作为第一个参数传递的字符串或动态数组。

    【讨论】:

      【解决方案2】:

      except 块的用途与 finally 块完全不同。这绝不是选择一个而不是另一个的问题。使用满足需求的任何一个。如果两者都需要,请同时使用。 except 块用于处理错误finally 块用于保护资源

      您对except 块的使用对于处理getFraction 失败的情况是正确的。

      您应该包含一个finally 块来保护您分配的资源,即TFraction 对象。你现在根本没有释放对象,所以你有内存泄漏。

      不允许异常转义 DLL 函数。你不能假设调用者知道如何处理抛出的 Delphi 对象。

      编写 DLL 是一个对您有编写 C 和使用 Windows API 的经验非常有帮助的案例。如果您编写 DLL 以遵循您在 Windows API 中看到的相同模式,那么您就有了良好的基础。您会注意到 Windows API 函数从不引发异常。它们总是返回一个状态值,可能带有错误代码。


      您的代码中还有其他问题。特别是,您将返回一个指向字符串的指针,该字符串会在您的 DLL 函数终止后立即释放,因此该指针已过时。同样,遵循 Windows API 的模型是有帮助的。 API 几乎从不返回一个字符串。相反,API 函数接收一个缓冲区指针和一个长度,然后填充调用者提供的缓冲区。 返回字符串的 API 通常会使用文档化的 API 分配内存,然后指定调用者稍后应该使用什么内存管理函数来释放内存。

      在您的情况下,您正在返回一个指向 无法管理的内存的指针。编译器为你管理它,编译器看不到你的函数的调用者仍然想使用那个内存,所以编译器插入代码来释放你的字符串。

      【讨论】:

      • 谢谢你,超级罗布,现在很清楚了。是的,我认为我会返回 -1、-2、-3 之类的代码,并且会放入 readme.txt,因此 dll 中的代码永远不会引发异常。
      • 所以我不应该返回一个 PChar 吗?我应该只返回 char 数组的第一项的地址吗?谢谢
      • 如果您要返回 -1、-2、-3 之类的代码,那么您不能返回 PChar,因为您只能返回一件事。考虑像@​​987654321@ 这样的函数。它接收一个缓冲区指针,然后写入该缓冲区。它返回一个表示成功或失败的数字。
      • 我想我已经明白了。我现在将对此进行更多研究,但从你所说的一般来说,返回一个整数作为状态是一种很好的做法。函数结果将被“加载”到作为缓冲区的变量(= 函数参数)中。我说的对吗?
      • 基本上是的。保持简单并使用原始类型。如果您返回复杂的对象,您可能会失去兼容性并制作一个仅限 delphi 的 dll。返回 int 或 bool 作为状态并对参数进行操作(请参阅我的答案)。哦,正确使用 stdcall/cdecl。
      猜你喜欢
      • 2012-01-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-17
      • 1970-01-01
      相关资源
      最近更新 更多