View Full Version : calling back to an interpreter function
Chris Holbrook
12-01-2009, 19:53
Is it possible to derive a function address, which a dll function can call? It would save a lot of rewriting to use a library which calls "user functions" in the main program - in "Compiled World", that is!
So the "main" passes in an address to the dll, which calls that address when the time is right, and returns to the dll. How would I emulate that sort of behaviour?
Petr Schreiber
12-01-2009, 20:08
Hi Chris,
do you mean that your DLL module would call functions from ThinBasic script?
This is possible using following functions when working with module:
thinBasic_FunctionGetPtr - to retrieve user function parameter on the beginning ( during DLL initialization )
thinBasic_FunctionSimpleCall_ByPtr - to call that user function
Eros will tell more, this is just to let you know it should be possible :)
ErosOlmi
12-01-2009, 22:16
Chris,
I imagine you are talking about been able to fire a script function from inside a thinBasic module.
If this is the case this is the way (as suggested by Petr).
Imagine you have developed in your module named thinBasic_MYMODULE a keyword that you named:
MyModule_FireAFunction
that has the following syntax
MyModule_FireAFunction(AScriptFunction)
used in a script in a way like (for example)
USES "MYMODULE"
FUNCTION TBMAIN() AS LONG
MyModule_FireAFunction ( Fired ) '<<< MyModule_FireAFunction expects a function
END FUNCTION
FUNCTION Fired() AS LONG
END FUNCTION
Inside your compiled module you have to parse MyModule_FireAFunction in this way:
'...
DIM pPresent AS LONG
DIM fPtr AS LONG
'---Check if (
pPresent = thinBasic_CheckOpenParens_Optional
'---Now ask thinCore to parse a token and check it is a function.
fPtr = thinBasic_FunctionParseAndGetPtr
if thinBasic_ErrorFree then
'---If ( was present than ) must be present
if pPresent then thinBasic_CheckCloseParens_Mandatory
'---OK now we have a script function pointer. We can call the function whenever we need using something like:
thinBasic_FunctionSimpleCall_ByPtr fPtr
end if
'...
The above is quite close to what you need to do if you want to use a function ptr.
If you want ot to use function names as strings, you can use a similar way but use
thinBasic_FunctionGetPtr
and
thinBasic_FunctionSimpleCall
UI and TBEM modules uses the above methods respectively for DIALOGs and CONTROLs script callbacks and for functions firing on events.
Hope to have understood yur question correctly and not confused you too much.
Eros
Chris Holbrook
12-01-2009, 22:56
Hi Eros,
having read your reply and looked at the thincore function prototypes, it looks as if I could avoid address-passing altogether and use a call by name instead, with the script functions becoming "new" thinBasic statements. I shall try a simple example first - like a statement to call that popup calendar.
Forgive the barrage of questions - I have to learn to "think thin!"
Thanks!
ErosOlmi
12-01-2009, 22:59
I'm happy if I can help, always :)
So feel free to ask whatever you need.
Ciao
Eros
Chris Holbrook
13-01-2009, 00:29
OK, but going back to Petr's comment that thinbasic_FunctionGetPtr should be called during dll initialisation, then the dll has to know the names of the functions to be called - which is not the case! So, can this function be called safely after initialisation?
Chris Holbrook
13-01-2009, 01:31
Well, it appears to work. So far I am not passing any parameters back to the script.
' first attempt at making the module call the script
' this is the script!
uses "thintrylib"
declare sub go lib "thintrylib.dll" alias "go" ()
declare sub registerfn lib "thintrylib.dll" alias "registerfn" ( byval s as string)
'---------------------------------
function first() as long
msgbox 0, "first"
end function
'---------------------------------
function second() as long
msgbox 0, "second"
end function
'---------------------------------
function tBMAIN () as long
registerfn("first")
registerfn("second")
go()
end function
' this is the PowerBasic dll
#compile dll
#dim all
#include "Win32API.inc"
#include "thinCore.inc"
type fnref
fnname as asciz * 64
fnaddr as dword
end type
global ghInstance as dword
global fns() as fnref
'-------------------------------------------------------------------------------
' functions required by ThinCore
'----------------------------------------------------------------------------
function LoadLocalSymbols alias "LoadLocalSymbols" (optional byval sPath as string) export as long
' This function is automatically called by thinCore whenever this DLL is loaded.
' This function MUST be present in every external DLL you want to use
' with thinBasic
' Use this function to initialize every variable you need and for loading the
' new symbol (read Keyword) you have created.
'----------------------------------------------------------------------------
end function
'----------------------------------------------------------------------------
function UnLoadLocalSymbols alias "UnLoadLocalSymbols" () export as long
' This function is automatically called by thinCore whenever this DLL is unloaded.
' This function CAN be present but it is not necessary. If present, this function
' will be executed by thinBasic core when module will be released.
' Use this function to perform uninitialize process, if needed.
'----------------------------------------------------------------------------
function = 0&
end function
'-------------------------------------------------------------------------------
sub go alias "go" () export
local i, n as long
local s as string
for i = 0 to ubound(fns)
s = fns(i).fnname
? "calling you back, " + s
thinBasic_FunctionSimpleCall ( byval s )
next
end sub
'-------------------------------------------------------------------------------
sub registerfn alias "registerfn" ( byval s as string) export
local n as long
n = ubound(fns) + 1
redim preserve fns(0 to n) as global fnref
fns(n).fnname = s
fns(n).fnaddr = thinBasic_FunctionGetPtr(byval s)
end sub
'-------------------------------------------------------------------------------
' Main DLL entry point called by Windows...
'
function libmain (byval hInstance as long, _
byval fwdReason as long, _
byval lpvReserved as long) as long
select case fwdReason
case %DLL_PROCESS_ATTACH
'Indicates that the DLL is being loaded by another process (a DLL
'or EXE is loading the DLL). DLLs can use this opportunity to
'initialize any instance or global data, such as arrays.
ghInstance = hInstance
function = 1 'success!
'FUNCTION = 0 'failure! This will prevent the EXE from running.
case %DLL_PROCESS_DETACH
'Indicates that the DLL is being unloaded or detached from the
'calling application. DLLs can take this opportunity to clean
'up all resources for all threads attached and known to the DLL.
function = 1 'success!
'FUNCTION = 0 'failure!
case %DLL_THREAD_ATTACH
'Indicates that the DLL is being loaded by a new thread in the
'calling application. DLLs can use this opportunity to
'initialize any thread local storage (TLS).
function = 1 'success!
'FUNCTION = 0 'failure!
case %DLL_THREAD_DETACH
'Indicates that the thread is exiting cleanly. If the DLL has
'allocated any thread local storage, it should be released.
function = 1 'success!
'FUNCTION = 0 'failure!
end select
end function
Petr Schreiber
13-01-2009, 02:14
Hi Chris,
of course you can call thinbasic_FunctionGetPtr anytime, I just wrote about the most common use. I admit it was not truly general advice.
So - use it when you need :)
ErosOlmi
13-01-2009, 06:33
Chris,
I have to say your module is not exactly what we intend for a thinBasic module. The way you wrote it is close to a standard DLL and not a thinBasic module.
I know, it is not so easy to get at the beginning but it is normal that you wrote in that way due to our missing good documentation on the subject. Sorry about that :oops:
I will try to give you here a little more info to let you start. More detailed info can be found in TBJ (thinBasic Journal) issue 2 you can find here: http://community.thinbasic.com/index.php?topic=2310.msg17554#msg17554
Issue 2 has a chapter covering the very basic of thinBasic module creation.
1st:_________________________________________________
Usually every thinBasic module does not have to export any function other than: LoadLocalSymbols, UnLoadLocalSymbols and LibMain (only LoadLocalSymbols is mandatory)
So no need to use DECLARE in your scripts to be able to use thinBasic module keywords (see later in this post).
When in your script you use something like:
USES "MyModule"
thinBasic automatically loads the module and call LoadLocalSymbols
When the module is not needed anymore thinBasic call UnLoadLocalSymbols if present.
2nd:_________________________________________________
you create new thinBasic keywords using the thinBasic SDK interface function called thinBasic_LoadSymbol
You need to call this function as many times as many new keywords you want to create. And usually this function is executed inside LoadLocalSymbols
thinBasic_LoadSymbol ask thinCore engine to create a new keyword and connects it to the internal compiled one passing its pointer.
3rd:_________________________________________________
Inside your compiled functions, you need to parse the syntax you prefer using the many SDK function interface has given to the programmer. So it is programmer responsibility to decide if to parse ( and ), if you want to parse a string or a numeric expression, to parse a comma or something else, and so on.
_________________________________________________
Attached to this post a revised version of your module and script example.
and here the code
_________________________________________________
Module:
_________________________________________________
' this is the PowerBasic dll
#Compile Dll
#Dim All
#Include "Win32API.inc"
#Include "thinCore.inc"
Type fnref
fnname As Asciz * 64
fnaddr As Dword
End Type
Global ghInstance As Dword
Global fns() As fnref
'-------------------------------------------------------------------------------
Sub Exec_Go
'-------------------------------------------------------------------------------
'---Function Script syntax: Go
'-------------------------------------------------------------------------------
Local i, n As Long
Local s As String
'---Check if "(" then ")"
If thinBasic_CheckOpenParens_Optional Then thinBasic_CheckCloseParens_Mandatory
For i = 0 To UBound(fns)
s = fns(i).fnname
? "calling you back, " + s
thinBasic_FunctionSimpleCall ( ByVal s )
Next
End Sub
'-------------------------------------------------------------------------------
Sub Exec_RegisterFn
'-------------------------------------------------------------------------------
'---Function Script syntax: RegisterFn(AString)
'-------------------------------------------------------------------------------
Local ParPresent As Long
Local sFunName As String
Local pFunPtr As Long
Local uIdx As Long
'---Check (
ParPresent = thinBasic_CheckOpenParens_Optional
'---Parse a string exoression
thinBasic_ParseStr sFunName
'---If ( was present then we need )
If ParPresent Then thinBasic_CheckCloseParens_Mandatory
'---If all went fine ...
If thinBasic_ErrorFree Then
'---Ask thinBasic to check it is a function. If yes, returns its internal thinBasic ptr
pFunPtr = thinBasic_FunctionGetPtr(UCase$(sFunName)) '---Always UCASE
If pFunPtr Then
uIdx = UBound(fns) + 1
ReDim Preserve fns(0 To uIdx) As Global fnref
fns(uIdx).fnname = sFunName
fns(uIdx).fnaddr = pFunPtr
End If
End If
End Sub
'-------------------------------------------------------------------------------
' functions required by ThinCore
'----------------------------------------------------------------------------
Function LoadLocalSymbols Alias "LoadLocalSymbols" (Optional ByVal sPath As String) Export As Long
' This function is automatically called by thinCore whenever this DLL is loaded.
' This function MUST be present in every external DLL you want to use
' with thinBasic
' Use this function to initialize every variable you need and for loading the
' new symbol (read Keyword) you have created.
'----------------------------------------------------------------------------
'---thinBasic_LoadSymbol asks to thinBasic engine to create a new keyword and
'---connects it to the module compiled function or SUB. It is importantant to indicate
'---what return code it expects:
'--- %thinBasic_ReturnNone for SUBS
'--- %thinBasic_ReturnSTRING for functions returning a string
'--- %thinBasic_ReturnNumber for functions returning an EXT
thinBasic_LoadSymbol "Go" , %thinBasic_ReturnNone , CodePtr(Exec_Go ), %thinBasic_ForceOverWrite
thinBasic_LoadSymbol "RegisterFn" , %thinBasic_ReturnNone , CodePtr(Exec_RegisterFn ), %thinBasic_ForceOverWrite
End Function
'----------------------------------------------------------------------------
Function UnLoadLocalSymbols Alias "UnLoadLocalSymbols" () Export As Long
' This function is automatically called by thinCore whenever this DLL is unloaded.
' This function CAN be present but it is not necessary. If present, this function
' will be executed by thinBasic core when module will be released.
' Use this function to perform uninitialize process, if needed.
'----------------------------------------------------------------------------
Function = 0&
End Function
'-------------------------------------------------------------------------------
' Main DLL entry point called by Windows...
'
Function LibMain (ByVal hInstance As Long, _
ByVal fwdReason As Long, _
ByVal lpvReserved As Long) As Long
Select Case fwdReason
Case %DLL_PROCESS_ATTACH
'Indicates that the DLL is being loaded by another process (a DLL
'or EXE is loading the DLL). DLLs can use this opportunity to
'initialize any instance or global data, such as arrays.
ghInstance = hInstance
Function = 1 'success!
'FUNCTION = 0 'failure! This will prevent the EXE from running.
Case %DLL_PROCESS_DETACH
'Indicates that the DLL is being unloaded or detached from the
'calling application. DLLs can take this opportunity to clean
'up all resources for all threads attached and known to the DLL.
Function = 1 'success!
'FUNCTION = 0 'failure!
Case %DLL_THREAD_ATTACH
'Indicates that the DLL is being loaded by a new thread in the
'calling application. DLLs can use this opportunity to
'initialize any thread local storage (TLS).
Function = 1 'success!
'FUNCTION = 0 'failure!
Case %DLL_THREAD_DETACH
'Indicates that the thread is exiting cleanly. If the DLL has
'allocated any thread local storage, it should be released.
Function = 1 'success!
'FUNCTION = 0 'failure!
End Select
End Function
Script example:
_________________________________________________
uses "TryIt"
'---------------------------------
function first() as long
msgbox 0, "first"
end function
'---------------------------------
function second() as long
msgbox 0, "second"
end function
'---------------------------------
function tBMAIN () as long
registerfn("first")
registerfn("second")
go()
end function
Let me know.
Ciao
Eros
ErosOlmi
13-01-2009, 07:21
In case needed, after the very basic in thinBasic module creation is clear, I can produce other examples covering the way to:
parse string and numeric expressions
parse variables passed BYREF
parse functions by name and not only by string name
parsing arrays or UDT BYREF
...
Consider all thinBasic modules are created with the same thinBasic SDK every user can have hands on :)
Ciao
Eros
Chris Holbrook
13-01-2009, 09:39
Petr, thanks for that clarification.
Eros - big thanks! what you have posted this moring will save me a lot of time and questions.
And yes, I shall need to pass parameters. For the current library functions I need to be able to pass both strings and 4-byte integers "byval" and "byref", so examples of these would be very useful.
Thinking ahead, the means of raising meaningful error messages would also be useful. Can a module define its own error numbers and messages, or does it have to use predefined error messages?
Regards,
Chris
ErosOlmi
13-01-2009, 11:32
Chris,
I'm at work now so not enough time to reply but I will do later.
Can you please give me some light of what you are trying to do with the module you are testing?
I've the impression you are going into a direction I smell but not sure it is right. If I'm right and you want create a module to embed a control with its callback function I can show you how to achieve it in the same way the UI module does with all the current supported thinBasic controls.
Ciao
Eros
Petr Schreiber
13-01-2009, 12:19
Can a module define its own error numbers and messages, or does it have to use predefined error messages?
Chris,
I use the following for TBGL module:
thinbasic_RunTimeError ( %ERR__MODULE_SPECIFIC, "Add your custom error description here" )
ErosOlmi
13-01-2009, 13:41
Yes, sorry, I forgot to reply on that
Thanks Petr.
Chris Holbrook
13-01-2009, 20:27
I use the following for TBGL module Ah good. They are local to the module then. Thanks!
Chris Holbrook
13-01-2009, 20:33
Can you please give me some light of what you are trying to do with the module you are testing? The library I am developing uses user-written functions. The user registers these functions and other library functions call them at the appropriate point.
Regards,
Chris