PreCompiler for OOP and "IncludeLibraries"

Share your advanced PureBasic knowledge/code with the community.
GPI
PureBasic Expert
PureBasic Expert
Posts: 1394
Joined: Fri Apr 25, 2003 6:41 pm

PreCompiler for OOP and "IncludeLibraries"

Post by GPI »

The previous OOP solutions always had disadvantages. For example, a method could have completely different parameters to those specified in Interface. This can lead to pretty stupid programming errors. The PreCompiler is the result of this idea, which has meanwhile "escalated" and can do a few things more.

What does my PreCompiler do?
  • Support for OOP
  • Creation of Include-Libraries
  • When creating DLLs, import files are created
  • Creation of a PreProcess, which can be further processed without PreCompiler.
  • Original files are not changed.
  • Error messages are displayed by the IDE in the correct place
  • Runs on Mac and Windows
For a Linux version I don't have a Linux installation (and patience to get used to it). In principle, the PreCompiler is prepared for this. It is only necessary to create a block containing the Linux definitions at the beginning.

What are IncludeLlibraries?
Unfortunately, I can't offer any real libraries, this is the closest I can get. The basic structure of a IncludeLibrary corresponds to a DLL. The PreCompiler automatically detects in source code whether this library is needed and automatically adds it.

How does OOP work?
I assume that OOP is known in principle. A class is defined as:

Code: Select all

Interface <Class name>[extends <Parent>]
  <method>
EndInterface
Structure s<class name>[extends s<Parent>]
  <member>
EndStructure
DeclareClass(<class name>[,<Parent[,AllowNull]])

DefineClass(<class name>[,<Parent[,AllowNull]])
Procedure <class name>_<Methode>(*Self.s<class name> ...)
EndProcedure
DeclareClass is optional. For example, if it is if you want to create a class in a module. AllowNull can be #True or #False. This allows you to not define methods. Otherwise, the PreCompiler complains that methods are missing.

Besides the usual methods, there are a number of special cases:
The constructors: If successful, a #True must be returned.

Code: Select all

_Constructor (*self)
ConstructorEx (*self, *para)
<class name> (*self)
<Class name>Ex (*self, *para)
The destructors:

Code: Select all

_Destructor (*self)
De<class name> (*self)
The Copy-Constructor: If successful, a #True must be returned

Code: Select all

_CopyConstructor (*self)
The OOP functions of the PreCompiler support a parameter for class creation.

An object is created with

Code: Select all

<class name>\new ([*para])
is created. Either you get the object back or #Null.

Release goes with

Code: Select all

FreeObject (<Object>)
a copy of an object can be created with

Code: Select all

CopyObject (<Object>)
Further functions

Code: Select all

<class name>\IsAccessable (<Object>)
Returns #True if the object is addressed by this class. The object does not necessarily have to belong to the specified class!

Code: Select all

<class name>\IsClass (<Object>)
Returns #True if the object corresponds exactly to the specified class.

There are also two constants

Code: Select all

#<Class name>_NameTable
#<class name>_ParaTable
They contain the names (separated by ;) of each method of the class and the parameters (also separated by ;).
Unnecessary constants are removed by the PreCompiler, so that they do not create bigger exe.

How do I create a IncludeLibrary?
In principle, you can write a normal DLL with ProcedureDLL etc. The source code for such a library ideally has an extension ". pbLib". The PreCompiler then splits the whole file and saves it. This has to be done for every PureBasic version. There are the following additional commands:

Code: Select all

ResidentExport
...
EndResidentExport
A. res file for the compiler is created from the code segment between the two control words. Macros, structures, interfaces and constants are allowed. Anything else will result in an error message. The res - file makes it available everywhere, even in every module. So be carefull.

Code: Select all

ExportClass (<class name>)
Exports a class. The interface end interface and structure end structure area is packed into the res file. An exported class can be used as a parent (with Extends). Important: you have to write a "NEW"-procedure to create an object!

IMPORTANT!
No Module/EndModule are allowed in IncludeLibraries. PreCompiler will also acknowledge them accordingly with an error message.
IncludeLibraries may use other IncludeLibraries (including their exported classes). If you change a library, you should also rebuild all dependent libraries.

What is the automatic creation of an import file when creating a DLL?
When a DLL is created, a file with an import-endImport block is automatically created. The name is simple "<DLL-name>.imp.pb" .
It is located where the. DLL,. LIB and. EXP (Windows) are located.

What is important to note
Due to the way the PreCompiler works, it is not allowed to use any PreCompiler specific things or IncludeLibraries in the first line of the main file. In the second line there are no problems. If you don't pay attention to this, you get a module error.
IncludePath works with relative information more in the main file. The best thing to do is to avoid this command completely.
The PreCompiler is written for 5.61 and higher and assumes that it is compiled with Unicode.

How to set up the PreCompiler - Automatic version
Simple create the executeable (i recommend a empty directory) and launch it. A Setup should appear. Just

How to set up the PreCompiler - Manual version
First, you should teach the IDE the extension "pbLib". Under "File>Preferences..." add the extension "pbLib" in the area "Editor" "Code file extensions".

Compile the source code for the PreCompiler and enter it in the Tools menu. 4 entries are required.
Entry 1:
It fulfils a dual function. Once the PreCompiler is started here when you run Compile/Run. Second a PreProcess can be created by calling it directly from the menu. A "<FileName>.PreProcess.pb" will then be created.
  • Commandline = <Precomiler. exe>
  • Arguments = "%FILE" "%COMPILEFILE" "%EXECUTABLE"
  • WorkingDirectory = <Directory where the IncludeLibraries are to be stored>
  • Name = PreCompiler
  • Event to trigger the tool = Before Compile/Run
  • Wait until Tools end = Checked
Entry 2
  • Commandline = <Precomiler. exe>
  • Arguments = "%FILE" "%COMPILEFILE" "%EXECUTABLE"
  • WorkingDirectory = <Directory where the IncludeLibraries are to be stored>
  • Name = PreCompiler Executable
  • Event to trigger the tool = Before Create Executable
  • Wait until Tools end = Checked
  • Hide Tool from Main Menu = Checked
Entry 3
  • Commandline = <Precomiler. exe>
  • Arguments = "%FILE" "%COMPILEFILE" "%EXECUTABLE" CreateLib
  • WorkingDirectory = <Directory where the IncludeLibraries are to be stored>
  • Name = PreCompiler Create Lib
  • Event to trigger the tool = Menu or Shortcut
Entry 4
  • Commandline = <Precomiler. exe>
  • Arguments = "%FILE" "%COMPILEFILE" "%EXECUTABLE" DeleteLib
  • WorkingDirectory = <Directory where the IncludeLibraries are to be stored>
  • Name = PreCompiler Create Lib
  • Event to trigger the tool = Menu or Shortcut
The PreCompiler needs the libClass.pbLib for OOP. Simply save the source code below and convert it to a IncludeLib using the menu item. If the source file has the extension. pbLIB, pressing F5 is also sufficient for Compile/Run.

Now you only have to restart the compiler in the compiler menu.

Important to know: The PreCompiler can be 32Bit as well as 64Bit. He can then treat both. You don't have to create your own exe for each bit version. The directory for the IncludeLibraries can be the same for everyone. The names are chosen according to the operating system and the compiler version and do not overlap.
Last edited by GPI on Sun Oct 15, 2017 5:55 pm, edited 1 time in total.
GPI
PureBasic Expert
PureBasic Expert
Posts: 1394
Joined: Fri Apr 25, 2003 6:41 pm

Re: PreCompiler for OOP and "Fakelibraries"

Post by GPI »

Last edited by GPI on Sun Oct 15, 2017 5:56 pm, edited 1 time in total.
GPI
PureBasic Expert
PureBasic Expert
Posts: 1394
Joined: Fri Apr 25, 2003 6:41 pm

Re: PreCompiler for OOP and "Fakelibraries"

Post by GPI »

<reserved>
Last edited by GPI on Sun Oct 15, 2017 5:57 pm, edited 2 times in total.
GPI
PureBasic Expert
PureBasic Expert
Posts: 1394
Joined: Fri Apr 25, 2003 6:41 pm

Re: PreCompiler for OOP and "Fakelibraries"

Post by GPI »

<reserved>
Last edited by GPI on Sun Oct 15, 2017 5:56 pm, edited 1 time in total.
GPI
PureBasic Expert
PureBasic Expert
Posts: 1394
Joined: Fri Apr 25, 2003 6:41 pm

Re: PreCompiler for OOP and "Fakelibraries"

Post by GPI »

<reserved>
Last edited by GPI on Sun Oct 15, 2017 5:56 pm, edited 1 time in total.
GPI
PureBasic Expert
PureBasic Expert
Posts: 1394
Joined: Fri Apr 25, 2003 6:41 pm

Re: PreCompiler for OOP and "Fakelibraries"

Post by GPI »

<reserved>
Last edited by GPI on Sun Oct 15, 2017 5:56 pm, edited 1 time in total.
IndigoFuzz

Re: PreCompiler for OOP and "Fakelibraries"

Post by IndigoFuzz »

Looking forward to testing this out!...

The option for OOP is something PureBasic lacks, which is a shame as FreeBasic gives you the ability via Types (Yes I know, people don't want it to be added to the standard feature set~), especially when you can make such bigger apps which can be organised a lot better with the code, and a lot quicker/tidier to write.

Can I ask though why this solution wasn't packaged up? Seems less laborious to do than having to C/P through many posts xD
IndigoFuzz

Re: PreCompiler for OOP and "Fakelibraries"

Post by IndigoFuzz »

So, after compiling the Precompiler, installing the tools, and then compiling each of the *.pbLibs, the tests work very well, and I love how easy it is to override methods from a base class :)

Here's the code I tested with;

Code: Select all

; == BASE CLASS ==
Interface testClass
  DoSomething()
  CallBase()
EndInterface

Structure sTestClass
  *__vt  
EndStructure

DeclareClass(testClass)
  
Procedure testClass_DoSomething(*self.stestClass)
  Debug "Something Done"  
EndProcedure
  
Procedure testClass_CallBase(*self.stestClass)
  Debug "Base Class Method Called"
EndProcedure

DefineClass(testClass)
  
; == INHERITING CLASS ==
  
Interface testClass2 Extends testClass
EndInterface

Structure sTestClass2 Extends sTestClass
EndStructure

DeclareClass(testClass2, testClass)

Procedure testClass2_DoSomething(*self.stestClass)
  Debug "Something Else Done"
EndProcedure

DefineClass(testClass2, testClass)
   
; == TEST ==

Define *obj.testClass = testClass\new()
Define *obj2.testClass2 = testClass2\new()

Procedure DebugTest(*obj.testClass)
  *obj\DoSomething()  
  *obj\CallBase()
EndProcedure

DebugTest(*obj)
DebugTest(*obj2)
Overall, very good job, I'll definitely begin using this :)
User avatar
luis
Addict
Addict
Posts: 3876
Joined: Wed Aug 31, 2005 11:09 pm
Location: Italy

Re: PreCompiler for OOP and "Fakelibraries"

Post by luis »

I know there is the source (thanks) but I have no time right now even if I'm still curious.
Error messages are displayed by the IDE in the correct place
How did you solve the problem above without a #LINE preprocessor directive ?
"Have you tried turning it off and on again ?"
A little PureBasic review
GPI
PureBasic Expert
PureBasic Expert
Posts: 1394
Joined: Fri Apr 25, 2003 6:41 pm

Re: PreCompiler for OOP and "Fakelibraries"

Post by GPI »

IndigoFuzz wrote:Can I ask though why this solution wasn't packaged up? Seems less laborious to do than having to C/P through many posts xD
Planed for the next release. It will include a installation-script and can create on Windows real userlibraries.

@luis
I use Macros, there are in the libClass.pblib . I don't need to change anything in the code, the macros does this. This macros are compiled in a res file, so they are available everywhere.

The PreCompiler does many things. First he create are /PREPROCESS the macros only create an error free, but not working code. With this preprocess i can create the declaration and defition-part. then i store them in the temp directory. These files can be found at %temp%\PBPreCompiler (on mac on /tmp/PBPreCompiler). after that, i create the compiler-temp-main-file and modify only the first line (i add a Module). The macros are desgined,that when they found the module, they will include the temporary files and create a working code.

Because i don't need to change the files, the error-code should be correct. Only on pbLIB-Files this doesn't work, because i must do more stuff.
IndigoFuzz

Re: PreCompiler for OOP and "Fakelibraries"

Post by IndigoFuzz »

Have been playing around with it and have ported a module that I use as an application base over:

Code: Select all


Interface MApp Extends ClassBase
  
  SetName(Name.s)
  GetName.s()
  SetVendor(Vendor.s)
  GetVendor.s()
  SetUUID(UUID.s)
  GetUUID.s()
  
  SetVersion(Major, Minor)
  GetVerMajor.l()
  GetVerMinor.l()
  GetVerString.s()
  
  DoWindowEvents(Enable)
  
  OnInit()
  OnLoop()
  OnExit()
  
  Run()
  Exit(ExitCode = 0)
  
EndInterface

Structure sMApp Extends sClassBase
  m_appName.s
  m_uuid.s
  m_appVendor.s
  m_appVersion.l[2]
  m_uievt.a
  m_isRunning.a
  m_exitCode.i
EndStructure

DeclareClass(MApp, ClassBase)

Procedure MApp_SetName(*self.sMApp, Name.s)
  Protected *this.MApp = *self
  With *self
    \m_appName = Name
  EndWith
EndProcedure

Procedure.s MApp_GetName(*self.sMApp)
  Protected *this.MApp = *self
  With *self
    ProcedureReturn \m_appName
  EndWith
EndProcedure

Procedure MApp_SetVendor(*self.sMApp, Vendor.s)
  Protected *this.MApp = *self
  With *self
    \m_appVendor = Vendor
  EndWith
EndProcedure

Procedure.s MApp_GetVendor(*self.sMApp)
  Protected *this.MApp = *self
  With *self
    ProcedureReturn \m_appVendor
  EndWith
EndProcedure

Procedure MApp_SetUUID(*self.sMApp, UUID.s)
  Protected *this.MApp = *self
  With *self
    \m_uuid = UUID
  EndWith
EndProcedure

Procedure.s MApp_GetUUID(*self.sMApp)
  Protected *this.MApp = *self
  With *self
    ProcedureReturn \m_uuid
  EndWith
EndProcedure

Procedure MApp_SetVersion(*self.sMApp, Major, Minor)
  Protected *this.MApp = *self
  With *self
    \m_appVersion[0] = Major
    \m_appVersion[1] = Minor
  EndWith
EndProcedure

Procedure.l MApp_GetVerMajor(*self.sMApp)
  Protected *this.MApp = *self
  With *self
    ProcedureReturn \m_appVersion[0]
  EndWith
EndProcedure

Procedure.l MApp_GetVerMinor(*self.sMApp)
  Protected *this.MApp = *self
  With *self
    ProcedureReturn \m_appVersion[1]
  EndWith
EndProcedure

Procedure.s MApp_GetVerString(*self.sMApp)
  Protected *this.MApp = *self
  With *self
    ProcedureReturn \m_appName + " v" + Str(\m_appVersion[0]) + "." + Str(\m_appVersion[1])
  EndWith
EndProcedure

Procedure MApp_DoWindowEvents(*self.sMApp, Enable)
  Protected *this.MApp = *self
  With *self
    \m_uievt = Bool(Enable)
  EndWith
EndProcedure

Procedure MApp_OnInit(*self.sMApp)
  Protected *this.MApp = *self
  With *self
  EndWith
  ProcedureReturn #True
EndProcedure

Procedure MApp_OnLoop(*self.sMApp)
  Protected *this.MApp = *self
  With *self
  EndWith
EndProcedure

Procedure MApp_OnExit(*self.sMApp)
  Protected *this.MApp = *self
  With *self
  EndWith
EndProcedure

Procedure MApp_Run(*self.sMApp)
  Protected *this.MApp = *self
  With *self
    \m_isRunning = #True
    If *this\OnInit() = #True
      While \m_isRunning = #True
        If \m_uievt
          WindowEvent()
        EndIf
        *this\OnLoop()
      Wend
    EndIf
    *this\OnExit()
    End \m_exitCode
  EndWith
EndProcedure

Procedure MApp_Exit(*self.sMApp, ExitCode = 0)
  Protected *this.MApp = *self
  With *self
    \m_exitCode = ExitCode
    \m_isRunning = #False
  EndWith
EndProcedure

DefineClass(MApp, ClassBase)

;===============================
;-- Our Own Application Class --
;===============================

Interface MyApp Extends MApp
  ;OnInit() - Override
  ;OnLoop() - Override
  ;OnExit() - Override
EndInterface

Structure sMyApp Extends sMApp
  count.i
EndStructure

DeclareClass(MyApp, MApp)

Procedure MyApp_OnInit(*self.sMyApp)
  Protected *this.MyApp = *self
  Debug "Application Initialised: " + *this\GetVerString()
  ProcedureReturn #True ; Continue Executing  
EndProcedure

Procedure MyApp_OnLoop(*self.sMyApp)
  Protected *this.MyApp = *self
  *self\count = *self\count + 1
  If *self\count >= 5
    *this\Exit()
  EndIf
  Debug "Application Loop"
EndProcedure

Procedure MyApp_OnExit(*self.sMyApp)
  Debug "Application End"
EndProcedure

DefineClass(MyApp, MApp)

;- Create Own Own Application and Run
Define App.MyApp = MyApp\new()

App\SetName("Test App")
App\SetVendor("IndigoFuzz")
App\SetVersion(1,0)

App\Run()
Again, great work :)
GPI
PureBasic Expert
PureBasic Expert
Posts: 1394
Joined: Fri Apr 25, 2003 6:41 pm

Re: PreCompiler for OOP and "Fakelibraries"

Post by GPI »

when you set the "allownull" flag to #true, you don't need to declare the MApp_onLoop and MApp_OnExit

Code: Select all

;Procedure MApp_OnLoop(*self.sMApp)
;  Protected *this.MApp = *self
;  With *self
;  EndWith
;EndProcedure

;Procedure MApp_OnExit(*self.sMApp)
;  Protected *this.MApp = *self
;  With *self
;  EndWith
;EndProcedure

DefineClass(MApp, ClassBase,#True)
but you must define the methods in MYAPP!

And you should play with the Constructors and Destructors. When a class exends a parent class, all Constructors are called, first parent, then child. And Destructors form Child to Parent.
IndigoFuzz

Re: PreCompiler for OOP and "Fakelibraries"

Post by IndigoFuzz »

I figured that was the behaviour - Just added them in as safe placeholders should the inherited class not define the overrides :)
GPI
PureBasic Expert
PureBasic Expert
Posts: 1394
Joined: Fri Apr 25, 2003 6:41 pm

Re: PreCompiler for OOP and "Fakelibraries"

Post by GPI »

IndigoFuzz wrote:I figured that was the behaviour - Just added them in as safe placeholders should the inherited class not define the overrides :)
In this case the PreCompiler create a error-message, that a Method was not defined. The "allownull"-Flag is not inherited.

btw. you should move "DefineClass" above the methods, because in the next release the declares of the methods are generated here.

edit:
and by the way, you can include the *this - variable in the structure. Create a Constructor of the "Base"-Class and set the *self\this=*self. Now you can easily use *self\this\onEnd().
IndigoFuzz

Re: PreCompiler for OOP and "Fakelibraries"

Post by IndigoFuzz »

GPI wrote:
IndigoFuzz wrote:I figured that was the behaviour - Just added them in as safe placeholders should the inherited class not define the overrides :)
In this case the PreCompiler create a error-message, that a Method was not defined. The "allownull"-Flag is not inherited.

btw. you should move "DefineClass" above the methods, because in the next release the declares of the methods are generated here.

edit:
and by the way, you can include the *this - variable in the structure. Create a Constructor of the "Base"-Class and set the *self\this=*self. Now you can easily use *self\this\onEnd().

Awesome :)
Post Reply