【问题标题】:Delphi App Communicates with Program That Ends Up Crashing Occasionally - Vendor Blames My Delphi AppDelphi 应用程序与偶尔崩溃的程序通信 - 供应商责备我的 Delphi 应用程序
【发布时间】:2010-11-05 21:10:50
【问题描述】:

我编写了一个 Delphi DLL,它通过 COM 与第三方程序通信。一些用户报告说第三方程序偶尔会崩溃。其他以相同方式使用该软件的人从未经历过崩溃。发生此崩溃时,第三方程序似乎在我的 DLL 应用程序中变得不可用。

供应商发誓这是 Delphi DLL 的编码方式有问题,虽然他们没有看过源代码,也无法判断 DLL 正在做什么导致崩溃,但他们知道这是“某事” .

除了我认为第三方程序不应该因为我的 DLL 中的一些小问题而崩溃之外,让我们假设我的 DLL 中有一些东西需要修复。

我如何确定我的应用可能是如何导致此问题的?有没有人有通过 COM 与这样的超敏感程序进行通信的经验?是否有一些常见的事情可能会导致第三方程序崩溃?

【问题讨论】:

    标签: windows performance delphi debugging com


    【解决方案1】:
    1. 让客户满意。
    2. 不要假设它不是你的 dll,它可能是。即使“以相同方式使用该软件的其他人从未经历过崩溃”,也可能是使用不同的数据,它会做不同的事情......
    3. 我建议您将日志记录设置为“特殊”诊断版本中的文本文件。
    4. 记录所有内容、您的参数、您的异常以及您正在经历的步骤。甚至可能是每个函数的开始和结束,以及每隔一行。

    这是它的外观......

    Loaded DLL
    Started MyFunction1 with parameters: 1,4,hello
       1
       2
       ...
       500
    Ended MyFunction1
    

    为了做到这一点,我会设置一些功能(在它们自己的单元中):

    // opens a text file (fixed name), and appends to it.
    function InitializeLog; 
    
    // closes the file
    function CloseLog;      
    
    //add a log line.
    function Log(message:string='', startNewFunction:boolean:False); 
    

    你可以这样称呼它:

    function MyFunction1(Integer,Integer,String);
    begin
      try
        Log('Loaded DLL');
    
        //use inttostr and do some string concats to get the params
        Log('Started MyFunction1 with parameters: 1,4,hello',true); 
    
        //Then every other line:
        Log; 
        //this would increment a global variable FuncLine:Integer
        //and write it to the file.    
    
      except
        On E:Exception (Log('***'+E.Message));
      end;
    end;
    

    这样的东西应该有一个 {$DEFINE} 来启用这些日志记录功能,启用/禁用诊断日志记录。

    This could also be useful.

    【讨论】:

      【解决方案2】:

      查看 Quality Central 以获取报告 58409。

      这是关于 FPU 掩码和 Dll 的。

      简而言之:

      FPU Mask 的设置决定了浮点异常的处理方式。

      例如,如果您有一个加载 Dll_A(也由其他人编码)和 Dll_B(由您编码)的 Applicaion_A(由其他人编码),并且您的 Dll 更改了 FPU 掩码,那么此更改对 Application_A 和DLL_A 也是如此。

      我们举个例子: 您已经安装了例如 WinZIP、SubVersion 等,它们在 Windows 文件资源管理器中注册了附加功能(鼠标右键单击弹出菜单),现在您从 application.exe 内部调用 TOpenDialog,那么这些附加功能可能会损害您的 FPU 设置。

      希望这会有所帮助。 (附加提示:使用 Sysinternal 查看您的应用加载了哪些 dll)

      【讨论】:

      • Samuel - 有没有办法判断这是否发生在我的情况下?
      • 保存并恢复控制字,以便他们的应用始终看到与以往相同的值。如果这样可以解决问题,那就是原因。
      【解决方案3】:

      您是否考虑过使用MadExcept?如果您在接口方法中发现错误,您可以记录调用堆栈或向用户显示对话框并将标准 EOleSysError 返回给调用 exe。

      类似这样的:

        except
          on e: Exception do
          begin
            MadExcept.HandleException();
      
            raise EOleSysError.Create('InitializeObject Failed',
              ErrorNumberToHResult(1 + CODE_BASE), 1);
          end;
      

      如果应用程序挂起但没有抛出异常,您可以使用 MadExcept 实用程序 madTraceProcess 查看正在发生的情况。这将让您为正在运行的应用程序生成一个调用堆栈。您的 dll 将不在主线程上,但您将能够看到您的调用堆栈。这是一个很好的方法来判断你的 dll 在挂起时是否真的在做任何事情。

      我有一个 COM dll,它与不使用 MadExcept 的 exe 交互,这种方法对我来说效果很好。

      【讨论】:

      • 我在 DLL 中运行 EurekaLog,当第三方应用程序崩溃时它不会引发异常。据我所知,当它崩溃时根本没有收到通知。
      【解决方案4】:

      如果他们的程序在您使用他们发布的界面时崩溃,我很确定它有问题。完全证明这是另一回事。

      您能否使用可以提供给供应商的小型 Delphi 应用程序可靠地复制问题?看到可重现的故障有助于说服他们需要进行修复。至少,它可以帮助查明他们认为你在做什么“错误”,并告诉你如何“正确”做。

      您也可以尝试使用 C# 甚至 VBScript 复制失败。

      【讨论】:

      • 我非常同意。我以前来过这个地方很多次,最快的解决方案一直是创建一个新的可重复测试程序并将其提供给具有完整源代码的供应商。如果可能,记录与 API 的所有交互(传递了什么参数以及返回什么)。
      • 供应商说 Dave 的 DLL 错误。应该由供应商而不是 Dave 来制定测试程序,因为供应商才是真正看到问题的人。如果不是测试程序,那么至少要列出一个步骤来演示问题。
      • 我认为供应商没有看到这一点。我以为是戴夫的客户。无论如何,我同意供应商应该加强并至少帮助隔离问题。不幸的是,他们似乎并不急于求成,因此 Dave 需要帮助他们说服他们存在问题,或者找到解决办法。谁知道,在重复这个问题时,他可能会发现自己到底犯了一个错误。底线;戴夫有他的客户要担心。
      • 是的,供应商在这里一点帮助都没有。如果我发现了错误,我很乐意承认错误。第三方应用程序无法处理我的 DLL 的“多线程”的可能性有多大?有没有可能它根本没有能力正确处理它?
      • 是的,这是可能的。我会直接询问供应商他们的应用程序是否支持并发。无论如何,在尝试重现问题时,这可能是一个很好的起点。
      【解决方案5】:

      如果我对情况的理解正确,很幸运,如果您的 DLL 没有崩溃并且被调用的第 3 方程序只是停止响应,那么您无能为力。崩溃在他们的代码中,但仅由调用它的 DLL 触发。调试日志确实应该在他们的应用程序中完成。
      但是,您可以做的是使用参数和一些上下文信息记录您的 DLL 对该第 3 方程序的所有调用。
      然后查看崩溃前的最后一条痕迹可能会为您提供一些信息...

      【讨论】:

        【解决方案6】:

        我不知道 madExcept 等是如何工作的,但我经常使用 jclDebug.pas + JclHookExcept.pas(来自 JEDI JCL 库)。它创建了一个 Windows API 挂钩,因此它可以捕获所有 (!) 异常,即使您进行了如下操作:

        try
          raise exception.create('test');
        except
          //eat exception
        end;
        

        通常你看不到这个异常,因为它被“吃掉”了......但是使用钩子你会得到所有异常。例如:我曾经在Midas.dll中遇到过“灾难性故障”,通过钩子我在这个异常之前看到了dll中的“数据库连接丢失”错误,所以我知道发生了什么。 (顺便说一句:JclHookExcept.pas = 钩子,jclDebug.pas = 轨迹跟踪)。

        我现在看到 JclHookExcept.pas 还有一个“JclHookExceptionsInModule”过程,因此您可以强制 (?) 挂钩来自特定库的所有异常...

        一些演示代码:

        procedure AnyExceptionNotify(ExceptObj: TObject; ExceptAddr: Pointer; OSException: Boolean);
        begin
          //log exception
        end;
        
        initialization
          // Start Exception tracking
          JclStartExceptionTracking;
          JclTrackExceptionsFromLibraries;
          JclStackTrackingOptions := [stStack, stRawMode, stAllModules];
          // Assign notification procedure for hooked RaiseException API call. This
          // allows being notified of any exception
          JclAddExceptNotifier(AnyExceptionNotify);
        

        【讨论】:

          猜你喜欢
          • 2015-09-21
          • 1970-01-01
          • 2016-06-13
          • 1970-01-01
          • 1970-01-01
          • 2015-07-24
          • 2012-12-07
          • 2014-10-24
          • 2018-09-07
          相关资源
          最近更新 更多