How to call procedures by name using an array of records (Views: 30)
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. |