Important Note: please see most recent version of this documentation here.
Content:
- What is DLL?
- What is exception?
- How DLLs report about failures?
- What is the proper way to handle exceptions in DLL?
- Using exception tracer tool in DLLs
- Using exception tracer with frameworks in DLLs
- Conclusion
Important Note: please see most recent version of this documentation here.
Note: this article will use EurekaLog 7.0.4.3 or higher as exception tracer.
What is DLL?
DLL is a library of functions. Application (.exe file) can load DLL (.dll file) and call a function from it. DLL is isolated file, not related to application. Therefore, DLL and application can be written in different programming languages.Application is often called a "host application" or just "host" . Both DLL and host are also called "executable modules" or just "modules". Sometimes "application" is referred to host with loaded DLLs (not just host itself).
DLL and application need to understand each other. So, a set of rules must be established to communicate. Such set of rules is called "API".
API can be developed by you or some other developers/company. If you're the API developer - then you can decide how DLL will work with host. If you're not the API developer - then you can only follow already established rules, but you can't invent your own rules.
What is exception?
Exception is represented by an object (class instance) in most modern high-level programming languages. This means that exceptions can be inherited from base classes, as well as be extended with arbitrary properties.Since exception is a way to interrupt normal execution path of a code - it requires support from hardware level. Modern CPUs provides such support. However, user-mode applications do not have direct access to the hardware. Therefore, operating system provides method to use exceptions on particular hardware. This is called SEH ("Structured Exception Handling") in Windows.
Exception on operating system level is represented by its address, code, options ("flags") and up to 15 4-byte integers ("params"). High-level programming languages use SEH and this low-level representation as basis for their own exception handling. For example, exception in high-level programming language (i.e. exception object) is implemented as OS exception with special code (for example:
$EEECFADE
for Delphi) and pointer to object is stored in exception params. Exceptions with other codes are wrapped in generic class (EExternalException
for Delphi).Short conclusion:
- There are 3 levels of exceptions support: hardware, OS, and programming language.
- User-mode code has access to OS and language levels.
- OS exceptions are compatible among all programming languages.
- Language exceptions are specific to programming language and could not be properly used in another programming language.
How DLLs report about failures?
Remember that object and class implementations are specific for programming language and compiler. I.e. a Delphi application doesn't know how to work with objects/classes from (for example) Microsoft C++ (and visa versa). This means that hi-level exception raised in DLL could not be properly handled by host application, unless both DLL and host are compiled by the same compiler and exception class uses the virtual destructor.Also note that mixing OS and language exceptions within same module is confusing/problematic thing.
Therefore, APIs for DLLs usually do not use exceptions as a way to report errors. Instead: functions can use error codes - such as numeric codes, success/failure flags (booleans) and so on. There are de facto standard ways to report errors - provided by operating system (for example: GetLastResult, HRESULT - on Windows). However, 3rd party DLLs may use arbitrary error reporting method.
What is the proper way to handle exceptions in DLL?
As you should already understood by now: rule #1 when working with exceptions in DLLs is "never let exception escape DLL". All exceptions in DLL functions must be captured and handled by translating them to error code or other error signature as required by DLL API.How this should be done? That highly depends on what your DLL API is. This also depends on what framework you do use. There are 3 possible cases:
- You develop DLL by using a framework.
For example: you write a control panel applet by using VCL. Or you write ISAPI module by using IntraWeb. - You develop DLL for already established API without using a ready framework.
For example: you write a plugin for 3rd party application (like Total Commander). Or you write a global system hook (which requires DLL). - You develop both DLL and API specification.
For example: you write your own DLL to be used by different applications.
Case 1: Framework
This is the simplest case - because all pitfalls are already handled by a framework. All your code is called by the framework. All exception from your code are handled by the framework. Framework handles exceptions and convert them to something (what is required by the API).In this case you can just write your code as you usually do. Framework will provide a default handling and error reporting. Some frameworks also allow you to alter default handling (useful for customizations). You should refer to the documentation of your framework if you want to do such customizations. Usually, there is some sort of global
Application.OnException
event, which you may assign to your handler's code.Case 2: 3rd party API
This case is a more complex. Basically, you need to study the API and figure out how you should report about errors in your function. You can not use arbitrary nor default way - because API is already established by someone. It's not you who develop API. You only develop a DLL.Let's consider a little example. Suppose that you want to write a global system hook - the one that is installed via
SetWindowsHookEx
function. Global hook requires you to place your handler code inside DLL, so that DLL can be injected in all running programs (which makes the hook a global one).Naturally, API (i.e. communication rules between OS and your code) is already established - it's defined by Microsoft (as a developer of hooking functions). Therefore, the first thing that you should do is to study documentation for the functions. You pass a pointer to your handler's code via second argument in
SetWindowsHookEx
function (lpfn
param). Prototype of the handler depends on what kind of hook do you want to use. Let's use WH_GETMESSAGE
hook for this example. This means that we must study description of GetMsgProc
callback.The important part for error handling looks like this:
If code is less than zero, the hook procedure must return the value returned by CallNextHookEx.In other words, your code could not report any failure reason. All that you can do is either return 0 or return whatever
If code is greater than or equal to zero, it is highly recommended that you call CallNextHookEx and return the value it returns; otherwise, other applications that have installed WH_GETMESSAGE hooks will not receive hook notifications and may behave incorrectly as a result. If the hook procedure does not call CallNextHookEx, the return value should be zero.
CallNextHookEx
returns.Therefore, your DLL code must looks at least like this:
library Project2; uses Windows; function MyHook(Code: Integer; _wParam: WPARAM; _lParam: LPARAM): LRESULT; stdcall; begin try if Code >= 0 then begin // <- your code end; except // There is no way to report errors, so we must handle all exceptions end; Result := CallNextHookEx(Code, _wParam, _lParam); end;Usually it's not a good idea to silently hide all exceptions. If API doesn't allow you to report about errors - then you should at least implement some kind of logging, so you can store information about exception in the log.
Let's see another example. Suppose you're writing a control panel applet without using any framework. This means that you must write and register DLL. DLL must export
CPlApplet
function. This function will be used for all communication between OS and your code. Description of CPlApplet
says:
The return value depends on the message.This means that you also must study each message from the system that you want to process. Luckily, most messages require you to handle errors in the same way:
For more information, see the descriptions of the individual Control Panel messages.
If the CPlApplet function processes this message successfully, the return value is zero; otherwise, it is nonzero.So, you should write your DLL at least like this:
library Project2; uses Windows; function CPlApplet(hwndCPl: HWND; uMsg: UINT; lParam1, lParam2: LPARAM): LongInt; stdcall; begin try case uMsg of ... end; Result := 0; except Result := 1; end; end; exports CPlApplet; end.Since you can't report what is actual report is - a good idea would be to report error to the user. We can safely do this because control panel applet is a single interactive GUI application. Showing error as dialog box is not a good idea for non-interactive applications (such as services) or code that may be used multiply times (such as global hook).
library Project2; uses Windows; function CPlApplet(hwndCPl: HWND; uMsg: UINT; lParam1, lParam2: LPARAM): LongInt; stdcall; begin try case uMsg of ... end; Result := 0; except on E: Exception do begin MessageBox(hwndCPl, PChar(E.Message), 'Error', MB_OK or MB_ICONERROR); Result := 1; end; end; end; exports CPlApplet; end.Please note that code above is just example. Not all messages to control panel applet have the same requirements. You should study description of each message that you're going to handle in your code. For example,
CPL_INIT
message has different requirements:
If initialization succeeds, the CPlApplet function should return nonzero. Otherwise, it should return zero.Therefore, you need to use such code to handle
If CPlApplet returns zero, the controlling application ends communication and releases the DLL containing the Control Panel application.
CPL_INIT
message:
library Project2; uses Windows; function CPlApplet(hwndCPl: HWND; uMsg: UINT; lParam1, lParam2: LPARAM): LongInt; stdcall; var SuccessCode, FailureCode: LongInt; begin // "If initialization succeeds, the CPlApplet function should return nonzero. Otherwise, it should return zero." if uMsg = CPL_INIT then begin SuccessCode := 1; FailureCode := 0; end else // "If the CPlApplet function processes this message successfully, the return value is zero; otherwise, it is nonzero." begin SuccessCode := 0; FailureCode := 1; end; try case uMsg of ... end; Result := SuccessCode; except on E: Exception do begin MessageBox(hwndCPl, PChar(E.Message), 'Error', MB_OK or MB_ICONERROR); Result := FailureCode; end; end; end; exports CPlApplet; end.Next example would be a Shell extension. Shell extensions are implemented as COM objects. That means that you need to write and register a DLL, which follows COM rules. A COM rule for error handling is to use HRESULT as return value of any method. There are two ways to work with HRESULT. First one is quite direct: you write a function/method that returns HRESULT and you convert each exception to HRESULT value:
... function ConvertExceptionToHRESULT(const E: Exception): HRESULT; begin Result := E_FAIL; // <- this is just a simple example // See HandleSafeCallException function from ComObj unit to see more complicated example end; type ICopyHook = interface(IUnknown) ['{000214FC-0000-0000-C000-000000000046}'] function CopyCallback(Wnd: HWND; wFunc, wFlags: UINT; pszSrcFile: PWideChar; dwSrcAttribs: DWORD; pszDestFile: PWideChar; dwDestAttribs: DWORD): HRESULT; stdcall; end; TMyHook = class(TInterfacedObject, ICopyHook) protected function CopyCallback(Wnd: HWND; wFunc, wFlags: UINT; pszSrcFile: PWideChar; dwSrcAttribs: DWORD; pszDestFile: PWideChar; dwDestAttribs: DWORD): HRESULT; stdcall; end; function TMyHook.CopyCallback(Wnd: HWND; wFunc, wFlags: UINT; pszSrcFile: PWideChar; dwSrcAttribs: DWORD; pszDestFile: PWideChar; dwDestAttribs: DWORD): HRESULT; stdcall; begin try // your code Result := S_OK; except on E: Exception do Result := ConvertExceptionToHRESULT(E); end; end; function DllCanUnloadNow: HRESULT; stdcall; begin try if { it's OK to unload DLL } then Result := S_OK else Result := S_FALSE; except on E: Exception do Result := ConvertExceptionToHRESULT(E); end; end; ...The second way is to use Delphi wrapper for HRESULT. Delphi compiler provides assisting for HRESULT returning methods via
safecall
keyword.
Any function like this:
function Funcensten1(... some arguments ...): HRESULT; stdcall; function Funcensten2(... some arguments ...; out AResult: TSomeType): HRESULT; stdcall;has the same protype and the same calling convention as such function:
procedure Funcensten1(... some arguments ...); safecall; function Funcensten2(... some arguments ...): TSomeType; safecall;In other words, the above code fragments are binary compatible with each other. So, for example, DLL may use first code block and host may use second code block - and both will work correctly.
The difference between HRESULT/stdcall and safecall headers is assisting from Delphi compiler. Each safecall function and method automatically handles all exceptions within itself. Moreover, each call to safecall function/method automatically converts HRESULT return value back to exception.
So, the second way to work with HRESULT is:
... type ICopyHook = interface(IUnknown) ['{000214FC-0000-0000-C000-000000000046}'] procedure CopyCallback(Wnd: HWND; wFunc, wFlags: UINT; pszSrcFile: PWideChar; dwSrcAttribs: DWORD; pszDestFile: PWideChar; dwDestAttribs: DWORD); safecall; end; TMyHook = class(TInterfacedObject, ICopyHook) protected procedure CopyCallback(Wnd: HWND; wFunc, wFlags: UINT; pszSrcFile: PWideChar; dwSrcAttribs: DWORD; pszDestFile: PWideChar; dwDestAttribs: DWORD); safecall; end; procedure TMyHook.CopyCallback(Wnd: HWND; wFunc, wFlags: UINT; pszSrcFile: PWideChar; dwSrcAttribs: DWORD; pszDestFile: PWideChar; dwDestAttribs: DWORD); safecall; begin // your code end; function DllCanUnloadNow: HRESULT; stdcall; // Unfortunately, it's not possible to customize return code to be S_FALSE for simple function. // Otherwise DllCanUnloadNow could have been written like this: // procedure DllCanUnloadNow; safecall; begin try if { it's OK to unload DLL } then Result := S_OK else Result := S_FALSE; except on E: Exception do Result := ConvertExceptionToHRESULT(E); end; end; ...Converting exception to HRESULT value will be done automatically by Delphi's RTL code.
Notes:
- Barebone converting to HRESULT may be insufficient for your needs. It's possible to customize it by overriding
SafeCallException
method. See Delphi help for more information. - COM also allow you to ship additional information with exception. See
SetErrorInfo
function.
Case 3: your own API
When you want to develop a new DLL which will be used by many applications ("common DLL") or if you want to write an application which may be extended with 3rd party DLLs ("plugins") - then you need to develop API, i.e. set of rules which will be used to communications between host and DLLs.It's a good idea to provide an informative and easy way to report and handle errors. An easy solution would be to use COM. That's because COM is relatively modern API, which provides a decent way to work with errors. COM also has support in many frameworks.
If you think that COM is an "overkill" for your application, then you have to develop your own API. It would be a good idea to use HRESULT as base of error handling part in your API. That's because HRESULT offers a good range of possible error values, it has additional support in Delphi (via safecall) and it's familiar for many Windows developers.
So, functions from your DLL may looks like this:
library Project2; uses Windows; procedure Init; safecall; // the same as: // function Init: HRESULT; stdcall; begin // your code end; function DoSomething(A1: Integer; const A2: WideString): Integer; safecall; // the same as: // function DoSomething(A1: Integer; const A2: WideString; our AResult: Integer): HRESULT; stdcall; begin // your code end; procedure Done; safecall; // the same as: // function Done: HRESULT; stdcall; begin // your code end; exports Init, DoSomething, Done; end;
Note: it's also a good idea to use interfaces instead of simple functions in your DLLs. Interfaces allow you to customize safecall handling by overriding
SafeCallException
method. Interfaces also allow you to simplify memory management and avoid using shared memory manager.Using exception tracer tool in DLLs
Many developers prefer to use exception tracer tool in their DLLs. Exception tracer collects information about problems in your code, allowing you to diagnose failures more easily.Remember what exception tracer does to your application:
A typical executable module with exception tracer (EurekaLog is used as example) |
Exception tracer includes its code in your module. It also injects some data - debug information and options. Both (code and data) are required for exception tracer to function.
When you have more than just single executable module - things become interesting. Exception tracer could be inserted into one module or into each module:
- There is single instance of exception tracer in application
- Each module has its own exception tracer code
First case is good when you can afford enabling exception tracer in host application. Centralized management will allow you to reduce performance cost when you have many DLLs. For example, consider application with 50 DLLs (keep in mind "plugins" scenario). Each exception must be analyzed by exception tracer. If each DLL has its own exception tracer - then each exception will be analyzed 50 times. A good idea would be to have only one instance of exception tracer, so information is collected only once. Any DLL can ask central exception tracer for information about exception.
Second case is good when host application is out of your control. Since you can not use exception tracer in host - then the only choice left is to add it to DLL. Each DLL will have its own isolated exception tracer.
Now, let's examine both cases on practice.
Note: this article will use EurekaLog 7.0.4.3 or higher as exception tracer.
Case 1: Single instance of exception tracer: "DLL" profile
This case require you to enable exception tracer for host application. You should do this in the same way as you do it for typical application without any DLLs. For example, if you have VCL Forms application as the host - then you need to enable EurekaLog for host application and set application type to "VCL Forms Application". This will add EurekaLog code and data into final .exe file. It would also set hook forForms.TApplication.HandleException
method, which will allow to automatically handle exceptions without writing any code.Now, the host has exception tracer. It will catch any exception regardless of where exception was raised. It will also collect information about each exception. Exceptions from DLL will be also analyzed by tracer in the host.
Each DLL must also has EurekaLog enabled and application type must be set to "DLL". Such settings will inject debug information into DLL, but will not include exception tracer code. Rather DLL will ask host application for information. Please note that majority of EurekaLog settings will be ignored, since there will be no EurekaLog code in your DLL.
Note: it's not strictly necessary to enable EurekaLog for DLLs in this example. You can just supply debug information and keep EurekaLog off. For example, you may create .map file or use JCL debug information. This will work fine.
Host application loads multiply DLLs with "DLL" profile (click image to enlarge/zoom in) |
Let's see this on practice. Create a new VCL application and place buttons to invoke functions from DLL.
... type TForm1 = class(TForm) Button1: TButton; Button2: TButton; Button3: TButton; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure Button3Click(Sender: TObject); private FDLL: HMODULE; end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.FormCreate(Sender: TObject); begin FDLL := LoadLibrary('Project2.dll'); Win32Check(FDLL <> 0); end; procedure TForm1.FormDestroy(Sender: TObject); begin FreeLibrary(FDLL); FDLL := 0; end; procedure TForm1.Button1Click(Sender: TObject); begin raise Exception.Create('Error Message'); end; procedure TForm1.Button2Click(Sender: TObject); var P: procedure; begin P := GetProcAddress(FDLL, 'Test1'); Win32Check(Assigned(P)); P; end; procedure TForm1.Button3Click(Sender: TObject); var P: procedure; begin P := GetProcAddress(FDLL, 'Test2'); Win32Check(Assigned(P)); P; end; ...Code is pretty simple: we load DLL on form's creating and unload it when form is gone. There are 3 buttons on the form to invoke different testing routines. First button raises exception in application itself. The 2nd and 3rd buttons raise exceptions in DLL.
Don't forget to enable EurekaLog for host and set application type to "VCL Forms Application". That's all.
Now, create a new DLL project:
library Project2; uses // Added automatically EAppDLL, // "DLL" profile // Added manually Windows, SysUtils, Classes; {$R *.res} procedure Test1; begin // This is just example! It's highly not recommended to do this. raise Exception.Create('Error Message'); end; procedure Test2; var Msg: String; begin // Recommended way: handle exceptions, do not let them escape DLL try raise Exception.Create('Error Message'); except on E: Exception do begin Msg := E.ToString + sLineBreak + E.StackTrace; MessageBox(0, PChar(Msg), 'Error', MB_OK or MB_ICONSTOP); end; end; end; exports Test1, Test2; end.This simple DLL exports 2 functions to test exceptions. First function raises exception and lets it escape DLL, so it will be catched by caller. In our test example caller would be the host application. Such approach is not recommended - as it's already explained above: you should never let exceptions escape DLL. This is done only for example (illustration). It will work correctly for our case, because DLL and exe are both compiled by the same compiler. This will not work properly for generic case. So, it's only suitable for testing.
Note: if you want to raise exception in DLL and catch it by the caller - then do not use DLLs. Use packages instead. Using packages will guarantee compatible compiler for host and DLL (package). It's also generally less problematic with all cross-modules communications.
Second function illustrate more correct approach: we catch exceptions in function and handle them somehow. For this example we will do very simple handling: just display error message with stack trace. More proper approach was discussed above: you should use some kind of error indication (such as boolean flag, HRESULT, etc.) to indicate failure condition to the caller.
Now, enable EurekaLog for this DLL and set application type to "DLL".
Note: EAppDLL unit will be added automatically when you set profile to "DLL".
Compile both host and DLL project, run host application.
Hit buttons 1-3.
Typical exception in host application Call stack shows only items for exe (click image to enlarge/zoom in) |
Exception escaped DLL Call stack shows mixed exe/DLL lines Notice line numbers for routine in DLL (click image to enlarge/zoom in) |
Please note that last case was a simple example of trivial exception handling in DLL. You may be not satisfied with looks of error dialog. You may want not just "error message", but complete bug report. To do this - you need to replace the call to
MessageBox
with a call to exception manager. Normally, it would be ExceptionManager.Handle
. However, there is no exception manager in our DLL. We'll show how to do this below (see "Working with frameworks and exception tracers in DLLs" section). Case 2: Multiply instances of exception tracer: "Standalone DLL" profile
This case does not require you to enable exception tracer for host application. You can do it, but it's not required. Typically this approach should be used only when you develop DLLs to be used in non-EurekaLog enabled host. If you have EurekaLog enable for the host - please try to implement case 1 approach above.Since host application do not necessary have exception tracer - you must to include tracer in each of your DLLs. Each DLL will have exception tracer. All tracers and DLLs will be independent of each other. Each exception will be catched by each exception tracer in each DLL.
Therefore, each DLL must has EurekaLog enabled and application type must be set to "Standalone DLL". Such settings will add exception tracer in DLL and inject debug information.
Host application loads multiply DLLs with "Standalone DLL" profile (click image to enlarge/zoom in) |
Let's see this on practice. We'll use the same host application for this example. Of course, it has EurekaLog enabled, but remember that it's not necessary. You may turn EurekaLog off for host application, if you want. Actually, let's do this for the sake of better illustration. So, open your host application project, disable EurekaLog for it and recompile (all source code will remain the same as above).
We'll use the same DLL project for this example. We'll make only few changes. Open DLL project and change application type from "DLL" to "Standalone DLL". This will also replace
EAppType
unit in uses clause with multiply EurekaLog units. Also, go to dialogs options and change "None" to any dialog that you like. We will use "MS Classic" for this example.This could be all, but since now we have full exception tracer on board - why not ask it to handle exceptions? We can replace our old MessageBox with a call to exception manager. So full changed code will looks like:
library Project2; uses // Automatically generated: EMemLeaks, EResLeaks, EDialogWinAPIMSClassic, EDialogWinAPIEurekaLogDetailed, EDialogWinAPIStepsToReproduce, EDebugExports, EDebugJCL, ExceptionLog7, // Added manually: EExceptionManager, Windows, SysUtils, Classes; {$R *.res} procedure Test1; begin raise Exception.Create('Error Message'); end; procedure Test2; begin try raise Exception.Create('Error Message'); except ExceptionManager.ShowLastExceptionData; end; end; exports Test1, Test2; end.Save all and recompile. Run application and hit all 3 buttons:
|
|
This example gives you full EurekaLog support within DLL, but exe completely lacks any support. It doesn't even have debug information, so even exception tracer from DLL is unable to display call stack for exe. Of course, this can be fixed by enabling EurekaLog for exe. Just remember that host application is not always under your control.
Working with frameworks and exception tracers in DLLs
The above sections assumed that you write DLLs without using any frameworks. If you use framework (such as VCL or IntraWeb), then your actions will be slightly different. That's because framework already contain some sort of exception handling code.A general concept would be the same. You can use either "DLL" or "Standalone DLL" profiles for your DLLs. So the above facts would be the same. Additionally, you have to configure DLLs for your framework. EurekaLog has support for common cases out of the box. For example, if you use Forms unit in your DLL (i.e. your DLL has forms) - then you need to hook
Application.HandleException
method. This can be done by enabling "VCL Forms application" option on Advanced/Code/Hooks page in EurekaLog project options. This is true for both "DLL" and "Standalone DLL" profiles.Note: "Standalone DLL" profile with "VCL Forms application" option on Hooks page is equal to "VCL Forms Application" profile. When you enable such options - DLL profile will be switched to "VCL Forms Application" profile. This is normal behavior. After all, a profile is just set of predefined options. If you change options to match another profile - it will be shown as used. There is no build-in profile for "DLL" profile with "VCL Forms application" option, so "DLL" profile will not be changed after enabling option.
Now, let's change our example to illustrate this on practice. As usual, host application will remain unchanged. All changes will be done for DLL.
Case 1: Single instance of exception tracer: "DLL" profile
Open DLL, enable EurekaLog and set application type to "DLL". Since we're going to use forms in our DLL - go do Advanced/Code/Hooks page in EurekaLog project options and enable "VCL Forms application" option.Now, create a new form for DLL, place a button to raise exception:
... type TForm2 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); end; implementation {$R *.dfm} procedure TForm2.Button1Click(Sender: TObject); begin raise Exception.Create('Error Message'); end; ...And change DLL code as:
library Project2; uses // Automatically generated by EurekaLog EAppDLL, // "DLL" profile EAppVCL, // "VCL Forms application" hook // Added manually EAppType, Windows, SysUtils, Classes, Unit2 in 'Unit2.pas' {Form2}; {$R *.res} procedure Test1; begin try raise Exception.Create('Error Message'); except on E: Exception do // Ask exception manager in host application to process this exception _ExceptionManagerHandle(E, ExceptAddr); end; end; procedure Test2; var Form: TForm2; begin try Form := TForm2.Create(nil); try Form.ShowModal; finally FreeAndNil(Form); end; except on E: Exception do // Ask exception manager in host application to process this exception _ExceptionManagerHandle(E, ExceptAddr); end; end; exports Test1, Test2; end.Normally, if you want to ask EurekaLog to process exception (display error dialog with bug report, send it to developer, etc.) - then you have to call
ExceptionManager.Handle
. However, we can not do this in our case, because we've used "DLL" profile, which means no exception tracer (and no exception manager) in our DLL. That's why we use _ExceptionManagerHandle
function instead of ExceptionManager.Handle
._ExceptionManagerHandle
function is a lightweight exception manager. If there is exception tracer code in current module - the function will invoke it (i.e. ExceptionManager.Handle
). If there is no tracer in the module - the function will try to invoke exception manager from host application. If there is no tracer in host application either - the function will act as if EurekaLog was disabled.Therefore, you can use
_ExceptionManagerHandle
function to handle exceptions when you don't know if there will be EurekaLog in your project. This function will automatically use suitable way to process exceptions.Okay, so the first function in our DLL will just raise exception in DLL function. The difference with first example is that we handle it properly now: there is try/except block which handles exception by asking exception manager from host application to perform full processing (displaying bug report, sending it to developer, etc.).
Second function will create and show a modal form. There is no exception inside function itself, but form contains button to raise exception. This exception will not be catched by our try/except block, because exceptions in form's event handlers are handled by VCL framework. That's why we need
EAppVCL
unit (it contains hooks for VCL). Try/except block in second function will catch exceptions only for form's creating or destroying.That's all. Save all and compile. Run application and hit all buttons. First button is not changed at all. Second button and third button behave differently:
Button #2: Exception did not escape DLL, it was handled by DLL by displaying complete bug report Call stack shows mixed exe/DLL lines (click image to enlarge/zoom in) |
Button #3: Exception was raised by form. It was handled by VCL. EurekaLog hook displays full bug report Call stack shows mixed exe/DLL lines (click image to enlarge/zoom in) |
Case 2: Multiply instances of exception tracer: "Standalone DLL" profile
Open DLL, enable EurekaLog and set application type to "Standalone DLL". Since we're going to use forms in our DLL - go do Advanced/Code/Hooks page in EurekaLog project options and enable "VCL Forms application" option. Also change dialog to "MS Classic" or any other desired type.Note: a combination of "Standalone DLL" profile + "VCL Forms application" hook will set the same options as "VCL Forms Application" profile. That's why you'll see "VCL Forms Application" instead of "Standalone DLL" in "Application type" option when you open project settings next time. That's totally expected behavior. You can also initially only switch profile to "VCL Forms Application" and do nothing else - that's because this profile will set dialogs and turn on hooks for VCL.
The code for both DLL and exe remain unchanged from previous example. Run application and hit the buttons. You should see the same behavior and dialogs as in previous example.
Note that even if visual appearance seems the same - the internals are working differently now. DLL now has its own exception tracer.
_ExceptionManagerHandle
function will just invoke ExceptionManager.Handle
in DLL. It will not try to communicate with exe host.