by Jon Ripley, July 2006

The SYS statement allows you to call functions in the following DLLs (Dynamic Link Libraries) by name. Functions in all other DLLs must be called by their address in memory.
  • ADVAPI32.DLL
  • COMCTL32.DLL
  • COMDLG32.DLL
  • GDI32.DLL
  • KERNEL32.DLL
  • SHELL32.DLL
  • USER32.DLL
  • WINMM.DLL

The price for the convenience of calling functions in the above DLLs by name is speed. Calling a function by name is approximately 12 times slower than calling it by address. Where a function is called only a few times in a program this may not have much of an impact on the overall speed of a program. However, where a program makes extensive use of functions in the above DLLs, especially in tight loops, calling the functions by name instead of by address can have a significant performance cost.

Finding the address


To find the address of any function that can be called by name use the following routine:
      DEF FNSYS_NameToAddress(f$)
      LOCAL P%
      DIM P% LOCAL 5
      [OPT 0
      call f$
      ]
      =P%!-4+P%
Here f$ is the name of the function. FNSYS_NameToAddress returns the address of the function. You should read the addresses of functions you will use in the initialisation routine of your program.

The following example demonstrates using FNSYS_NameToAddress:
      REM Program initialisation
      _sleep% = FNSYS_NameToAddress("Sleep")
 
      REM Main loop
      REPEAT
        SYS _sleep%, 10
      UNTIL FALSE: REM Loop forever

Do not do the following, this is slower than calling a function by name:
        SYS FNSYS_NameToAddress("Sleep"), 10

Finding the address of BASIC I/O routines


FNSYS_NameToAddress can be used to read the address of the BASIC I/O routines as listed in the Using BASIC input/output section of the manual. To read the address of a BASIC I/O routine use code similar to the following:

      oswrch = FNSYS_NameToAddress("oswrch")
Here we read the address of the "oswrch" routine and store it in the variable oswrch. Limited practical uses of this technique do exist.

Proof


To demonstrate that calling a function by pointer is significantly faster than calling the same function by name the following proof is included.

      REM SYS Timing Test
      C%=1000000
      REPEAT
Here C% is set to the number of iterations for the timing loops.

        REM How long does an empty loop take?
        T%=TIME
        FOR I%=1 TO C%
          :
        NEXT
        O%=TIME - T%
        PRINT "Empty loop    : " ;(TIME - T%)/100"s"
Here we time how long an empty loop counting to one million takes to execute and store the result in O%. This value is used later to offset the time measurements made later. The displayed time is in seconds.

        REM How long does calling a SYS by name take?
        T%=TIME+O%
        FOR I%=1 TO C%
          SYS "GetSystemMetrics",0 TO X%
        NEXT
        PRINT "SYS by name   : ";(TIME - T%)/100"s"'
Here we call GetSystemMetrics by name one million times timing how long it takes and display the result in seconds.

        REM How long does calling a SYS by pointer take?
        T%=TIME+O%
        SYS "GetModuleHandle","User32.DLL" TO _user32%
        SYS "GetProcAddress",_user32%,"GetSystemMetrics" TO GetSystemMetrics%
        FOR I%=1 TO C%
          SYS GetSystemMetrics%,0 TO X%
        NEXT
        PRINT "SYS by pointer: ";(TIME - T%)/100"s"
      UNTIL 1=0
Here we read the function pointer for GetSystemMetrics, call the function by pointer one million times timing how long it takes and display the result in seconds.

If the above code exits with a Division by zero error you should increase the value of C% by a factor of ten; that is, add an extra zero to C%.