delphi检查是否使用了函数结果值

时间:2017-11-12 23:16:21

标签: function delphi return-value

除了语言之外,我的问题与this one here相同。我正在使用Delphi。

按照您在Java中看到的链接,这似乎是不可能的,所以我认为这也适用于Delphi,但我不完全确定

我想知道函数是否可以检查调用过程/函数是否正在使用函数提供的返回值。

我已经尝试了这一点,但它没有工作:(我想如果存在解决方案,它会表现得与此相似)

  if Assigned(Result) then begin
    showmessage('return value is assigned');
  end else begin
    showmessage('return value is NOT assigned');
  end; 

检查似乎没用,但是我的函数产生了一个加密文件,为了解密这个文件需要返回值,所以通知用户而不仅仅是生成一个无用的文件会很好因此,我想检查返回值的用法。

可能的解决方法:

我有一个想法,但要么不知道这是否可能,或者怎么做:该功能可以扫描我的应用程序占用的RAM部分并检查返回触发命令Result := 'something like F8975BE8AC0192#2983FE4A#B9DFE25'后存在的值(字符串)。字符串存在两次是不可能的,这个简单的检查完全符合我的目的。 一般情况下,这可能不起作用,因为结果可能存在两次,但就我而言,这在某种程度上是不可能的。

为了解决这个问题,我扩展了原来的问题: 如何在包含该函数的应用程序所占用的RAM部分中搜索字符串,该函数要检查这个? 据我所知,为外部应用程序检查它会更加困难,这里不需要。

3 个答案:

答案 0 :(得分:4)

在Delphi中,您无法实现的目标。而且这不是“语言问题”。但它也 不是问题

你真的在考虑这种情况。我会试着解释原因;希望你会放弃这种愚蠢行为。

摘要

  • 功能应该只做一件事。函数试图做的事情越多:正确实现就越困难,其他人重用就越困难。
  • 您可能会轻易绕过任何 hack 。因此,您最终会遇到更多复杂性和相同的 问题。
  • 确保呼叫者正确使用您的功能不是您的责任。这种方式就是疯狂和不断增长的兔子 - 沃伦。
  • 您应该只根据“合同”实现您的功能,并且调用者有责任正确使用它。如果调用者没有正确使用它,调用者有一个bug,直到调用代码被修复。 那么简单。

只做一件事

当函数执行多个操作时,使用一个特征而不是另一个特征变得不可能(没有仔细的重构)。该功能更难以测试,因为:在通话期间更多的事情发生变化,需要更多的设置,应该检查更多的事情,可能需要考虑更多的排列。

Javascript问题中的场景更糟糕,因为这两件事情有根本区别:

  • 功能式版本可以保证不改变状态。
  • 而另一种风格旨在修改状态。

这是一个可变性与不变性的概念。了解 不变性 何时得到保证是非常有益的。将两者捆绑到一个函数中意味着您不再拥有不可变的版本。

黑客攻击“解决方案”毫无意义

假设你找到一些黑客来查询函数本身的调用栈 ,以确认调用者将函数结果存储到变量中供以后使用。

让我们也关注不同操作系统,CPU架构,编译器优化等问题。

没有什么可以阻止调用者执行以下操作:

S := HybridFunction(...);
if (S <> '') then S := '';

是调用者存储了变量,但显然没有使用它。但来电者甚至不必对此如此公然。将变量存储在类字段中但从不再次引用它具有相同的效果。

不要仔细检查您的来电者 - 这不是您的责任

您有足够的担心确保您的代码正常运行,而不必担心您的呼叫者会做什么。如果你试图补偿来电者:

  • 如果来电者是正确的,那么您的特殊代码就毫无意义。
  • 如果来电者错了,你就无法“修理”任何东西。你无法知道每个可能的来电者应该做些什么。

最好花在处理来电代码上并确保 正确

另外,如果您担心来电者;那个来电者的来电怎么样?或者是来电者的来电者? ...... 它永远不会结束。

实施合同并完成

您可以使用 out 参数强制调用者接收结果。但这仍然无法阻止来电者在调用后 不使用 out 值。因此,您将牺牲干净的函数式调用约定,以实现零利益。

我假设您有以下几行功能:

function EncryptFile(): TKey; { returns the decryption key }

这里已经存在问题,因为你的功能显然是做两件事。将其拆分为2个函数,您的调用者也更容易编写。

function GetKey(): TKey;
procedure EncryptFile(AKey: TKey);

然后您的 来电者可以在尝试加密前验证

LKey := GetKey();
if Assigned(LKey) then 
  EncryptFile(LKey)
else { report the error as you choose }

这就是为什么我说你要从头到尾

答案 1 :(得分:1)

我相信你的问题可能源于实施绝对防弹的愿望,或者使事情变得比现在更难的设计选择。

防弹实施

如果您作为开发人员做得很好,函数命名和签名将指示调用者的预期用途。他是否理解并最终正确使用它可能是你无法控制的,正如评论所表明的那样:你在哪里停下来?总有一种方法可以打败你的实施。

可以而且应该做些什么吗?

但是考虑到你的问题的技术方面以及无法检查是否有功能结果已完成 - 因为功能代码已经退出 - 我的想法本来就是让结果本身变得更聪明&# 39;

您提到加密密钥是您要调用的字符串&#39; unique&#39;。因此,可以使用Delphi的字符串引用计数 - 至少对于WideString以外的字符串 - 来确保它仍被引用到某处。加上IInterfacedObject也是引用计数,TInterfaceObject可以检查它是否超过受监控的字符串。

<强>接口

  IRefCheck = interface
  ['{E6A54A33-E4DB-4486-935A-00549E6793AC}']
    function Value: string;
  end;

TInterfacedObject

实施
  TRefCheck = class(TInterfacedObject, IRefCheck)
  strict private
    FString: string;
  public
    constructor Create(const S: string);
    destructor Destroy; override;
    function Value: string;
  end;

实施类似

constructor TRefCheck.Create(const S: string);
begin
  FString := S;
end;

destructor TRefCheck.Destroy;
begin
  Assert(StringRefCount(FString) > 1, 'Reference check failed for ' + FString);
  inherited Destroy;
end;

function TRefCheck.Value: string;
begin
  Result := FString;
end;

因此,每当TRefCheck被销毁并保留对其字符串的唯一引用时,Assert将失败。我选择接口作为结果,因为当它是一个字符串时,调用者不需要控制结果的生命周期,现在也不需要。您的功能现在看起来与此类似:

function GetEncryptedFileKey(Key: string): IRefCheck;
begin
  Result := TRefCheck.Create(Key);
end;

使用方式如下:

procedure Test;
var
  S: string;
begin
  S := GetEncryptedFileKey('Test Value 1').Value;
  GetEncryptedFileKey('Test Value 2');
end;

调用Test的小型测试程序将失败

  

EAssertionFailed:测试值2的参考检查失败

因为这需要知道调用者访问IRefCheck的{​​{1}}接口,并且隐式运算符不能用于类(或接口),所以我用记录来尝试它,所以它可以是像之前的字符串函数一样使用:

Value

您的功能和测试代码如下所示:

  TRefCheckRec = record
  private
    RefCheck: IRefCheck;
    function AsString: string;
  public
    class operator Implicit(Value: TRefCheckRec): string;
    class function CreateNew(S: string): TRefCheckRec; static;
  end;

function TRefCheckRec.AsString: string;
begin
  RefCheck.Value;
end;

class function TRefCheckRec.CreateNew(S: string): TRefCheckRec;
begin
  Result.RefCheck := TRefCheck.Create(S);
end;

class operator TRefCheckRec.Implicit(Value: TRefCheckRec): string;
begin
  Result := Value.AsString;
end;

最后即便如此,击败也就像从未真正使用&#39; function GetEncryptedFileKey(Key: string): TRefCheckRec; begin Result := TRefCheckRec.CreateNew(Key); end; procedure Test2; var S: string; begin S := GetEncryptedFileKey('Test Value Record'); GetEncryptedFileKey('Test Value Record 2'); end; ,实际上测试代码正是如此。所有的毛茸茸和没有优势超过

S

function GetEncryptedFileKey: string;

设计选择

阅读你的评论和我认为我会做的事情是将你的逻辑封装到一个类中,让你的函数返回它的一个实例。

procedure EncryptFile(var EncryptionKey: string);

您的函数现在返回它的一个实例,调用者负责清理,如函数名所示。

  TEncryptedFile = class
  private
    FEncryptionKey: string;
    FKeyPersisted: Boolean;
  public
    constructor Create;
    destructor Destroy; override;
    function GetEncryptionKey: string;
    procedure SaveEncryptionKeyToFile;
  end;

constructor TEncryptedFile.Create;
begin
  FKeyPersisted := False;
  FEncryptionKey := ...
end;

destructor TEncryptedFile.Destroy;
begin
  if not FKeyPersisted then
    SaveEncryptionKeyToFile;
  inherited Destroy;
end;

function TEncryptedFile.GetEncryptionKey: string;
begin
  Result := FEncryptionKey;
  FKeyPersisted := True;
end;

procedure TEncryptedFile.SaveEncryptionKeyToFile;
begin
  FKeyPersisted := True;
  // Save to file
  ...
end;

他是否决定直接访问您的密钥或将其保存到文件中取决于他。如果他清理实例并且两者都没有完成,则会自动保存密钥。

答案 2 :(得分:0)

所以我认为你问的是你是否可以从函数,加密字符串和解密密钥返回两个数据。 您可以使用这两个项声明一个类并返回该实例,也可以将它们作为参数接口的一部分传回。

procedure cruncher(incoming:string;var encrypted:string;var decryptkey:string)

作为一种个人风格,我反对通过功能结果传回一半答案。