Delphi DLL类异常

时间:2017-04-21 20:54:30

标签: delphi dll

我需要编写一个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,但我怀疑我还没有用谷歌解决。正如你所看到的,我已经声明了另一个包含该类的单元,所以我可以将这两个东西分开。

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

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

2 个答案:

答案 0 :(得分:5)

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 :(得分:0)

你的代码编译并运行,但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; 

这里我返回一个整数(但也可能是一个好的bool),表示操作成功。在这种情况下,您会看到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关键字)传递的第一个参数字符串或动态数组。