Delphi – getting the sourcefile name from a source file with an Assert trick using EAssertionFailed
Posted by jpluimers on 2009/08/21
For one of our projects, we have a set of configuration files that we want to be able to locate automatically.
(in this case they are XML files as most development environments have one way or the other to do some XML Data Binding that maps objects or interfaces to/from XML; in Delphi you can use the XML Data Binding Wizard for that: it has been there since Delphi 6, and CodeBeach has a nice Delphi XML data binding wizard video tuturoial on how to use that wizard)
We usually have a few instances of those config files hanging around:
- for testing; this one goes into the same directory as the Delphi source file generated by the XML Data Binding Wizard
- for the Delphi IDE (which might be relative to the Delphi IDE .exe)
- for the application (which usually is relative to the Appliation .EXE, or user settings directory)
If a specific version (higher numbers in the list above) does not exist, we want to revert to a more generic version (with lower numbre in the list above).
Ultimately we want to revert to the one for testing, which is in the subdirectory of a specific sourcefile.
Since all our development machines are configured in a similar way (i.e. having the same root path like C:\DEVELOP for our sources), it would be nice to be able to somehow automatically detect the file name of a source file.
There is a way in Delphi where you can get the name of a full source file: when an assertion failes, then Delphi raises an EAssertionFailed exception.
That exception contains the full path to the source file in which the assertion failed.
Note that this requires your application to be compiled with the $ASSERTIONS ON directive, which is the default, and also enabled for our production builds.
So this statement:
Assert(False, 'no valid configuration file found relative to ' + Application.ExeName);
will result in a message like this:
no valid configuration file found relative to C:\Program Files\CodeGear\RAD Studio\6.0\bin\bds.exe (C:\develop\ActiveMQ\ActiveMQDemo\common\src\ConfigHelperUnit.pas, line 84)
Knowing this, we need a function like GetSourceFileName, preferably on the EAssertionFailed class that gets us the source filename that is inside the parentheses.
Having a function like that, would allow for code like this:
function GetConfigPathName(const Application: TApplication): string; overload; var TriedFileNames: IStringListWrapper; SourceFileName: string; begin TriedFileNames := TStringListWrapper.Create(); Result := FindConfigPathName(Application, TriedFileNames); try AssertConfigFileNameExists(Result, Application.ExeName, TriedFileNames); except on E: EAssertionFailed do begin SourceFileName := E.GetSourceFileName(); Result := FindConfigPathName(SourceFileName, TriedFileNames); AssertConfigFileNameExists(Result, Application.ExeName, SourceFileName, TriedFileNames); end; end; end;
Since we want the function to be on EAssertionFailed, I have written a class helper like this:
unit AssertionFailedHelperUnit; interface uses SysUtils; type TAssertionFailedHelper = class helper for EAssertionFailed public function GetSourceFilename: string; end; implementation uses SysConst; function TAssertionFailedHelper.GetSourceFilename: string; var Mask: string; ExceptionMessage: string; OpeningParenthesesPosition: Integer; begin //no valid configuration file found relative to C:\Program Files\CodeGear\RAD Studio\6.0\bin\bds.exe (C:\develop\ActiveMQ\ActiveMQDemo\common\src\ConfigHelperUnit.pas, line 84) // SAssertError = '%s (%s, line %d)'; // now create this string " (, line 0)" and use it to parse from right to left Mask := Format(SAssertError, ['', '', 0]); ExceptionMessage := Self.Message; // remove the closing ")" if (ExceptionMessage <> '') then begin Delete(ExceptionMessage, Length(ExceptionMessage), 1); Delete(Mask, Length(Mask), 1); // remove the "0" from the Mask Delete(Mask, Length(Mask), 1); end; while (ExceptionMessage <> '') and (CharInSet(ExceptionMessage[Length(ExceptionMessage)], ['0'..'9'])) do Delete(ExceptionMessage, Length(ExceptionMessage), 1); // from the right side, remove the ", line " portion while (ExceptionMessage <> '') and (ExceptionMessage[Length(ExceptionMessage)] = Mask[Length(Mask)]) do begin Delete(ExceptionMessage, Length(ExceptionMessage), 1); Delete(Mask, Length(Mask), 1); end; // now find the opening "(" OpeningParenthesesPosition := Length(ExceptionMessage); while (ExceptionMessage <> '') and (ExceptionMessage[OpeningParenthesesPosition] <> '(') do Dec(OpeningParenthesesPosition); Delete(ExceptionMessage, 1, OpeningParenthesesPosition); Result := ExceptionMessage; end; end.
This unit has made life for us a lot easier.
Hope it does it for you as well :-)