Creating your own DLL for Rapid-Q using FreeBasic

FreeBASIC is a free open source 32-bit BASIC compiler for Windows, DOS, and Linux. It is a powerful development tool and has support for many C libraries including SDL and OpenGL. It is nearly 100% compatible with QuickBASIC, and shares many keywords with RapidQ. FreeBASIC is not case sensitive and has full support for pointers (functions, virtual tables, etc),  unsigned data types and nested UDT structures. 

FreeBasic is more powerful than RapidQ: 
1) it is optimized compiled machine code, not interpreted, and allows inline Assembly code.
2) it can link with C-libraries including iDispatch COM and DirectX9
3) has advanced support for pointers and variable types and much more.  

Where do you download FreeBasic?      www.freeBasic.net, and check the forum at www.freeBasic.net/forum.


When would you use a DLL in RapidQ? The most common reason is because of the slow execution for intensive calculations. That will be covered below.  So how do you make a RapidQ-FreeBasic program? Let's start with a very simple example.


'Make a program called Test.bas that will be compiled as RapidQ code:


DECLARE SUB TestInteger lib "DLL_test" ALIAS "TestInteger@4" (byval myData AS Integer)
TestInteger(12345)



Now make a FreeBasic file called DLL_test.bas :

'option explicit   'this keyword may not be needed, check FB docs
'this declare statement sets the case sensitive name for the sub in the DLL 
'without it the name would be all UPPERCASE. Be sure to use ByVal, ByRef, etc in FreeBasic

DECLARE SUB TestInteger lib "DLL_test" alias "TestInteger" (byval myData AS Integer)

SUB TestInteger (byval myData AS Integer) EXPORT
    print "In FreeBasic your integer  = " , myData
END SUB



Now compile DLL_test.bas as a FreeBasic DLL file; i.e., at the DOS prompt (or in an IDE)

  ***  the -dll command instructs it to generate a DLL file.  ***


 fbc -dll DLL_test.bas



That's it! You can run Test.exe (the RapidQ program) at the DOS prompt and you should see "12345" printed.  You will notice a few important details. The DECLARE statement in RapidQ has a funny @4 name added to the sub in the DLL name:

DECLARE SUB TestInteger lib "DLL_test" ALIAS "TestInteger@4" (byval myData AS Integer)
This is because the compiler code that generates the DLL automatically tags on this "mangled name" so that programs using the DLL know to use the STDCALL convention. The @4 means that 4 bytes will be put on the stack for so the calling program knows how many parameters need to be read. If instead you were going to pass two integers to the DLL then the compiler will add @8 to the end of the name, and  for a three integer-parameter SUB it will add @12, and so on. Remember case is important for the Sub or Function in the DLL.

Other things to note here, the FreeBasic option explicit command is like  $TYPECHECK ON in RapidQ, it may not be needed. The other is EXPORT, which informs FreeBasic that this SUB will be the one available to external programs calling the DLL. All other SUB / FUNCTION, and variables will be global only to the FreeBasic program, not the RapidQ program.  The other thing you will notice is that FreeBasic makes an DLL_test.dll.a. The .a files are for import libraries, you can delete them because you won't need them for RapidQ.


OK so now I know how to call a simple DLL with an integer. 
What about other types? What about pointers?


'Let's try one with a  DOUBLE and a pointer to an INTEGER:
DECLARE SUB TestVars lib "DLL_test" ALIAS "TestVars@12" (byval myData AS Double, ByRef myInt AS INTEGER)
DIM a AS INTEGER
a = 12
TestVars(12345.12345, a)
ShowMessage STR$(a)        'let's see what returns



The FreeBasic code looks very familiar:
option explicit  
DECLARE SUB TestVars lib "DLL_test" alias "TestVars" (byval myData AS Double, ByRef myInt AS INTEGER)

SUB TestVars(byval myData AS Double, ByRef myInt AS INTEGER) EXPORT
    print "In FreeBasic your Double  = " , myData
    print "In FreeBasic your integer value is  = " , myInt
    print "now it is 100"
    myInt = 100
END SUB


As you can see it works pretty much like in RapidQ. If you want the SUB or FUNCTION to change the variable, then pass it ByRef.  Notice the 
"TestVars@12" because the Double is 8 bytes, and the pointer (32-bit Windows) is 4 bytes.  If you find it too difficult to figure out the name mangling? Download a DLL viewer like ADllExports.exe, and the program will show you the actual name. 



For RapidQ users! The RapidQ  BYTE, SHORT, SINGLE and DOUBLE has a bug when 
passing these data types to a DLL.

BYTE, SHORT, WORD, SINGLE, and DOUBLE must be stored in a 32-bit long if passing ByVal to a DLL. 
Basically always try to pass a 32-bit variable such as INTEGER, DWORD, or LONG. If your DLL needs less than 2 SINGLE or more than 2 DOUBLE variables, these will not be passed correctly! The safest way to pass these variables ByRef, if possible. If you must pass them ByVal then you should just state them as INTEGERS or LONG, in the DECLARE statement.

'here is the actual API call
SUB gluPerspective(fovy as Double, aspect as Double, zNear as Double, zFar as Double)
'here is how to implement it
DECLARE SUB gluPerspective_API LIB "glu32.dll" ALIAS "gluPerspective" _
   (fovy1 as long,fovy2 as long, aspect1 as long, aspect2 as long, _
   zNear1 as long, zNear2 as long, zFar1 as long, zFar2 as long)
   DEFDBL d: DEFINT i(7)
   d = fovy: MemCpy (VarPtr(i(0)), VarPtr(d), 8)
   d = aspect: MemCpy (VarPtr(i(2)), VarPtr(d), 8)
   d = zNear: MemCpy (VarPtr(i(4)), VarPtr(d), 8)
   d = zFar: MemCpy (VarPtr(i(6)), VarPtr(d), 8)
   gluPerspective_API(i(0), i(1), i(2), i(3), i(4), i(5), i(6), i(7))
END SUB

'here is another example of BYTE
Declare Sub keybd_event Lib "user32" Alias "keybd_event" _
(bVk As BYTE, bScan As Long, dwFlags As Long, dwExtraInfo As Long)

'all you have to do is change BYTE to LONG, and it will work fine
Declare Sub keybd_event Lib "user32" Alias "keybd_event" _
(bVk As Long, bScan As Long, dwFlags As Long, dwExtraInfo As Long)





Arrays and looping

Obviously a simple programs are not of real use. You do not want to call a DLL for a single simple calculation because it takes time to just run the DLL code. What you need a DLL for is complicated calculations requiring multiple loops, access to high level libraries, and so forth. The most common use for RapidQ is speeding up calculations. Take an example where you would like to find the average of a large data set: 

SUB CalcAverage(Datas() AS SINGLE, NumPts AS LONG, ByRef Average AS SINGLE)
  DIM Summ AS SINGLE, i AS INTEGER
  FOR i = 0 TO NumPts
     Summ = Summ + Datas(i)
  NEXT i
  Average = Summ / NumPts 'this will pass back the new average
END SUB

This calculation will take about 50 times longer in RapidQ relative to FreeBasic. So how do I pass my array to the DLL? 
RapidQ handles arrays by reference, you just have to pass the reference to the first element in the array AND make sure FreeBasic knows it is getting an array of a specific type and how many elements are in it. There are actually two ways to do this:

FreeBasic has special keyword  PTR to indicate the SUB gets a pointer to an array of SINGLE:

SUB CalcAverage (Datas AS SINGLE PTR, BYVAL NumPts as LONG, ByRef Average AS SINGLE) EXPORT
  DIM Summ    AS SINGLE
  Dim fp As Single Ptr
  fp = @Datas					'asign the address local
  DIM f(NumPts) AS SINGLE  			'allocate a local array
  memcpy(@f(0), fp, NumPts*SIZEOF(SINGLE))   	'copy it
  FOR i = 0 TO NumPts				'now the code is just like RQ
     Summ = Summ + Datas(i)
  NEXT i
  Average = Summ / NumPts 			'this will pass back the new average
  memcpy(fp, @f(0), NumPts*SIZEOF(SINGLE))	'be sure to copy back!
END SUB

It is important to know RapidQ and FreeBasic use different descriptors of arrays (i.e., definition of the length of the array and the number of dimensions). But each program knows the address of the data. That is why the code is complicated with the assignment of the array address (fp =@Datas) and the memcpy command to create a new array that FreeBasic. There is another simpler way to write the code using indexing with brackets [ ]  and using BYVAL Datas AS SINGLE PTR:

SUB CalcAverage (BYVAL Datas AS SINGLE PTR, BYVAL NumPts as LONG, ByRef Average AS SINGLE) EXPORT
  DIM Summ    AS SINGLE

  FOR i = 0 TO NumPts				'now the code is just like RQ
     Summ = Summ + Datas[i]			'note brackets not parens!
  NEXT i
  Average = Summ / NumPts 			'this will pass back the new average
END SUB



Passing strings and functions

Strings are complicated due to differences between compilers on how to describe all the parameters of a string. Like arrays, the complier needs to know how long the string is, and so forth. You can freely pass strings to FreeBasic, but it will just treat it as an array of bytes. An easy way to work with strings is to copy the passed string to a temporary FreeBasic string. RapidQ always passes strings by reference.

Compile this with fbc -dll mydll.bas

declare function teststr lib "mydll" alias "teststr" ( byval tstr as byte ptr) as long

function teststr (byval tstr AS BYTE PTR) as long EXPORT
  dim sss as string				'this is local to sub only, not RapidQ

  PRINT "the first char = "; CHR$(tstr[0])  	'note reference 0 based
  sss = *tstr 					'dereference value held in pointer
  sss=sss+"bzzzz"				'now treat it like any string
  print "in dll the new string =" ;sss
  teststr = INSTR(sss, "bz")  			'return back function result
end function

Test with this RapidQ code:

declare function teststr lib "mydll" alias "teststr@4" (tstr1 as string) as long
  DEFSTR aa$="abcdefgh"
  DEFLNG zz
  zz=teststr ( (aa$))
  print "teststr=" ; zz

There are several important things to notice. First FreeBasic uses ByVal tstr AS BYTE PTR to treat the string as an array that is passed by reference. Second, the temporary string, sss, is assigned the string with the asterisk (*) character which is used in the C language to dereference a pointer (that is get the value the memory that pointer is pointing to). The memory is assigned to sss until a CHR$(0) is reached, indicating a zero-terminated string. Third, you can also access individual characters with the index tstr[0] like that in arrays discussed above.

The last thing to point out is the FUNCTION works, by passing back to RapidQ a LONG value to the variable zz. At the time of this writing, 
ONLY LONG, and INTEGERS can be passed back to RapidQ from a FreeBasic FUNCTION. 




User defined types and more complicated calls

Since FreeBasic uses a syntax very similar to RapidQ, you can also pass UDTs (User define types) with ease. The following example shows just how complicated the structure can be and you will have total access to the RapidQ data inside the FreeBasic DLL:

Let's define the structure in RapidQ that uses most variable types:

DECLARE SUB TestUDT lib "DLL_test" alias "TestUDT@4" (lpUDT AS LONG) '@4 is 4 bytes for UDT address
TYPE myUDT
  a AS BYTE
  b AS SHORT
  c As LONG
  d As STRING * 7	'must specify size
  e(0 to 20) As DOUBLE   'just the pointer is passed to the DLL!
END TYPE

DIM TheUDT as myUDT
  TheUDT.a = &HF0
  TheUDT.b = &HBBBB
  TheUDT.c = &HFF000001
  TheUDT.d = "hello!"  +CHR$(0)  'must end buffer with zero char
  TheUDT.e(3) = 5.2076

TestUDT(TheUDT)
ShowMessage str$(TheUDT.e(3))	'returns back new value

Again the FreeBasic code looks very familiar except for some variable types:

TYPE myUDT  FIELD=1	'sets this is a PACKED structure
  a AS BYTE		'that is default in RapidQ, not FB
  b As SHORT
  c As LONG
  d As ZSTRING * 7	'ZSTRING means zero-terminated string
  e As DOUBLE PTR	'RapidQ passes the array pointer!
END TYPE

DECLARE SUB TestUDT lib "DLL_test" alias "TestUDT" (BYREF lpUDT AS myUDT)

SUB TestUDT (BYREF lpUDT AS myUDT) EXPORT
  Print "Byte = "; HEX$(lpUDT.a)
  Print "Short = "; HEX$(lpUDT.b)
  Print "Long = "; HEX$(lpUDT.c)
  Print "print string is "; lpUDT.d		'FB knows it is a string
  Print "third char = "; CHR$(lpUDT.d[3])	'print out just one character
  lpUDT.e[3] = -20.567				'change its value by pointer reference ...
END SUB


There are some important things to note. The ByRef statement for the UDT because RapidQ always passes UDTs by reference.  Also, the  FIELD=1 statement must be set in FreeBasic to handle the RapidQ UDT, which is commonly called a "packed structure."  That just means all variables in the UDT are packed right next to each other without padding or aligning the data on specific boundaries. The other is the conversion of STRING to ZSTRING, and an array of DOUBLE to DOUBLE PTR. Again these are needed in FreeBasic to work with the way RapidQ actually passes the data to any DLL, not just FreeBasic. From this one example you should be able to see how to pass different variable types. Here is a table of other variable types. Note that Freebasic has more types than RapidQ, including unsigned versions of all RapidQ types. FreeBasic assumes that variables are passed ByRef unless specified ByVal.

RAPID-Q  declaration FreeBasic declaration  Range
var   AS BYTE ByVal   var   AS UBYTE      unsigned  0 to 255
var   AS BYTE ByVal   var   AS BYTE  signed   -128 to 127
var   AS SHORT ByVal   var   AS SHORT  signed  -32768 to 32767
var   AS WORD ByVal   var   AS USHORT unsigned   0 to 65365
var   AS LONG ByVal   var   AS LONG signed 4 bytes 32-bits
var   AS INTEGER ByVal   var   AS INTEGER signed 4 bytes 32-bits
var   AS DWORD  (actually a LONG in Windows) ByVal   var   AS UINTEGER unsigned 4 bytes 32-bits
var   AS STRING             var   AS ZSTRING  * size fixed length or find last 0 char.
var   AS SINGLE ByVal   var   AS SINGLE signed 4 bytes 32-bits  ( 6 decimal digits)
var   AS DOUBLE ByVal   var   AS DOUBLE signed 8 bytes 64-bits ( 15 decimal digits )
Arry(index)  AS BYTE,  var AS STRING ByVal   Arry  AS BYTE PTR Array of bytes, can be strings
Arry(index)  AS INTEGER, etc. ByVal   Arry  AS INTEGER PTR
access by Arry[i]
Array of 32-bit integers
var  AS UDTtype ByRef  var  AS UDTtype packed structure




Advanced Topics, passing data between the FreeBasic DLL and your RapidQ program

In this example you will pass the address of a RapidQ sub to the FreeBasic DLL. FreeBasic will then call your RapidQ sub by passing
an integer to it. You will find this important if a RapidQ component needs to be updated while the FreeBasic DLL is doing a long loop.

'**** FreeBasic DLL CODE filename: FB_CallBackFromDll.Bas
'compile this as fbc -dll FB_CallBackFromDll.Bas
' -----------------------------------------------------------------
' TEST SET INTEGER IN CALLING APP FROM A FreeBasic DLL
' by Jacques March 7th, 2006
' -----------------------------------------------------------------

' -----------------------------------------------------------------------------
' first set function declares
Declare function CallBackInDll lib "FB_CallBackFromDll" _
alias "FB_CallBackFromDll@4"_
(ByVal iCallBack AS FUNCTION(ByVal i AS INTEGER) AS INTEGER) as integer
' -----------------------------------------------------------------------------

function CallBackInDll (ByVal iCallBack AS FUNCTION(ByVal i AS INTEGER) AS INTEGER) as integer export
Dim iProgress As Integer
While iProgress < 200
  iCallBack (iProgress)
  Sleep (200)
  iProgress = iProgress + 1
Wend
end function
' -----------------------------------------------------------------------------


'Now this will be your RapidQ program
'**** RAPIDQ CODE FileName: WhatEverYouwant.Bas
' -----------------------------------------------------------------------------
$ESCAPECHARS ON
$TYPECHECK ON
$INCLUDE "RAPIDQ.INC"
'
Declare function CallBackInDll lib "FB_CallBackFromDll" _
alias "FB_CallBackFromDll@4"(ByVal iCallBack as Integer) as integer
'
Create frmGauge As QForm
Center
ClientWidth = 300
ClientHeight = 10
 CREATE Gauge AS QGauge
   Align = alClient
   Max = 200
   Color = &HFF00
   position = 10
   Kind = gkHorizontalBar ' gkPie
 END CREATE
End Create
'-------------
Sub GaugeCallBack (iProgress As Integer)
Print "In RQ_CallBack: iProgress = ";iProgress ' or change your Gauge position
Gauge.Position = iProgress
DoEvents
End Sub
'
frmGauge.Show
'
CallBackInDll(CodePtr(GaugeCallBack))
'
' -------------------------------------------------------------
DefStr sExit
Input " CR to QUIT ", sExit
frmGauge.Close
Application.Terminate
' -------------------------------------------------------------
'



Other details:

FreeBasic will automatically use UPPERCASE for the Sub/Function in the DLL unless you use the ALIAS keyword in the FreeBasic program.

RapidQ uses the STDCALL convention and this is the default calling convention for FreeBasic and Windows API calls. STDCALL pushes the arguments from right to left onto the stack so that the last argument can be accessed by ebp+8. With stdcall convention the callee ( the called function ) has to completely clean up the stack, that is local variables/temps plus the arguments. as a last convention with stdcall function names are appeneded a @<num> where num is the number of bytes taken on the stack by the arguments of the function. CDECL convention hand pushes the argument list from right to left onto the stack so that the first element of the argumentlist is at ebp+8. no suffix is appended. this is most often used for variable argument lists and the like as the first element can tell how many arguments have been passed to the function.