Charles,
Congrats on OxygenBasic 60
Tested a few apps and all is well so far.
James
Thanks James,
There are some small but important additions to 050, to improve encapsulation options. It is now possible to nest functions, inside functions. We also have subroutines for use with gosub, as an alternative to using raw labels. A subroutine can be safely placed anywhere inside a parent function, and have its own local variables.
'05/05/2023
'test to demo inner functions
'defpro
function f()
function g()
function h()
function i()
print "i"
end function
i
end function
h
end function
g
end function
f
more to follow..
subroutine sb
...
if a>b
exit subroutine
end if
...
end subroutine
function main(int a=1,b=2,c=3) as int
a*=2
b*=3
gosub showAB
a*=4
b*=5
gosub showAB
subroutine showAB
...
if a>b
exit subroutine
end if
print "A=" a ", B=" b
end subroutine
end function
main
Subroutines can be nested, and also have their own local variables:
function main() as int
int i=1
subroutine sa
int i=2
subroutine sb
int i=3
subroutine sc
int i=4
end subroutine
gosub sc
print i '3
end subroutine
gosub sb
print i '2
end subroutine
gosub sa
print i '1
end function
main
Subs and functions are treated as equivalent by the o2 compiler. Both may or may not return a value. It is the programmer's choice of semantics. I've also introduced the equivalent term procedure. In the o2 compiler source code I use it to replace sub. It reads better and the 'sub' is only used in assembler.
Type and Class now follow the same route through the compiler. So if you dislike classes use type instead :)
Note that internally defined classes do not carry a VFTP (Virtual Function Table Pointer). The structure is exactly as defined.
So you can simply add functions to well known types
type RECT
int left
int top
int right
int bottom
'
function area() as int
'avoid confusion with rigft() and left()
int x=this.right-this.left
int y=bottom-top
return x*y
end function
end type
'
'#recordof rect
RECT r
r={1,1,3,5} '2*4
print r.area '8
A function that call two functions inside a structure !!!
It make life real easy.
Charles, I now have the proof that you have found an extra-temporal door to visit the fifth dimension.
No way a normal human can come up with those ideas.
Thanks a lot
I like !
type RECT
int left
int top
int right
int bottom
function x() as int
// avoid confusion with string right() / left()
int x = this.right - this.left
return x
end function
function y() as int
int y = this.bottom - this.top
return y
end function
function area() as int
return x() * y()
end function
end type
RECT r = {-80,-50,-40,0}
print "Size x " r.x '40
print "Size y " r.y '50
print "Area " r.area '2000
We have a Type structure with a function instead of a type?
Sounds interesting. I generally like such new Options.
This way we could make an manual "ON Initializer" and "ON Destroy" Option for Types.
However an automatic way would be nice.
I pledge for "On First Run" and "On Destroy" as an Optional Feature for each Object, like Types, Functions etc.
The Point is that OFTEN when using such Components, you will additionally need to initialize something
GLOBAL, or in MAIN(). And this currently can not yet be encapsulated easily.
Thinking about your TYPES. Seems like you have re-invented Objects in a preferable way (no COM Overhead).
Now also add the Option to let them "Live alone" (start them as own thread).
If we can design programs as in interaction of "Living types" (each an own thread comparable to a worker in a company) it would be a new sort of programming paradigma.
About Subroutines ... the advantage of a normal GOSUB in Powerbasic was that it will just "compile to a JSR.-Mnemonic". No Overhead.
Now if it becomes local variables, it will also get a stackframe which is Overhead.
Do Subroutines always have that stackframe or ONLY IF they have local variables?
Thanks Pierre,
I have some more updates in the pipeline for further nesting options - not quite working yet.
Hi Theo,
We have quite a wide range of options for classes/types. If constructor() and destructor() functions are included in the class, they are automatically invoked with new and del respectively, as in C++.
new RECT r
del RECT r
Regarding subroutines, they share the static and local space with their parent function, and are called directly without setting a new stack frame. Any variables defined inside a subroutine also belong to the parent function but are in a nested scope, so they are not visible outside the subroutine. The efficiency of subroutines is uncompromised.
@Charles Pegge thats the best way to implement it.
Finally. Assume that you have a Subprogram in a Library that will need to Declare and define a
GLOBAL Variable.
How would you do that inside the Library definition currently?
Not sure what you mean by subprogram, Theo.
You can define anything in a library header file. Do you mean getting access to a DLL's Global variable space? That is technically possible.
Hi Charles.
You amaze us every time you do an update.
Thank you for your great work.
I also have work for Help.
Cheers
Thanks Nicola,
I've added a few more things to RECT. It is starting to look rather bulky :)
'18/05/2023
macro RECTcalc()
'avoid confusion with rigft() and left()
float x=this.right-this.left
float y=bottom-top
end macro
'
type RECT
'
int left
int top
int right
int bottom
'
function constructor(int x1,y1,x2,y2)
with this
.left=x1
.top=y1
.right=x2
.bottom=y2
end with
print "RECT created"
end function
'
function destructor()
print "RECT destroyed"
end function
'
function area() as float
RECTcalc
return x*y
end function
'
function diagonal() as float
RECTcalc
return hypot(x,y)
end function
'
function aspect() as float
RECTcalc
return x/y
end function
'
end type
'
'#recordof rect
new RECT r(1,1,3,5)
print r.area '8
print r.diagonal '4.47
print r.aspect
del r
@Charles Pegge Here is a Code-Example. It shows the Part of a Library-Subprogram that needs to be put into the GLOBAL Scope of the Program. Currently i must call "#include Globalincludes.inc" and inside you will find stuff like this:
The Variable "%X_PROCESS_Globalincludes" is defined when that Library is used.
#IF %DEF(%X_PROCESS_Globalincludes)
#IF NOT %DEF(%X_PROCESS_Globalincludes_done)
%X_PROCESS_Globalincludes_done=1
%TH32CS_SNAPPROCESS = &H2& ' dwFlags for
%TH32CS_SNAPMODULE = &H8& ' CreateToolhelp32Snapshot
%MAX_MODULE_NAME32 = 255
TYPE WR_CR_PROCESSENTRY32
dwSize AS DWORD
cntUsage AS DWORD
th32ProcessID AS DWORD ' This process
th32DefaultHeapID AS LONG PTR
th32ModuleID AS DWORD ' Associated exe
cntThreads AS DWORD
th32ParentProcessID AS DWORD ' This process's parent process
pcPriClassBase AS LONG ' Base priority of process threads
dwFlags AS DWORD
szExeFile AS ASCIIZ * %MAX_PATH ' Path
END TYPE
TYPE WR_CR_MODULEENTRY32
dwSize AS DWORD
th32ModuleID AS DWORD ' This module
th32ProcessID AS DWORD ' Owning process
GlblcntUsage AS DWORD ' Global usage count on the module
ProccntUsage AS DWORD ' Module usage count in th32ProcessID's context
modBaseAddr AS BYTE PTR ' Base address of module in th32ProcessID's context
modBaseSize AS DWORD ' Size in bytes of module starting at modBaseAddr
hModule AS DWORD ' The hModule of this module in th32ProcessID's context
szModule AS ASCIIZ * (%MAX_MODULE_NAME32 + 1)
szExePath AS ASCIIZ * %MAX_PATH
END TYPE
DECLARE FUNCTION CreateToolhelp32Snapshot (BYVAL dwFlags AS DWORD, BYVAL th32ProcessID AS DWORD) AS LONG
DECLARE FUNCTION Process32First (BYVAL hSnapshot AS DWORD, pe AS WR_CR_PROCESSENTRY32) AS LONG
DECLARE FUNCTION Process32Next (BYVAL hSnapshot AS DWORD, pe AS WR_CR_PROCESSENTRY32) AS LONG
DECLARE FUNCTION Module32First (BYVAL hSnapshot AS DWORD, ME AS WR_CR_MODULEENTRY32) AS LONG
DECLARE FUNCTION Module32Next (BYVAL hSnapshot AS DWORD, ME AS WR_CR_MODULEENTRY32) AS LONG
DECLARE FUNCTION EnumProcesses (idProcess AS DWORD, BYVAL cb AS DWORD, cbNeeded AS DWORD) AS LONG
DECLARE FUNCTION GetModuleFileNameEx (BYVAL hProcess AS DWORD, BYVAL hModule AS DWORD, ModuleName AS ASCIIZ, BYVAL nSize AS DWORD) AS DWORD
DECLARE FUNCTION EnumProcessModules (BYVAL hProcess AS DWORD, hModule AS DWORD, BYVAL cb AS DWORD, cbNeeded AS DWORD) AS LONG
DECLARE FUNCTION GetVersionStringInfo (BYVAL Fname AS STRING) AS STRING
#ENDIF
#ENDIF
'------------------------------------------------------------------------------------------------
#IF %DEF(%WR_CS_Globalincludes)
#IF NOT %DEF(%WR_CS_Globalincludes_done)
%WR_CS_Globalincludes_done=1
GLOBAL WR_CS_A AS STRING, WR_CS_B AS STRING, WR_CS_C AS LONG
#ENDIF
#ENDIF
In the same way i currently must use a "#Include "Maincludes.inc"ยง that contains the Code-Portion of a Library-Code that needs to be placed into the "MAIN()".
Here is an example. There may be several Subprogram which may later use these Initialized Datatypes,
that need to be initializes in MAIN(). This is done using the definition of "%D_SList_01_INC".
I would like to place such stuff inside the Library instead of in an external Include like i must do now.
#IF %DEF(%D_SList_01_INC)
InitializeCriticalSection D_List_01A
DIM D_ListBAT_01(%D_ListPANZ01,%D_ListVd01)
D_Init_List_01()
D_ListC01.A=0 ' reale Elemente
D_ListC01.B=%D_ListVd01 ' Aktuelle Dimensionierung
D_ListC01.Z=%D_ListPANZ01 ' erste Dimension
D_ListC01.C=%D_ListVu01 ' Stepfaktor
D_ListC01.F=0 ' erste Freie Zelle
#ENDIF
This is very interesting. It is not new but O2 has a facility for sharing global space with DLLs, as well as exported procedures, of course. You will need an inc file for each DLL with which you want to share variables. The variables are declared in a bind block. I have and example in inc\winutil.inc for sharing GUI state variables:
'--------------------------------------
'INCLUDE ON BOTH SERVER AND CLIENT SIDE
'--------------------------------------
sys b
b = guistate()
bind b {
sys hWndMain,hInst,inst,hDC,hRC
int pixelform
int mposx,mposy,sposx,sposy,eposx,eposy,iposx,iposy
int mmove,bleft,bmid,bright,bwheel
int pause
int bkey,keyd,lastkey,lastchar
int running
int key[256]
}
'--------------------------------------
On the DLL (server) side, the shared space is set up as follows:
sys bu[0x400] 'STATIC BUFFER TO HOLD SHARED STATE VARIABLES
function guistate() as sys export
return @bu
end function
sys b = guistate()
bind b {
sys hWndMain,hInst,inst,hDC,hRC
int pixelform
int mposx,mposy,sposx,sposy,eposx,eposy,iposx,iposy
...
}
on the client side
declare guistate lib "MyGuiLib.dll" () as sys
sys b = guistate()
bind b {
sys hWndMain,hInst,inst,hDC,hRC
int pixelform
int mposx,mposy,sposx,sposy,eposx,eposy,iposx,iposy
...
}
It is also possible with this technique, to share variables between different Processes. Even between 32bit and 64bit processes, using Named Shared Memory.
The trick is to bind the variables to shared memory instead of a static buffer. And to ensure 32/64bit compatibility, the variables within a bind block always align to 64bit boundaries.
Creating Named Shared Memory
http://msdn.microsoft.com/en-us/library/aa366551(VS.85).aspx
These are really advanced Features, Charles.
You can bind variables to shared Memory, that is AMAZING.
Can it be also done with Powerbasic currently?
If so, how?
The question i had was rather simple.
What I would need is something like
#INTOMAIN
GLOBAL GV as STRING
#ENDINTOMAIN
Which can be anywhere in an Include file and will then "as Text" merged into at the Start of the MAIN() Prozedure (PBMAIN() in Powerbasic).
This can be done "In Text-Replace", if you just automatically add a "##MAININCLUDES##" automatically as first stement in MAIN() and then after all replace it with all the collected #INTOMAIN-Blocks.
And the same with
#INTOGLOBAL
#ENDINTOGLOBAL
which will then be a GLOBAL Declaration, as if it was done before all Includes.
While theoretically any Declaration outside of a Procedure is global, Includes can also be included "Inside a scope" especially now, that you support functions-in-functions therefore a "#IntoGlobal" also makes sense. For Libraries. Technically this will be just treated as "Text Replacement" so its not hard to implement.
However that what you have is also very interesting. While i already use shared Memory for interprocess communication, i could not yet bind it to variables.
In PowerBasic you can use absolute arrays or pointer variables, and assign the mapped address (with offsets) of the shared space to them. But there isn't a construct like bind which would allow a whole block of individual variables to be dimensioned in a specified location.
With regard to distributing blocks of global variables to different programs , I would generally use an inc file.
As an example I am going to use an include file: GuiInterface.inc. This is useable on both 'gui servers' and 'gui clients'
'GuiInterface.inc
'20/05/2023
'
' This file can be included in both client and
' server source code
'
' USAGE:
'
' SERVER ONLY:
' % GuiStateServer
' uses GuiInterface
'
' CLIENT ONLY:
' % GuiDllName "MyGuiLib.dll" 'your server DLL
' uses GuiInterface
'
'
#ifdef GuiStateServer
'
sys bu[0x400] 'STATIC BUFFER TO HOLD SHARED STATE VARIABLES
'
function guistate() as sys export
return @bu
end function
#undef bu 'end bu scope
'
#else
declare guistate lib GuiDllName () as sys
#endif
sys b = guistate()
'
bind b
sys hWndMain,hInst,inst,hDC,hRC
int pixelform
int mposx,mposy
int sposx,sposy
int eposx,eposy
int iposx,iposy
int mmove,bleft,bmid,bright,bwheel
int pause
int bkey,keyd,lastkey,lastchar
int running
int key[256]
end bind
#undef b 'end b scope