/**************************************************************************
 *
 * PROJECT:	Empty BASIC-SDK Library
 *
 * FILE:        HOWTO.TXT
 *              This files describes how to write a SDK library for R-BASIC
 *
 * AUTHOR:      Rainer Bettsteller
 *              Copyright (c) by RABE-Soft 01/2010, 10/2010, 09/2012
 *
 **************************************************************************


 **************************************************************************
 * How to setup the framework
 **************************************************************************

 1. Copy all files from this package (see files.txt) to your working path
    e.g.  \PCGEOS\MyName\Library\mybaslib

 2. Rename the sdklib.gp and sdklib.ref files.
    The new name must fit to the folders name (e.g. mybaslib.gp, mybaslib.rev)

 3. Edit the gp file
    - Change the longname statement (currently longname    "SDKLib"):
	    e.g.  longname "MySysCallLib"
	    Select a "self explaining" name and avoid spaces, because this name is
	    used for the INCLUDE statement in R-BASIC
    - Change the name statement (currently "name sdklib.lib"):
	    to e.g., "name  mybaslib.lib"
	    Use up to 8 letters and dont remove the lib extension
	    This name is used by the GEOS system and by swat
    - You also may modify the usernotes statement
    - DO NOT change tokenchars  and tokenid !

 4. Edit the SDKLIB.GOH file
    - You MUST change the @deflib statement
	    e.g. @deflib  mybaslib
      The @deflib statement MUST be the same as the name statement in the gp file
      There is no need to use the libraries folder name for the name statement
      in the gp file and the @deflib statement, but it's often done.

 5. Edit the LOCAL.MK file
    - You MUST change the "XGOCFLAGS = -L" statement to point to the same
      name as "@deflib" in sdklibs.goh and "name" in the gp file, e.g.
      XGOCFLAGS = -L mybaslib
    - You also may modify the NO_EC = 1 statement to NO_EC = 0 and
      the LINKFLAGS statement to create your own copyright notice

 6. Compile your library. You should not get any error messages.

 7. Test the library.
    - Copy the library to GEOS to "geos_root\USERDATA\R-Basic\Library\BIN"
    - Write a little BASIC program (Classic mode for simplicity) and run it:
	INCLUDE "MySysCallLib"
	CLS
	Print "I have";SysGetInfo(SGIT_NUMBER_OF_VOLUMES);" drives"
	Print "My CPU is";CPUSpeed();" times faster than a XT"




 **************************************************************************
 * How things work
 **************************************************************************

 To understand the steps to write your own SDK library, you should have an
 overview how R-BASIC works with your library.

 From BASIC programmers view, SDK libraries are not different to normal
 BASIC libraries. They are located in the "USERDATA\R-BASIC\Library\BIN" folder
 and are used by the keyword INCLUDE and it's name, e.g.
 INCLUDE "MySysCallLib"

 When R-BASIC meets this include statement, the library will be requested
 for a "BASIC header" which contains the names of exported routines,
 structs, constants etc.
 R-BASIC compiles the header and assignes every exported sub,
 function or action handler a number. This is simply done by counting
 the DECL statements. This numbers starts with zero for 1st routine,
 that is declared with DECL.
 Now, R-BASIC can use the SDK library just as any other library.

 When R-BASIC starts or compile a BASIC program, it calls
 SDKLibGetCodeVersion() to get the libraries version number.
 This prevents R-BASIC from execute programs with incompatible library versions.
 Therefore, if you add new routines, you have to modify SDKLibGetCodeVersion()

 R-BASIC does following steps if it finds a routine that comes from a SDK library:
 - Find out the number of that routine.
 - Parse the parameters and copy the values into a memory block.
 - Call the library routine SDKLibExecuteSubOrFunction() and pass
   a pointer (!) to the locked memory block, which contains the parameters.
 If the routine is a function, it should copy its result to that memory
 block to allow R-BASIC to use the result. See below how to do that.

 You are also allowed to return an error code, if a runtime error occurs.
 See section "How to return a runtime error" for details about that.
 I case of fatal errors, R-BASIC askes the library for the error explanation text,
 assigned to that error code. This is done by calling SDKLibGetErrorText().

 Thats all.



 **************************************************************************
 * Implementation of the routines
 **************************************************************************

 1. Modify the ExportedBasicFunctions[] chunk
 --------------------------------------------
 You must write a "BASIC header" first to make R-BASIC know the names
 and syntax of your exported routines. This must be done by modifying the chunk
 "ExportedBasicFunctions[]" in the data resource, located in the LIBUI.GOC file.
 Use the normal BASIC syntax, especially the keywords DECL, STRUCT and CONST,
 to declare your routines and types.
 Additionally, you can do anything, that is allowed in normal basic with
 TWO EXCEPTIONS
	You must NOT include other libraries.
	You must NOT write code outside of a sub, function or action handler.


 2. Write the routines
 ---------------------
 For every BASIC routine you have declared, you must write a corresponding
 routine in GOC. You are free to declare this routines as you wish, but 
 typically, you will pass the pointer to the locked parameter block
  	void DoMyOwnFunction(byte *parameterBlock);
 If the routine may fail (return a runtime error), you may return an error 
 code
 	int MyFunctionThatMayFail(byte *parameterBlock);
 If the error is a fatal error, you have to assign the returned value to the
 "errorCode" parameter of SDKLibExecuteSubOrFunction(). See below for detailed
 description how to return error codes.

 To access the parameters, you may retrieve a pointer to the parameter by using 
 the routine SDKLibGetParamPtr() or you may access it directly.
 To return a value, use SDKLibWriteReturnValue() to write a value or 
 SDKLibGetRetPtr() to retive a pointer to the return buffer. 
 See next sections for detailed explanation how to use this routines.

 Additionally, you may modify SDKLibGetCodeVersion().


 3. Modify SDKLibExecuteSubOrFunction()
 --------------------------------------
 To make BASIC have access to your routines, you have to modify
 SDKLibExecuteSubOrFunction(). It is passed a routineNumber. You have
 simply expand the switch statement in that routine and call your 
 own routine at the appropriate place. Remember, that routineNumber 
 is zero based and is simply the number of the DECL statement in 
 your BASIC header.




 **************************************************************************
 * How to access parameters
 **************************************************************************

 When R-BASIC executes a routine, that is declared in a SDK library, it
 creates a memory block and copies the parameters to it. You don't need to
 know its exact structure, there are the routines 
 	SDKLibGetNumParams()
 	SDKLibGetParamPtr()	
 that do the job for you. They are implemented in the INTERNAL.GOC file.
 In case of you wish to access the parameters directly, the structure of the 
 block is described below.
 
 
 Using SDKLibGetNumParams() SDKLibGetParamPtr()
 ----------------------------------------------
 Usually, you will know the numbers of parameter of your SUB or FUNCTION.
 You may use SDKLibGetNumParams() for error catching or if you are in a 
 stage of development where the number of parameters may change. This routine always
 returns the number of parameters that are passed from R-BASIC.
 
 To access a parameter, you have to get a pointer to that parameter. R-BASIC 
 passes a locked memory block to SDKLibExecuteSubOrFunction() that contains
 the parameters.  SDKLibGetParamPtr() returns a pointer in that block, 
 pointing to the parameter you requested. It also can return the size of
 the parameter. But, there is no type checking. You have to know the type
 of the parameter yourself. For example, if you have the parameter declared as 
 WORD, you should know that the pointer points to a WORD sized value.
 Remember that the R-BASIC type REAL is equal to long double in C, that are 
 10 byte.
 
 Additionally, you may find out the address of the parameter manually. This
 may be somewhat faster. For this purpose, the structure of the memory block 
 passed is described below. 

 
 Structure of the memory block passed to SDKLibExecuteSubOrFunction().
 ---------------------------------------------------------------------
 The pointer parameterData points  to a locked memory block that has the 
 following structure:
  +--------+----------------------+-----------------------+-------------------+
  | Header | Parameters           | Returnbuffer          | Extra data        |
  | 6 Byte | header.variableSpace | header.sizeOfRetValue | header.extraSpace |
  +--------+----------------------+-----------------------+-------------------+

The header is of type VarBlockHeader, declared in SDKLIB.GOH. Its fields hold
the sizes of the other parts of the memory block.

Parameters: This memory area simply contains the parameters in the order as they 
----------  are declared. Lets assume you have declared a FUNCTION as followed:
   
   DECL FUNCTION MyTestSub( x as word, t$ as STRING(30), q as dword) AS STRING
 
 Then, the parameter area will be 37 bytes long. Remember that Strings are 
 terminated by a zero value, therefore STRING(30) requires 31 byte.
  +--------------+----------------+--------------+
  | 2 byte for x | 31 byte for t$ | 4 byte for q |
  +--------------+----------------+--------------+
 
 To get a pointer the the first parameter, you may use the following code
 in the SDKLibExecuteSubOrFunction() routine:
 	ptrToParam = parameterData + sizeof(VarBlockHeader);
 ptrToParam is assumed to be declared as byte*. 
 	
 
Returnbuffer: If you have declared a FUNCTION, R-BASIC provides a retunbuffer
------------- of size header.sizeOfRetValue. In our example above, the buffer
              is 129 bytes long, because the type STRING allows strings up to
              128 characters, followed by a zero value as end marker.
              If you have declared a SUB, header.sizeOfRetValue will be zero.
              
extraSpace: This area is for internal use and may be up to some hundreds byte 
----------  long. When calling a Routine from an SDK library, it start with a 
            table of words, that contains the size for every parameter. This 
            Table ends with a zero value and is used by SDKLibGetParamPtr() 
            and SDKLibGetNumParams() to find out the correct value. 
            In our example above, the table will look as following:
            +----+----+----+----+
            |  2 | 31 |  4 |  0 |
            +----+----+----+----+



 **************************************************************************
 * How to return values
 **************************************************************************

 If your routine is a FUNCTION, you have to return a value for R-BASIC.
 R-BASIC expects, that the value is copied to the return buffer of the 
 parameter block, passed to SDKLibExecuteSubOrFunction().
 
 SDKLibWriteReturnValue():  This is the simplest way to copy a return value
 -------------------------  to the return buffer. The routines always copies
                            the number of bytes to the return buffer that is
                            specified by the return type of your function.
                            This value is stored in the header of the parameter
                            block, passed to SDKLibExecuteSubOrFunction().
                            
 SDKLibGetRetPtr(): This function returns a pointer to the return buffer and
 -----------------  the size of that buffer. It allows you to have more 
                    control over data to be copied. Especially for strings, 
                    you should copy only the string itself, not the garbage
                    bytes following it. But, don't forget to check the string 
                    length first in this case.
                    


 **************************************************************************
 * The other parameters of SDKLibExecuteSubOrFunction()
 **************************************************************************

 When you look over the parameters of SDKLibExecuteSubOrFunction(), you will
 see three other parameters, a LibExtraPassValues pointer,
 a LibExtraReturnValues pointer an a void pointer.

 extraParams:  The pointer to a LibExtraPassValues structure is designed to 
 -----------  pass some information to your library, that may be useful for
 	       special cases. In particular that are
 	       - a list of memory blocks, that contain the 'memory' that 
 	         R-BASIC uses for Peek, Poke and related commands,
 	       - two GState handles that allows you to write to the current
 	         screen directly.
 	       - a file handle and the corresponding offset that allows you
 	         to read or write HUGE variables, if your library exports
 	         HUGE variables.
 	         
 	      The fields of this structure are explained in the description
 	      of the LibExtraPassValues structure in the SDKLIB.GOH file.
 	      Some of this topics are also described below.
 	      
 errorCode:   This pointer to an integer value can be used to return a fatal 
 ---------    error code to R-BASIC. I is described in the next section.
 
 extraReturn: The pointer to a LibExtraReturnValues is designed to return some
 -----------  information to R-BASIC that are useful for special situations.
 	      In particular that are:
 	      - A way to "register" a file, opened by your library, to 
 	        allow R-BASIC to exit cleanly if an error occurs.
 	      - A way to pass non-fatal error codes to R-BASIC that 
 	        may be handled by the BASIC program properly.
 	        
 	      The fields of this structure are explained in the description
 	      of the LibExtraReturnValues structure in the SDKLIB.GOH file.
 	      They are also described in the next sections.

 reserved:    This pointer is reserved for further use. You have to ignore it.
 --------


 **************************************************************************
 * How to return an error condition to R-BASIC
 **************************************************************************
 
 Some operations may fail at runtime. Therefore, you may wish to return an
 error condition. From BASIC's point of view, there are three types of
 errors possible.
 
 1. File errors
    While working with files, a file may be not exits or cause other 
    problems. In this case, you should return an "fileError" value. 
    The program that calls your SDK routine may read the R-BASIC fileError
    variable and may react in an adequate way.
    To return a file error, use the "extraParams" parameter, passed to 
    SDKLibExecuteSubOrFunction(). Simply write an error code to 
    	extraParams->fileError = myerror
    and set the bit LRF_FILE_ERROR in
    	extraParams->flags |= LRF_FILE_ERROR
    to signal R-BASIC that it should copy the value to its fileError variable.
    Remember that all SDK File~ routines will set an error condition, that may 
    retrieved with ThreadGetError(). Simply pass this value back to R-BASIC.
    You also may find the error codes in the "Appendix" of the R-BASIC manual.
    If your routine whish to return a file error in the way described above, be 
    sure to return the error-code zero to signal that no fatal error occured.
    See "DoWriteToFile" in DEMO.GOC for an example.
    
 2. Custom errors
    You may have an error condition, that is not a file error, and not a
    fatal error. For this case, R-BASIC provides a system variable called
    customError, that may be set from your SDK library.
    To return a custom error, use the "extraParams" parameter, passed to 
    SDKLibExecuteSubOrFunction(). Simply write an error code to 
    	extraParams->customError = myerror
    and set the bit LRF_CUSTOM_ERROR in
    	extraParams->flags |= LRF_CUSTOM_ERROR
    to signal R-BASIC that it should copy the value to its customError variable.
    If your routine whish to return a custom error in the way described above, be 
    sure to return the error-code zero to signal that no fatal error occured.
    See "DoSetMoniker" in DEMO.GOC for an example.
 
 3. Fatal errors
    Sometimes, you may wish to emergency stop the BASIC program, e.g. because
    of missing data or of a wrong parameter. R-BASIC then will display an
    error message like "Runtime Error ... in ..." and terminate the program. 
    To trigger that, simply write an error code to the "errorCode" parameter 
    of SDKLibExecuteSubOrFunction(). The simplest way to do that is to have 
    your routine to return an error code.
    Example:
 	*errorCode = MyFunctionThatMayFail(parameterData);
    Be sure to return zero, if there is no error.
 
    In case of a fatal error, R-BASIC will stop execution the program and display 
    a runtime error message. To make the reason understandable for the user, 
    R-BASIC calls the library for an explanation text by calling 
    SDKLibGetErrorText() first. You have to modify SDKLibGetErrorText() and 
    return an optr to a chunk with the explanation text.
    See "DoInfoBox" in DEMO.GOC for an example.

    If the fatal error occurs in a function, you may or may not write a return
    value. R-BASIC will ignore any returned values in case of runtime errors.



 **************************************************************************
 * Registering files and handles
 **************************************************************************

 Usually, if a programmer tries to close a file which is already closed,
 GEOS will crash. This makes it hard to find out the wrong code line. 
 Therefore, R-BASIC provides a mechanism, called "registering" open files.
 When a R-BASIC programmer tries to close a file, R-BASIC will look if the 
 file is registerd. If not (that is, it has been closed yet), a runtime error
 will occur, showing the programmer the wrong code line.
 Additionaly, when the programmer cannot close the file, for example in case of
 another runtime error, R-BASIC whill close the registered files and exit cleanly.
 
 Therefore, when you open a file in your library, you should "register" it.
 The following code shows how to handle that.
    Registering a file handle fh:
 	fh = FileOpen(..)
 	extraReturn->handleOrData = fh;
 	extraReturn->registerType = RRT_OPEN_FILE;
 Of course you have to "unregister" your file, if you close it in your library,
 or R-BASIC will crash while trying to close a closed file.
    Unregistering a file handle fh:
 	FileClose(fh, FALSE)
 	extraReturn->handleOrData = fh;
 	extraReturn->registerType = RRT_CLOSE_FILE;
 	
 The same applies if you open / close a VMFile.
 
 Note that registering a file is only required, if it can be accessed or 
 closed by a R-BASIC routine. If you are sure that only your library can
 access the file, there is no need to register/unregister the file.

 
 Additionally, you may decide to register/unregister a memory handle, 
 but this is not required.
 Registering a memory handle mh can be done as followes:
 	mh = MemAlloc(..)
 	extraReturn->handleOrData = mh;
 	extraReturn->registerType = RRT_MEM_HANDLE_ALLOC;
    Unregistering a memory handle mh:
 	MemFree(mh)
 	extraReturn->handleOrData = mh;
 	extraReturn->registerType = RRT_MEM_HANDLE_FREE;
 	
 For more details, see description of LibExtraReturnValues in SDKLIB.GOH
 
 

 **************************************************************************
 * Accessing files and objects in a SDK library
 **************************************************************************

 For working with files and objects, the R-BASIC Structures ObjectVariable 
 and FHVariable from R-BASIC are described in the SDKLIB.GOH file. The file 
 DEMO.GOC contains examples how to use it.

 It's possible to access files that are opened in normal BASIC code in your SDK
 library. Also, it's possible to open a file and pass its handle back to 
 R-BASIC. You have to set up a FHVaribale structure and return it AND you 
 have to "register" the file Handle. See "Registering handles" above and the 
 description of LibExtraReturnValues in SDKLIB.GOH how to regsiter a handle.
 
 You also may work with objects, that are declared or created in BASIC code.
 If you wish to pass an object variable to your SDK routine, you have to
 call the BASIC function ConvertObjForSDK() first. This routine replaces the
 BASIC internal object reference by the real optr of that object.
 But, you may not create objects and pass it back. For more details, see 
 description of ObjectVariable structure in SDKLIB.GOH.
 Also, there is an example in the DEMO.GOC file, the "DoSendMonikerInternal" 
 routine, that is called by the "SendMoniker" example.
 

 **************************************************************************
 * Writing ActionHandlers in a SDK library
 **************************************************************************

 You may write action handlers in your SDK library. The file DEMO.GOC contains
 an example about that. In case of an action handler, the sender parameter 
 (which is the first parameter always), is already converted by R-BASIC. 
 The objReference will contain the real optr of that object. All other fields 
 are unchanged.
 

  **************************************************************************
  * Passing a large amount of data from or to a SDK library
  **************************************************************************

 The memory block, passed to any SDK routine may hold up to 8 kByte data
 as parameters. The return value size is limited to the size of a structure,
 which is 3500 byte. If you wish to pass or to return more data, you have 
 two choices: The "virtual" memory, that is used by R-BASIC for POKE, PEEK
 and similar commands. And the HUGE variable memory, which is stored in a
 plain DOS file. The virtual memory is fast and simple to use, but it is
 limited to 64 kByte. The HUGE memory may be up to 2 GByte large.
 
 Virtual Memory:  The virtual memory is organized in 8 blocks of 8 kByte
 --------------   See description of LibExtraPassValues in the SDKLIB.GOH 
 		  file for more details.
 		  To pass some data to your library, use POKE, SPOKE, POKE$
 		  or similar commands. In your SDK routine, lock the affected
 	          memory block and access the data. Then unlock it again.
 	          For returning data, lock the block and copy data to it.
 	          Don't forget to unlock it again.
 	          Use PEEK and similar commands to read the data in R-BASIC.
 	          
 	          Remember that a block may not be allocated, if it was not 
 	          used yet. You are alowed to allocate blocks yourself. The 
 	          block must be declared as HF_SHARABLE | HF_SWAPABLE and it 
 	          must be 8 kByte (8*1024) in size. 
 	          Additionally, you have to inform R-BASIC that you have 
 	          allocated one or more blocks by setting the flag
 	          LRF_VIRTUAL_MEM_ALLOCATED in the "flags" field of 
		  "extraReturn" pointer, which is passed to 
		  SDKLibExecuteSubOrFunction().
		  The following code allocates all "virtualMem" block that
		  are not allocated yet. It's assumed to be a part of the
		  SDKLibExecuteSubOrFunction() routine.
 	          {
		  int n;
		  for (n = 0; n < 8; n++) {
		     if ( extraParams->virtualMem[n] == 0 ) {
			extraParams->virtualMem[n] = MemAlloc(8*1024, 
				HF_SHARABLE | HF_SWAPABLE, HAF_ZERO_INIT);
			extraReturn->flags |= LRF_VIRTUAL_MEM_ALLOCATED;
			}
		     }
		  }		
 	          
 	          
HUGE memory:	  Usually, you cannot access R-BASIC's global variables in a 
-----------	  SDK Library. But there is one exception: If your SDK library 
		  exports HUGE variables, the structure LibExtraPassValues 
		  will contain information about the file where the HUGE 
		  variables are stored. You may read and write them. For 
		  example, lets assume you have declared an array in the
		  ExportedBasicFunctions chunk:
		  	DIM wf(20000) as HUGE word
		  To copy a buffer of 6 bytes to the first 3 elements 
		  of the "wf" array, use the code:
 			FilePos( extraParams->hugeVarFile, 
 				 extraParams->hugeVarStart, 
 				 FILE_POS_START);
			FileWrite(extraParams->hugeVarFile, buff, 6, FALSE);
		  This will set the elements wf(0), wf(1) and wf(2).
  		  See description of LibExtraPassValues structure in SDKLIB.GOH 
  		  for more  details.

 
 
 **************************************************************************
 * Drawing graphics to the current screen
 **************************************************************************

 The extraParams parameter of SDKLibExecuteSubOrFunction() points to two gstate
 handles, that reflect the current screen of R-BASIC. You may use it to draw any
 graphics to the current screen. If there is no screen object, both handles will
 be zero. In some cases, only one of the handles will be zero, the other will be 
 a valid gstate handle. And in some cases, both will contain (different) gstate
 handles. Therefore, always check the handle to be non zero if you wish to draw 
 to.
 
 The mainGState handle most often points directly to the display. Drawing will 
 appear on screen immediately.
 The bmpGState usually points to a "buffer" (if any), that allows the object to 
 redraw itself without calling a BASIC draw handler. This may be a bitmap or a 
 GString.
 
 If you draw some graphics, always draw the same to both gstates (if they are non 
 zero). You also may change the attributes (line color, e.g.), but again, do the 
 same operation on both gstate handles. You should not make any changes to
 the gstates permanent. Use GrSaveState() and GrRestoreState() to ensure that.
 Making permanent chages will result in unexpected result, because the gstates
 does not longer reflect the internal state of R-BASIC drawing system.
 
 
 


 R. Bettsteller,
 September 2012, 2015

 ************************** End of File HowTo.TXT ***************************




