Root > Advanced topics > Using EurekaLog in DLL > Using exception tracer with COM objects

Using exception tracer with COM objects

Previous pageReturn to chapter overviewNext page   

 

Important: as a general rule, you should write your application in a such way that it will behave correctly without EurekaLog on board. This means that if your or 3rd party code throws an exception in a DLL - it must be handled in a way that you expect. For example, by showing an error message, canceling action, retrying, etc. Once your application behaves like it is supposed to do - then you add EurekaLog for it. EurekaLog will auto-handle all unhandled exceptions automatically (by hooking few well-known places), and for everything else - you can call EurekaLog from your own handlers when needed. In other words:

Incorrect: your application does not show messages for exceptions (or do other things you want) from DLL and you are adding EurekaLog in hopes to fix this behavior;
Correct: your application correctly handles DLL exceptions and you are adding EurekaLog to receive reports about such exceptions.

 

COM objects are implemented in DLLs. Therefore, COM server projects with EurekaLog should follow usual rules for DLLs. Additionally, COM enforces additional restrictions on error handling: therefore a most complex issue for COM object is how to report bug reports. Many COM objects are multi-threaded. This means that you must follow guidelines for multi-threaded applications.

 

To summarize restrictions on error handling:

COM objects must handle each exception in the same method, do not allow exception escape to the caller;
Each COM object's method must be a function. Each such function must return an HRESULT value. Each such function must have the stdcall calling convention;
COM methods must return S_OK (zero) for success calls, E_UNEXPECTED for unknown errors, or any specific error/success code for known problems.
(Optional) COM objects may report additional details for failure error codes via IErrorInfo interface.

 

Additionally, COM objects are "plugins". I.e. a single application (.exe host) may operate with several COM object, each being loaded into the same process. Therefore, it's important to be sure that multiple COM objects with exception tracers will not conflict with each other. Since COM object can be used in any application host (with or without EurekaLog or exception tracer) - it means that each COM server project must be standalone. I.e. there must be exception tracer instance in COM server project. Thus, if application loads several COM objects - there will be multiple instances of exception tracer active.

 

Important notice: Windows 2000 does not provide any way to set exception hook in documented way. This means that any exception tracer have to install low-level injecting hook for internal routines. Only single module can install such hook reliably for the same routine. If two or more different modules attempt to install such hook - it will either fail or crash. Therefore, it's highly not recommended to use multiple instances of exception tracers on Windows 2000. Windows XP and above do not have such issues, because newer systems allow you to install arbitrary amount of exception hooks via documented API. This API is called VEH: Vectored Exception Handling. If you can't use single instance of exception tracer and have to support Windows 2000: we suggest to use Delphi 2009 or above and disable "Use low-level hooks" option. Delphi 2009 introduced better integration between application and exception tracer. It allow you to react on hi-level exceptions without need to install low-level hook (however, low-level hook still may be installed to capture CPU state). Combination of Windows 2000 and any IDE before 2009 (such as Delphi 7) will not work reliably for multiple instances of exception manager - regardless of options in those modules. If you can afford to not support Windows 2000 - it's recommended to enable "Use low-level hooks" option.

 

Normally, you develop COM servers in Delphi and C++ Builder by using assist from VCL. I.e. you use File / New / ActiveX library + File / New / COM Object. Resulting code will use ComServ unit with TComServer class and ComObj unit with TComObject class.

 

 

"stdcall + HRESULT" vs. "safecall"

RAD Studio offers you a helper to easily follow COM requirements outlined above. Normally, a COM method should look something like this:

 

type
  TSampleObject = class(TTypedComObject, ISampleObject)
  protected
    function DoSomething(Param1: Integer; const Param2: WideString; out Rslt: WideString): HResult; stdcall;
  end;
 
function TSampleObject.DoSomething(Param1: Integer; const Param2: WideString; out Rslt: WideString): HResult;
begin
  try
    Rslt := IntToStr(Param1 + StrToInt(Param2));
 
    Result := S_OK;
  except
    on EConvertError do
      Result := E_INVALIDARG
    else
      Result := E_UNEXPECTED;
  end;
end;

 

// ...
 
var
  Obj: ISampleObject;
  Value: WideString;
// ...
  OleCheck(Obj.DoSomething(1, '4', Value));

 

As you can see: you have to place explicit try/except block to handle all exceptions. You have to write error handling code by yourself for each method. You have to return HRESULT value. Function's real result must be converted to last output argument. You also have to manually check result code (HRESULT) on caller's side (notice a call to OleCheck in the example above). Obviously, this is not a very convenient way.

 

Fortunately, there is a special safecall calling convention. The safecall convention implements exception 'firewalls'. On Win32, this implements interprocess COM error notification. Therefore, you can use the following approach instead:

 

type
  TSampleObject = class(TTypedComObject, ISampleObject)
  protected
    function DoSomething(Param1: Integer; const Param2: WideString): WideString; safecall;
  end;
 
function TSampleObject.DoSomething(Param1: Integer; const Param2: WideString): WideString;
begin
  Result := IntToStr(Param1 + StrToInt(Param2));
end;

 

// ...
 
var
  Obj: ISampleObject;
  Value: String;
// ...
  Value := Obj.DoSomething(1, '4');

 

The both code samples are binary compatible. I.e. both have the same method prototype. The difference is that second example uses "compiler magic". Compiler automatically inserts a hidden try/except block to catch all exceptions within method and convert them to appropriate error code. Each exception is handled by SafeCallException virtual method of current object. Safecall calling convention allows you to write your code in usual way: use Result as you would normally do, no need to place exception handling blocks, no need to check return codes on caller's side.

 

For more information about safecall, see System.SysUtils.ESafecallException, System.SafeCallErrorProc, and System.TObject.SafeCallException.

 

When you create new COM object (server side) or import type library (client side) - you can create/import methods either in "stdcall + HRESULT" form or in "safecall" form. The IDE's behavior is controlled by corresponding options:

 

 

SafeCall options under "Tools / Options" menu item

 

 

Exception's life-time with COM objects

Exception's life-time is significantly different in COM applications (compared to typical applications). First, let's see an illustration for a normal VCL application:

 

 

Exception's life-time in VCL application

 

Here: exception is raised by some code. Exception tracer detects this moment via hooks and creates call stack. Then exception is passed through one or more exception handlers (such as finally and except blocks). Eventually, exception may be processed and deleted or (in case of unknown exceptions) it could be passed to default handler. Exception tracer intercepts default handler and create bug report for exception (shows dialog, send via Internet, etc.).

 

Now, let's see how this scenario will be different for COM objects:

 

 

Exception's life-time in COM applications

 

Here: "callee module" is COM server project (DLL). Caller module is .exe host (however, it can also be a DLL, package or even another COM object for generic case). Again, exception is raised by some code. However, it can not be passed directly to exception's handler of the caller. Because rule #1 for COM is: do not let exceptions escape your code. So, this exception is catched by "firewall" ("compiler magic" for safecall methods or explicit except block for stdcall/HRESULT).

 

Notice important difference: each conceptual exception is represented by two different exceptions - one for callee side and another one for caller side. This means that there will be two bug reports, two dialogs, etc. For this reason it's recommended to disable error dialogs for COM server projects (switch dialog to "None") and keep visual dialog for end user side (.exe host). COM server project will create bug report file, but will not show any error dialog and will not submit error to developers. Then, exception will appear in caller module (as HRESULT value + IErrorInfo interface). Caller will create another bug report, show dialog, and send report to developer. If caller is awared about exception tracing features in failed COM object, then caller may include bug report from failed COM object as attachment to its own bug report.

 

Please note that exception travel chain may include more than one "exception -> HRESULT -> exception" transformation. For example, your .exe host calls method from COM object, this method calls another COM method (in the same object or in some another COM object). If the last callee raise exception, then this exception will travel as "exception -> HRESULT -> exception -> HRESULT -> exception".

 

Unfortunately, there is no standard COM facility to pass call stack from callee to caller. It's not possible to pass original exception's information to the caller. You may try different approaches as explained in this article (see "Creating bug reports for DLL exceptions" section at the end of the article).

 

 

Using COM objects with exception tracer without framework (VCL)

There are no any special issues if you write COM server manually without using VCL support. It would be just a normal DLL with exported functions. Consider COM rules as system API. You have to wrap each method in try/except block and invoke EurekaLog to handle exceptions.

 

Note: "Handle every SafeCall exception" option will have no effect, since you're not using ComObj unit. You may use safecall methods instead of stdcall/HRESULT - based on TObject. However, you still need to manually invoke EurekaLog from your SafeCallException override.

 

Hints about options for your COM server project without VCL:

Profile should be "Standalone DLL";
(Optional) If you're going to use VCL, CLX or FireMonkey forms in your COM object: enable hooks for corresponding framework;
Since exceptions in COM objects are handled in the same module (even more: in the same method) - you should enable "Capture stack only for exceptions from current module" option;
Multi-threaded COM objects: set "Default EurekaLog state in new threads" option to "Enabled for RTL threads, disabled for Windows threads" value or enable EurekaLog manually for each thread.

 

See for more information:

 

 

Using COM objects with exception tracer and framework (VCL)

This approach is much easier, because you don't have to write a lot of code manually. You don't have to write any try/except blocks. You only have to enable "Handle every SafeCall exception" option.

 

Short summary of configuration:

Profile should be "Standalone DLL";
(Optional) If you're going to use VCL, CLX or FireMonkey forms in your COM object: enable hooks for corresponding framework;
Since exceptions in COM objects are handled in the same module (even more: in the same method) - you should enable "Capture stack only for exceptions from current module" option;
Multi-threaded COM objects: set "Default EurekaLog state in new threads" option to "Enabled for RTL threads, disabled for Windows threads" value or enable EurekaLog manually for each thread;
You must do at least one of the following:
oUse Delphi 2009 or above.

 

You can also customize EurekaLog behavior via event handlers. For example, you can disable dialog for safecall exceptions, but enable dialog for exceptions in forms. Note that safecall exceptions are considered to be handled exceptions.

 

Note: you don't have to enable "Catch handled exceptions" option.

 

You can also study COM object demo application shipped with EurekaLog installation.

 

 

See also:




Send feedback... Build date: 2024-09-30
Last edited: 2024-07-08
PRIVACY STATEMENT
The documentation team uses the feedback submitted to improve the EurekaLog documentation. We do not use your e-mail address for any other purpose. We will remove your e-mail address from our system after the issue you are reporting has been resolved. While we are working to resolve this issue, we may send you an e-mail message to request more information about your feedback. After the issues have been addressed, we may send you an email message to let you know that your feedback has been addressed.


Permanent link to this article: https://www.eurekalog.com/help/eurekalog/com_objects.php