我问这个是因为我没有好主意......希望别人有新观点。
我有一个用户在Windows 7 64位系统上运行我们的32位Delphi应用程序(使用BDS 2006编译)。我们的软件“工作正常”直到几周前。现在突然不是:它在初始化(实例化对象)时抛出了访问冲突。
我们让他重新安装了我们所有的软件 - 从头开始。相同的AV错误。我们禁用了他的反病毒软件;同样的错误。
由于某种原因,我们的堆栈跟踪代码(madExcept)无法为错误行提供堆栈跟踪,因此我们发送了几个错误日志记录版本供用户安装和运行,隔离产生错误的行...
事实证明,这是一个实例,它是一个简单的TStringList后代(没有重写的Create构造函数等等 - 基本上Create只是实例化一个TStringList,它有一些与后代类关联的自定义方法。)
我很想再向用户发送另一个测试.EXE;只是一个普通的TStringList实例,看看会发生什么。但是在这一点上,我觉得我在风车上挣扎,如果我发送了太多“尝试的东西”,那么冒着让用户忍耐的风险。
有关更好地调试此用户问题的方法的任何新想法? (我不喜欢挽救用户的问题......那些被忽视的人突然变成了其他5个用户突然“找到”的流行病。)
EDIT,正如Lasse所要求的那样:
procedure T_fmMain.AfterConstruction;
begin
inherited;
//Logging shows that we return from the Inherited call above,
//then AV in the following line...
FActionList := TAActionList.Create;
...other code here...
end;
这是正在创建的对象的定义......
type
TAActionList = class(TStringList)
private
FShadowList: TStringList; //UPPERCASE shadow list
FIsDataLoaded : boolean;
public
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
procedure DataLoaded;
function Add(const S: string): Integer; override;
procedure Delete(Index : integer); override;
function IndexOf(const S : string) : Integer; override;
end;
implementation
procedure TAActionList.AfterConstruction;
begin
Sorted := False; //until we're done loading
FShadowList := TStringList.Create;
end;
答案 0 :(得分:5)
我讨厌这些问题,但我认为你应该关注最近在对象试图构建之前发生的事情。
您描述的症状听起来像典型的堆损坏,所以也许你有类似的......
自上面的答案以来,您已发布了代码段。这确实引发了我可以看到的几个可能的问题。
a:AfterConstruction与修改后的构造函数: 正如其他人所提到的,以这种方式使用AfterConstruction充其量不是惯用语。我认为这不是真正的“错误”,但这可能是一种气味。这些方法在Dr. Bob's site here.
上有一个很好的介绍b:重写方法添加,删除,IndexOf 我猜这些方法以某种方式使用FshadowList项。在创建FShadowList之前,是否可以远程调用这些方法(因此使用FShadowList)?这似乎是可能的,因为您正在使用上面的AfterConstruction方法,此时虚拟方法应该“工作”。希望通过设置一些断点并查看它们被命中的顺序,可以很容易地通过调试器进行检查。
答案 1 :(得分:2)
我们的软件“工作正常”直到几周前......突然变成了其他5个用户突然“发现”的流行病。):
听起来您需要进行一些取证分析,而不是调试:您需要发现该用户环境中发生的变化才能触发错误。如果你有其他用户使用相同的部署没有问题(听起来就像你的情况那样),那就更是如此了。发送用户'要尝试的东西'是非常快速侵蚀用户信心的最好方法之一! (如果用户站点有IT支持,请让他们参与,而不是用户)。
首先,请探索以下选项:
*)如果可能的话,我会检查Windows事件日志,查看问题发生时该机器上可能发生的事件。
*)在用户方面是否有某种IT支持人员可以与该用户环境中的可能变更/问题进行讨论?
*)在错误浮出水面的时候,是否存在与该用户有某种支持问题/事件,并且/或者导致某种数据或文件损坏特定于它们?
(至于代码本身,我同意@Warran P关于解耦等)
答案 2 :(得分:2)
您应该从不覆盖程序中的AfterConstruction
和BeforeDestruction
方法。它们并不意味着您正在使用它们,而是针对低级VCL黑客攻击(如引用添加,自定义内存处理等)。
您应该覆盖Create constructor
和Destroy destructor
,并将初始化代码放在此处,例如:
constructor TAActionList.Create;
begin
inherited;
// Sorted := False; // not necessary IMHO
FShadowList := TStringList.Create;
end;
看看VCL代码和所有严肃发布的Delphi代码,你会发现从未使用AfterConstruction
和BeforeDestruction
方法。我想这是问题的根本原因,因此必须修改您的代码。在未来的Delphi版本中可能会更糟。
答案 3 :(得分:2)
显然,对TAActionList
在施工时所做的事情没有任何疑问。即使考虑祖先构造函数和设置Sorted := False
可能产生的副作用,也表明应该没有问题。我对T_fmMain
内发生的事情更感兴趣。
基本上正在发生导致FActionList := TAActionList.Create;
失败的事情,即使TAActionList.Create
的实现没有任何问题(可能是表单可能意外地被破坏)。
我建议您尝试更改T_fmMain.AfterConstruction
,如下所示:
procedure T_fmMain.AfterConstruction;
begin
//This is safe because the object created has no form dependencies
//that might otherwise need to be initialised first.
FActionList := TAActionList.Create;
//Now, if the ancestor's AfterConstruction is causing the problem,
//the above line will work fine, and...
inherited AfterConstruction;
//... your error will have shifted to one of these lines here.
//other code here
end;
如果表单使用的组件的环境问题导致它在AfterConstruction
期间破坏表单,那么将TAActionList.Create
实例分配给FActionList
实际导致AV。另一种测试方法是首先将对象创建为局部变量,然后将其分配给类字段:FActionList := LActionList
。
环境问题可能很微妙。例如。我们使用报告组件,我们发现需要安装打印机驱动程序,否则会阻止我们的应用程序启动。
您可以通过在表单的析构函数中设置全局变量来确认销毁理论。您也可以从析构函数中输出堆栈跟踪,以确认导致表单销毁的确切顺序。
答案 4 :(得分:0)
当MadExcept不够时(我很少说,这是很少见):
请尝试使用Jedi JCL的JCLDEBUG。如果您更改了MadExcept for JCLDEBUG,并且没有任何UI交互,则直接将堆栈跟踪写入磁盘,您可能会获得堆栈跟踪。
运行调试查看器,如MS / SysInternals debugview,并跟踪输出事件,例如发生问题的对象的自我指针。我怀疑INVALID实例指针不知何故在那里结束。
将事物分离并重构,并编写单元测试,直到找到真正令人讨厌的东西为止。 (有人建议堆损坏。我经常发现堆损坏与不安全丑陋的未经测试的代码,以及深层绑定的UI +模型级联故障密切相关。)