2007. július 15., vasárnap
How to call procedures by name using an array of records
Problem/Question/Abstract:
I have a unit that all it does is store SQL statement for me to load and right now I'm doing:
if ReportName = "Some_Report_Name" then
LoadSomeReportNameSql;
else if ReportName = "Some_Other_Report" then
LoadSomeOtherReportSql;
I have about 200 reports so far...would a case statment be faster? I would, of course, change the identifier for the report to a numeric identifier, rather than a string identifier. My concern is that there will begin to be a very noticable difference once I get up to the 500 or so reports.
Answer:
With that many reports, there are two better solutions than an if/ then or case statement.
Solve 1:
An array of records containing the report name and report procedure might be faster and easier to maintain. The list could be sorted on the report name, and a binary search algorithm could be used to quickly locate the correct report procedure to execute.
This method is not new, but works very well. It is not automagical, so the programmer has to do some typing. It could be improved in a myriad of ways, like array of const parameters, TVarRec results, action identifers and encapsulation in a class. The last could get hairy if you expect that class to serve objects of other classes as well, but it is possible.
unit NamedFunctions;
interface
const
MaxFuncs = 3;
MaxFuncName = 13;
type
TFuncRange = 1..MaxFuncs;
TNamedFunc = function(args: string): string;
TFuncName = string[MaxFuncName];
TFuncInfo = record
Name: TFuncName;
Func: TNamedFunc
end; { TNamedFunc }
TFuncList = array[TFuncRange] of TFuncInfo;
function XSqrt(args: string): string;
function XUpStr(args: string): string;
function XToggle(args: string): string;
const
{This list must be sorted for the function to be found}
FuncList: TFuncList = ((Name: 'xsqrt'; Func: XSqrt), (Name: 'xtoggle'; Func: XToggle), (Name: 'xupstr'; Func: XUpStr));
function ExecFunc(AName: TFuncName; args: string): string;
implementation
uses
Dialogs, SysUtils;
function ExecFunc(AName: TFuncName; args: string): string;
{ Binary search is overkill for a small number of functions. }
var
CompRes, i, j, m: integer;
Found: boolean;
begin
AName := LowerCase(AName);
i := 1;
j := MaxFuncs;
m := (i + j) shr 1;
Found := false;
while not Found and (i <= j) do
begin
CompRes := AnsiCompareStr(AName, FuncList[m].Name);
if CompRes < 0 then
j := m - 1
else if CompRes > 0 then
i := m + 1
else
Found := true;
if not Found then
m := (i + j) shr 1
end;
if Found then
Result := FuncList[m].Func(args)
else
begin
Result := '';
ShowMessage('Function ' + AName + ' not found in list')
end;
end;
function XSqrt(args: string): string;
var
value: real;
begin
value := 0;
try
value := StrToFloat(args)
except
on EConvertError do
ShowMessage(args + ' is not a valid real number (XStr)')
end;
if value >= 0 then
Result := FloatToStr(sqrt(value))
else
begin
Result := '0.0';
ShowMessage('Negative number passed to XSqrt')
end;
end;
function XUpStr(args: string): string;
begin
Result := UpperCase(args)
end;
function XToggle(args: string): string;
{ Anything other than 'TRUE' or 'T' is assumed false. }
begin
args := UpperCase(args);
if (args = 'TRUE') or ((length(args) = 1) and (args = 'T')) then
Result := 'FALSE'
else
Result := 'TRUE'
end;
end.
Solve 2:
Another way to go would be to use the GetProcAddress Win32 API function to locate the report procedure based on the report name. This way you could store the report names and report procedure names in a text file or database. (Tip: EXEs can export routines just like DLLs can. GetProcAddress only finds exported routine names). The code might look something like this (off the top of my head...):
unit MyReports;
interface
type
TReportProcedure = procedure;
procedure LoadSomeReportNameSql;
procedure LoadSomeOtherReportSql;
procedure ExecuteReport(AReportName: string);
implementation
procedure ExecuteReport(AReportName: string);
var
ReportProc: TReportProcedure;
ProcPointer: TFarProc;
begin
{Table contains two columns: "Report Name" and "Report Procedure". Primary key is "Report Name"}
try
Table1.Open;
if Table1.FindKey([AReportName]) then
begin
{Get the address of the exported report procedure}
ProcPointer := GetProcAddress(HInstance, Table1.FieldByName('Report Procedure').AsString);
if Assigned(ProcPointer) then
begin
ReportProcedure := TReportProcedure(ProcPointer);
ReportProcedure;
end;
end;
finally
Table1.Close;
end;
end;
procedure LoadSomeReportNameSql;
begin
end;
procedure LoadSomeOtherReportSql;
begin
end;
exports
LoadSomeReportNameSql;
LoadSomeOtherReportSql;
end.
Feliratkozás:
Megjegyzések küldése (Atom)
Nincsenek megjegyzések:
Megjegyzés küldése