This is the top level book/outline node for some general ABL (formerly known as Progress 4GL) code samples. This includes code snippets, code examples, utility programs, tricks, techniques, and patterns. There are other books and libraries for more specific topics, such as Win32 API programming and OO programming. This book is a more general catch-all.
Using sockets with the 4gl and classes is an "interesting" exercise. This book contains examples of how to use classes and sockets for a client and server system. There are examples of how to use the client to connect to a web page and download the source. There is also a client / server system that uses XML to pass messages between the server and clients.
There are two types of socket, a server socket and a client socket. The server socket spawns a client socket when a client connects. Each client socket is managed by the ClientSocket class. So, when a client connects to the server, the server creates a client connection socket.
Each socket has it's own read-response handler in the form of a persistent procedure.
Each socket has a message "terminator", so that a block of data can be sent en masse. Normally, this terminator is set to "~n", so each line of text will be published as a new message.
Every time a complete message is received, the message is published as "MessageIn". By default, MessageIn resides in the .w or .p that created the socket class. The following parameters are passed to MessageIn:
Socket: The handle of the socket that received the message
Message Type: The type of message (user defined)
Message From: The name of the client that sent the message
Message To: The name of the client the message was sent to
Message Subject: The subject of the message
Message Body: The body of the message
By default, the complete message is always in the Message body. If the client and server are using XML, then all parameters will be filled appropriately.
To connect a client to a server, the Connect() method should be used. There are several overrides to the Connect method:
Connect(): Connect to the host and port specified in the Host and Port properties
Connect(Port): connect to the host specified by the Host Property and the supplied Port parameter
Connect(Host,Port): connect to the supplied host and supplied Port parameters
There are the following examples supplied:
GetWebPageSource.w
Uses a client socket to connect to a web page
MessageClient.w
MessageServer.w
Shows how you can implement a 4GL-based XML messaging system that can be used to PUB/SUB across session boundaries
Unzip the dotr.com.zip file into a directory in the propath. You must keep the com\dotr directory structure.
The latest source code can be checked out from svn://oehive.org/oosockets/trunk
All comments are welcome and appreciated.
All the code supplied here is licensed under the BSD license. You are free to copy and use the code as you see fit. However, I would welcome any code changes so that I can make this a better example for all OE users. Thanks !
I've been programming in Java and C++ for so long that when I need to write ABL (formerly 4GL), I'm sometimes left scratching my head.
Here's some simple Java, which makes use of a "static" ("class") method to validate someValue:
String validationMessage = com.joanju.Widget.validateSKU(someValue);
How would I write the equivalent in ABL?
ABL lacks static/class methods or functions. I suspect that such a feature couldn't be easily added to the compiler because of the lack of a class loader in the platform runtime, but I'm just guessing.
That leaves me with having to use Singletons. I don't need Singletons in the strict sense of the formal pattern - I just need them as method libraries.
There are plenty of ways to implement this sort of thing in ABL, but I had some very specific goals:
I ended up having to use two of my old arch enemies: an include file and a global variable. The include file was necessary to "fake" some syntactic sugar for brevity. The global variable was necessary to implement something that looks a tiny bit like a platform's class loader.
Here's my sample test case:
{com/joanju/singletonpp.i "com/joanju/widget.p" widget}
{com/joanju/singletonpp.i "com/joanju/grommet.p" grommet}
run testSet in widget ("Hello world!").
display dynamic-function("testGet" in widget) format "x(30)".
run singletontest2.p.
The grommet.p program is empty, and I referenced it only so that I could see that my preprocessed code wasn't getting too fat with each new reference to the include file.
The singletontest2.p program just shows that the existing widget.p is found and re-used:
{com/joanju/singletonpp.i "com/joanju/widget.p" widget}
display dynamic-function("testGet" in widget) format "x(30)".
The include file uses an include guard to ensure that the global handle to a PP manager is defined and checked just once in each compile unit. The include contains the code for defining the handle and fetching the PP, giving us the much needed brevity in the main program code. The conditional RUN statement will never actually run - it is there just to make sure that Callgraph and COMPILE..XREF can do their jobs.
&if defined(com_joanju_singleton_pps) = 0 &then
&global-define com_joanju_singleton_pps
define new global shared variable comJoanjuSingletonPPManager as handle no-undo.
if not valid-handle(comJoanjuSingletonPPManager) then
run com/joanju/singletonmanager.p persistent set comJoanjuSingletonPPManager.
&endif
define variable {2} as handle no-undo.
run getSingleton in comJoanjuSingletonPPManager ("{1}", output {2}).
/* Next statement is just an xref from this program to "{1}". */
if not valid-handle({2}) then run "{1}" persistent set {2}.
Finally, the singletonmanager.p itself is dead simple:
define temp-table singleton no-undo
field progName as character
field progHandle as handle
index idx1 is unique progName.
procedure getSingleton:
define input parameter name as character no-undo.
define output parameter newHandle as handle no-undo.
find singleton where progName = name no-error.
if available singleton and valid-handle(singleton.progHandle) then
assign newHandle = singleton.progHandle.
else do:
run value(name) persistent set newHandle.
if not available singleton then do:
create singleton.
assign singleton.progName = name.
end.
assign singleton.progHandle = newHandle.
end.
end procedure.
Although there are a myriad of ways to implement function libraries and find and reference them in ABL, this method satisfies my goals:
COMPILE..XREF entry from the program to the PP.
RUN PERSISTENT SET, the RUN IN handle, and the DYNAMIC-FUNCTION(name IN handle) calls.
When you are looking for a way to improve the maintainability, the efficiency, the speed, the general quality of your ABL code or for new ways to do things through small changes easily implemented, here is the place to look for.
This place gathers information that is lost deep in the OpenEdge documentation, that comes from around the OpenEdge web or that is directly generated from the brain of the one who submits it.
These tips and tricks can be useful when you write new code and/or when you are refactoring or modernizing existing code.
Some entries will be old stuff to some, but if it's here, it's that at least someone found this information useful in his ABL coding work.
As a side note for speed tips, it is also good to know that faster code constructs can often result in smaller .r files thus providing an additional speed gain (file load time).
Assigning values to any number of variables is always faster when grouped in an ASSIGN statement compared to being set independently.
This does not always come naturally to people coming from other programming languages.
In my tests, ASSIGN of simple variables is 20 to 40% faster (when you remove the time it takes for the loop itself i.e. the execution time of an empty loop).
You can have some fun confirming this by commenting / uncommenting lines in the following code
DEFINE VARIABLE iTest1 AS INTEGER NO-UNDO. DEFINE VARIABLE iTest2 AS INTEGER NO-UNDO. DEFINE VARIABLE iTest3 AS INTEGER NO-UNDO. DEFINE VARIABLE iTest4 AS INTEGER NO-UNDO. DEFINE VARIABLE iIndex AS INTEGER NO-UNDO. ETIME(TRUE). DO iIndex = 1 TO 100000: /* iTest1 = 2. iTest2 = 2. */ /* ASSIGN iTest1 = 2 iTest2 = 2 . */ /* iTest1 = 3. iTest2 = 3. iTest3 = 3. */ /* ASSIGN iTest1 = 3 iTest2 = 3 iTest3 = 3 . */ iTest1 = 4. iTest2 = 4. iTest3 = 4. iTest4 = 4. /* ASSIGN iTest1 = 4 iTest2 = 4 iTest3 = 4 iTest4 = 4 . */ END. MESSAGE ETIME VIEW-AS ALERT-BOX.
To make code easier to read when you want to perform some logic on the first TRUE condition that you encounter, instead of using cascading IF THEN ELSE, you can use a CASE TRUE statement (it works because a CASE enters the first block that has a condition that matches the criteria of the CASE).
All of the WHEN criteria must be code constructs that evaluates to a LOGICAL.
This trick works great for range conditions, but it can also be used in many other contexts.
DEFINE VARIABLE deSomePercentage AS DECIMAL NO-UNDO INITIAL 42.9.
DEFINE VARIABLE lAccept20And60 AS LOGICAL NO-UNDO INITIAL FALSE.
CASE TRUE:
WHEN lAccept20And60 AND (deSomePercentage >= 20 AND deSomePercentage <= 60)
THEN DO:
/* do some logic */
END.
WHEN deSomePercentage > 20 AND deSomePercentage < 60
THEN DO:
/* do some logic */
END.
WHEN deSomePercentage > 60
THEN DO:
/* do some logic */
END.
OTHERWISE DO:
/* do some logic */
END.
END CASE.The DO loop constructs are much faster than the REPEAT loop (when you don't need the extra that REPEAT provides... if you don't know what the extra is, you probably don't need it).
You can have some fun confirming this with the following code (It doesn't take many loops to have execution times in seconds!):
DEFINE TEMP-TABLE aTempTable NO-UNDO
FIELD cTest AS CHARACTER.
DEFINE VARIABLE i AS INTEGER NO-UNDO.
DEFINE VARIABLE cResults AS CHARACTER NO-UNDO INITIAL "".
DEFINE VARIABLE iMethodTime AS INTEGER NO-UNDO.
&SCOPED-DEFINE NumLoops 1000000
&SCOPED-DEFINE NumLoops2 100000
ETIME(TRUE).
REPEAT i = 1 TO {&NumLoops}:
END.
iMethodTime = ETIME.
cResults = cResults + "~n Method 1:" + STRING(iMethodTime).
ETIME(TRUE).
DO i = 1 TO {&NumLoops}:
END.
iMethodTime = ETIME.
cResults = cResults + "~n Method 2:" + STRING(iMethodTime).
/*test with record creation*/
ETIME(TRUE).
REPEAT i = 1 TO {&NumLoops2}:
CREATE aTempTable.
END.
iMethodTime = ETIME.
cResults = cResults + "~n Method 1:" + STRING(iMethodTime).
EMPTY TEMP-TABLE aTempTable.
ETIME(TRUE).
DO i = 1 TO {&NumLoops2}:
CREATE aTempTable.
END.
iMethodTime = ETIME.
cResults = cResults + "~n Method 2:" + STRING(iMethodTime).
MESSAGE cResults VIEW-AS ALERT-BOX.
A couple of results on my machine (btw, both methods are faster using 10.1C then using 10.1B or 10.1A...):
Method 1:437
Method 2:281 - 35% faster
Method 1:2641
Method 2:2109 - 20% faster
The fastest way to aggregate a list of character values when all values are non null is to always add the delimiter inside the loop and remove it afterward with SUBSTRING (method 3).
You can have some fun confirming this with the following code:
&SCOPED-DEFINE NumLoops 10000
&SCOPED-DEFINE SmallListLength 20
DEFINE VARIABLE cExpressionToAdd AS CHARACTER NO-UNDO INITIAL "ab".
DEFINE VARIABLE i AS INTEGER NO-UNDO.
DEFINE VARIABLE j AS INTEGER NO-UNDO.
DEFINE VARIABLE cList AS CHARACTER NO-UNDO.
DEFINE VARIABLE iMethodTime AS INTEGER NO-UNDO.
DEFINE VARIABLE cResults AS CHARACTER NO-UNDO INITIAL "".
cResults = cResults + "~n One long list:".
cList = "".
ETIME(TRUE).
DO i = 1 TO {&NumLoops}:
cList = cList + MIN(",", cList) + cExpressionToAdd.
END.
iMethodTime = ETIME.
cResults = cResults + "~n Method 1:" + STRING(iMethodTime).
cList = "".
ETIME(TRUE).
DO i = 1 TO {&NumLoops}:
cList = cList + (IF cList <> "":U
THEN ","
ELSE "") + cExpressionToAdd.
END.
iMethodTime = ETIME.
cResults = cResults + "~n Method 2:" + STRING(iMethodTime).
cList = "".
ETIME(TRUE).
DO i = 1 TO {&NumLoops}:
cList = cList + "," + cExpressionToAdd.
END.
cList = SUBSTRING(cList, 2).
iMethodTime = ETIME.
cResults = cResults + "~n Method 3:" + STRING(iMethodTime).
cResults = cResults + "~n~n Many small lists:".
ETIME(TRUE).
DO j = 1 TO {&NumLoops}:
cList = "".
DO i = 1 TO {&SmallListLength}:
cList = cList + MIN(",", cList) + cExpressionToAdd.
END.
END.
iMethodTime = ETIME.
cResults = cResults + "~n Method 1:" + STRING(iMethodTime).
ETIME(TRUE).
DO j = 1 TO {&NumLoops}:
cList = "".
DO i = 1 TO {&SmallListLength}:
cList = cList + (IF cList <> "":U
THEN ","
ELSE "") + cExpressionToAdd.
END.
END.
iMethodTime = ETIME.
cResults = cResults + "~n Method 2:" + STRING(iMethodTime).
ETIME(TRUE).
DO j = 1 TO {&NumLoops}:
cList = "".
DO i = 1 TO {&SmallListLength}:
cList = cList + "," + cExpressionToAdd.
END.
cList = SUBSTRING(cList, 2).
END.
iMethodTime = ETIME.
cResults = cResults + "~n Method 3:" + STRING(iMethodTime).
MESSAGE cResults VIEW-AS ALERT-BOX.
Some results on my machine:
One long list:
Method 1:140
Method 2:141
Method 3:109 (~20% faster)
Many small lists:
Method 1:594
Method 2:609
Method 3:500 (~18% faster)
When you want to verify that a character value does not contain any character (i.e. it is "" or ?), using the construct TRUE <> (someCharacterExpression > "") is your best buy (unless, in the specific context, you're certain that someCharacterExpression will have a value of "" most of the time).
You can have some fun confirming this with the following code (it could take ~30 seconds to run) that shows 4 different ways to accomplish this task and the run time according to the value of someCharacterExpression (either "", ?, or any string):
DEFINE VARIABLE i AS INTEGER NO-UNDO.
DEFINE VARIABLE j AS INTEGER NO-UNDO.
DEFINE VARIABLE ct AS CHARACTER NO-UNDO.
DEFINE VARIABLE cResult AS CHARACTER NO-UNDO.
DEFINE VARIABLE iETIME AS INTEGER NO-UNDO.
DEFINE VARIABLE iLoopTime AS INTEGER NO-UNDO.
&SCOPED-DEFINE LoopSize 1000000
ETIME(TRUE).
DO i = 1 TO {&LoopSize}:
END.
iLoopTime = ETIME. /* loop execution time */
DO j = 1 TO 3:
CASE j:
WHEN 1 THEN ASSIGN
ct = ""
cResult = "For void: ".
WHEN 2 THEN ASSIGN
ct = ?
cResult = cResult + "~nFor ---?: ".
WHEN 3 THEN ASSIGN
ct = "asilhjdgfikh sdf lsidfh kldsf"
cResult = cResult + "~nFor char: ".
END CASE.
ETIME(TRUE).
DO i = 1 TO {&LoopSize}:
(ct > "") <> TRUE. /* 1 */
END.
iETIME = ETIME.
cResult = cResult + " #1 = " + STRING(iETIME - iLoopTime).
ETIME(TRUE).
DO i = 1 TO {&LoopSize}:
(ct = ? OR ct = "":U). /* 2 */
END.
iETIME = ETIME.
cResult = cResult + " #2 = " + STRING(iETIME - iLoopTime).
ETIME(TRUE).
DO i = 1 TO {&LoopSize}:
(ct = "":U OR ct = ?). /* 3 */
END.
iETIME = ETIME.
cResult = cResult + " #3 = " + STRING(iETIME - iLoopTime).
ETIME(TRUE).
DO i = 1 TO {&LoopSize}:
TRUE <> (ct > ""). /* 4 */
END.
iETIME = ETIME.
cResult = cResult + " #4 = " + STRING(iETIME - iLoopTime).
END.
MESSAGE cResult VIEW-AS ALERT-BOX.Note that TRUE <> (someCharacterExpression > "") is consistently faster than (someCharacterExpression > "") <> TRUE. (interesting...)
Here's a couple of results on my machine:
For void: #1 = 812 #2 = 1016 #3 = 578 #4 = 781
For ---?: #1 = 672 #2 = 594 #3 = 875 #4 = 593
For char: #1 = 844 #2 = 1031 #3 = 1031 #4 = 766
For void: #1 = 812 #2 = 1016 #3 = 578 #4 = 734
For ---?: #1 = 657 #2 = 593 #3 = 875 #4 = 610
For char: #1 = 843 #2 = 1016 #3 = 1047 #4 = 750
For void: #1 = 828 #2 = 1016 #3 = 578 #4 = 750
For ---?: #1 = 656 #2 = 594 #3 = 859 #4 = 610
For char: #1 = 828 #2 = 1031 #3 = 1031 #4 = 766
When you want to verify that a character value contains characters, using the construct:
someCharacterValue > ""
is, on average in my test, 10% (for a void string) to 27% (for a ? string) faster than the second fastest construct (not to mention that it is also shorter to type; not to mention that it is also 33% to 50% faster then the slowest of the constructs presented here) - running on an Intel P4; maybe someone could confirm the same type of percentage when code is run on other processors.
Sample code:
DEFINE VARIABLE i AS INTEGER NO-UNDO.
DEFINE VARIABLE j AS INTEGER NO-UNDO.
DEFINE VARIABLE ct AS CHARACTER NO-UNDO.
DEFINE VARIABLE cResult AS CHARACTER NO-UNDO.
DEFINE VARIABLE iLoopTime AS INTEGER NO-UNDO.
DEFINE VARIABLE iETIME AS INTEGER NO-UNDO.
&SCOPED-DEFINE LoopSize 1000000
ETIME(TRUE).
DO i = 1 TO {&LoopSize}:
END.
iLoopTime = ETIME. /* loop execution time */
DO j = 1 TO 3:
CASE j:
WHEN 1 THEN ASSIGN
ct = ""
cResult = cResult + "~nFor void: ".
WHEN 2 THEN ASSIGN
ct = ?
cResult = cResult + "~nFor ---?: ".
WHEN 3 THEN ASSIGN
ct = "asilhjdgfikh sdf lsidfh kldsf"
cResult = cResult + "~nFor char: ".
END CASE.
ETIME(TRUE).
DO i = 1 TO {&LoopSize}:
ct <> "" AND ct <> ?. /* 1 */
END.
iETIME = ETIME.
cResult = cResult + " #1 = " + STRING(iETIME - iLoopTime).
ETIME(TRUE).
DO i = 1 TO {&LoopSize}:
ct > "". /* 2 */
END.
iETIME = ETIME.
cResult = cResult + " #2 = " + STRING(iETIME - iLoopTime).
ETIME(TRUE).
DO i = 1 TO {&LoopSize}:
ct <> ? AND ct <> "". /* 3 */
END.
iETIME = ETIME.
cResult = cResult + " #3 = " + STRING(iETIME - iLoopTime).
ETIME(TRUE).
DO i = 1 TO {&LoopSize}:
LENGTH(ct) > 0. /* 4 */
END.
iETIME = ETIME.
cResult = cResult + " #4 = " + STRING(iETIME - iLoopTime).
END.
MESSAGE cResult VIEW-AS ALERT-BOX.
Two runs on my machine:
For void: #1 = 594 #2 = 532 #3 = 1078 #4 = 641
For ---?: #1 = 891 #2 = 390 #3 = 594 #4 = 532
For char: #1 = 1062 #2 = 548 #3 = 1062 #4 = 641
For void: #1 = 609 #2 = 563 #3 = 1047 #4 = 657
For ---?: #1 = 875 #2 = 391 #3 = 609 #4 = 516
For char: #1 = 1094 #2 = 531 #3 = 1110 #4 = 641
When adding records to a temp-table having at least a unique index, it can be faster to trap the ABL error generated on a collision instead of using a CAN-FIND prior to creating each record.
This tip works when the expected number of "collisions" is expected to be relatively low.
In the example below, with no collision at all, the version without CAN-FIND is faster by about 32%. This number decreases slowly the more collisions there are until both methods are almost equal with 30% collisions.
Note also that in the sample, using a NO-UNDO temp-table and deleting the problematic record was faster than using an UNDO temp-table and undoing the record addition on a fail.
DEFINE TEMP-TABLE ttMetricValue NO-UNDO
FIELD cMetricLiteralValue AS CHARACTER
FIELD deMetricDecimalValue AS DECIMAL
INDEX ByMetricDecimalValue IS PRIMARY UNIQUE deMetricDecimalValue.
&SCOPED-DEFINE FirstCase TRUE
/*&SCOPED-DEFINE FirstCase FALSE*/
DEFINE VARIABLE i AS INTEGER NO-UNDO.
ETIME(TRUE).
DO i = 1 TO 10000: /* 328 484 */
&IF {&FirstCase} = TRUE &THEN
IF NOT CAN-FIND(FIRST ttMetricValue NO-LOCK WHERE ttMetricValue.deMetricDecimalValue = i)
THEN DO:
CREATE ttMetricValue.
ASSIGN
ttMetricValue.deMetricDecimalValue = i
ttMetricValue.cMetricLiteralValue = STRING(i).
END.
&ELSE
CREATE ttMetricValue.
ASSIGN
ttMetricValue.deMetricDecimalValue = i
ttMetricValue.cMetricLiteralValue = STRING(i)
NO-ERROR.
IF ERROR-STATUS:NUM-MESSAGES > 0 /* we'll let Progress validate the uniqueness */
THEN DELETE ttMetricValue.
&ENDIF
END.
/* uncomment next block to test the speed with duplicates */
/*
DO i = 1 TO 3000: /*with 3000 duplicates (30%), both methods are about equal in speed. With less than that, the suggested method is faster.*/
&IF {&FirstCase} = TRUE &THEN
IF NOT CAN-FIND(FIRST ttMetricValue NO-LOCK WHERE ttMetricValue.deMetricDecimalValue = i)
THEN DO:
CREATE ttMetricValue.
ASSIGN
ttMetricValue.deMetricDecimalValue = i
ttMetricValue.cMetricLiteralValue = STRING(i).
END.
&ELSE
CREATE ttMetricValue.
ASSIGN
ttMetricValue.deMetricDecimalValue = i
ttMetricValue.cMetricLiteralValue = STRING(i)
NO-ERROR.
IF ERROR-STATUS:NUM-MESSAGES > 0 /* we'll let Progress validate the uniqueness */
THEN DELETE ttMetricValue.
&ENDIF
END.
*/
MESSAGE ETIME VIEW-AS ALERT-BOX.Use FORWARD-ONLY = TRUE on a QUERY that just goes from a record to the next (as a "FOR EACH" does).
Note that FORWARD-ONLY = FALSE is the default for queries, even the default query of a DATA-SOURCE (for ProDataSet) - this last type of query can be changed using "DATA-SOURCE someDataSource:QUERY:FORWARD-ONLY = TRUE."
In the following test, the code structure is ~9.6% faster when using FORWARD-ONLY:
&SCOPED-DEFINE LoopSize 100000
DEFINE TEMP-TABLE ttTest NO-UNDO
FIELD cTest AS CHARACTER.
DEFINE VARIABLE i AS INTEGER NO-UNDO.
DEFINE VARIABLE iLoopTime AS INTEGER NO-UNDO.
DEFINE QUERY QueryTtTest FOR ttTest.
QUERY QueryTtTest:FORWARD-ONLY = TRUE.
QUERY QueryTtTest:QUERY-PREPARE("FOR EACH ttTest NO-LOCK WHERE INTEGER(cTest) > 10").
ETIME(TRUE).
DO i = 1 TO {&LoopSize}:
END.
iLoopTime = ETIME. /* loop execution time */
DO i = 1 TO {&LoopSize}:
CREATE ttTest.
ttTest.cTest = STRING(i).
END.
ETIME(TRUE).
QUERY QueryTtTest:QUERY-OPEN().
DO WHILE QUERY QueryTtTest:GET-NEXT():
END.
MESSAGE ETIME - iLoopTime VIEW-AS ALERT-BOX.The SEARCH function is 30 to 70% faster than FILE-INFO:FILE-NAME to test for file existence if you expect that the file searched exists most of the time (if the file does not exist, both methods are equivalent). (works for file only, not directory)
You can have some fun confirming this with the following code (It doesn't take many loops to have execution times in seconds!):
&SCOPED-DEFINE NumLoops 500
DEFINE VARIABLE cFileToFindList AS CHARACTER NO-UNDO INITIAL "pm\KBC\GetSpecialKitNo.p|c:\req.txt". /* pipe '|' delimited list */
DEFINE VARIABLE cFileToFind AS CHARACTER NO-UNDO.
DEFINE VARIABLE iNumFileToFinds AS INTEGER NO-UNDO.
DEFINE VARIABLE iFileToFindIndex AS INTEGER NO-UNDO.
DEFINE VARIABLE i AS INTEGER NO-UNDO.
DEFINE VARIABLE lFileExists AS LOGICAL NO-UNDO.
DEFINE VARIABLE iEmptyLoopTime AS INTEGER NO-UNDO.
DEFINE VARIABLE iMethodTime AS INTEGER NO-UNDO.
DEFINE VARIABLE cResults AS CHARACTER NO-UNDO INITIAL "--Results--~n".
ETIME(TRUE).
DO i = 1 TO {&NumLoops}:
END.
iEmptyLoopTime = ETIME.
iNumFileToFinds = NUM-ENTRIES(cFileToFindList, "|").
DO iFileToFindIndex = 1 TO iNumFileToFinds:
ASSIGN
cFileToFind = ENTRY(iFileToFindIndex, cFileToFindList, "|")
cResults = cResults + "~n~nFile:" + cFileToFind
.
ETIME(TRUE).
DO i = 1 TO {&NumLoops}: /* Method 1 */
ASSIGN
FILE-INFORMATION:FILE-NAME = cFileToFind
lFileExists = FILE-INFORMATION:FULL-PATHNAME <> ?
.
END.
iMethodTime = ETIME.
cResults = cResults + "~nMethod 1:" + STRING(iMethodTime - iEmptyLoopTime).
ETIME(TRUE).
DO i = 1 TO {&NumLoops}: /* Method 2 */
lFileExists = SEARCH(cFileToFind) <> ?.
END.
iMethodTime = ETIME.
cResults = cResults + "~nMethod 2:" + STRING(iMethodTime - iEmptyLoopTime).
END. /* DO iFileToFindIndex = 1 TO iNumFileToFinds */
MESSAGE cResults VIEW-AS ALERT-BOX.
A couple of results on my machine:
File:C:\path1\File1.w
Method 1:110
Method 2:31
File:relative\File2.w
Method 1:6968
Method 2:3969
File:Relative2\File3.p
Method 1:5922
Method 2:4250
File:c:\File4.txt
Method 1:93
Method 2:47