Object serialization, allowing save and load of class structure data

Post your Gambas programming questions here.
Post Reply
User avatar
Godzilla
Posts: 63
Joined: Wednesday 26th September 2018 11:20am

Object serialization, allowing save and load of class structure data

Post by Godzilla »

This is a pending solution to the problem I had with my experiment with the Task method (Task_Test) in my other thread, where data contained inside structured class arrays would mysteriously vanish after the Tasks/Forks had completed their cycles.

However, I felt this object serialization code, in and of itself, is so nifty and cool, I felt it deserved its own thread. People who may be looking for something like this probably aren't going to find it buried in a seemingly unrelated thread.

This code was posted by Jussi Lahtinen in 2013. I've put it in a project and modified it to handle Date variables, and to identify any unknown variable types it may encounter, using the Print command. It isn't complete in the sense that it handles all variables, but can easily be modified to do so.

The reason I call this a "pending" solution to the Task_Test problem is because the LoadValues and SaveValues object serialization functions need to be able to handle not just a structured class, but a structured class array. I'm not quite sure how to approach that. So I thought I'd post a project example here, which saves and loads a non-array class structure. With a request for someone to check out the project and modify it to work with arrays as well.

Then, I'll use it to modify the Task_Test project in my other thread to fix the bug and get it fully functional.

Here's some background as to why Task_Test fails, and I'll also include this info in the other thread along with a working project.

To quote from a Perl thread regarding this exact same problem someone encountered with using Fork:
It's not a matter of scoping, it's a matter of different processes. Parallel::ForkManager uses fork() (hence the name). This means that each version running in parallel is actually a separate process (a separate invocation of the perl interpreter) and thus separate memory. The variables will have the same name in each process, but they won't point to the same place in memory.
So in reality, the variables inside Task_Test aren't even being altered by the Task method. Task copies these processes and hands them over to the system (allowing the bypassing of the 1 thread limitation of Gambas). The system completes these tasks and then they vanish, along with all their variables and any alterations done to them. The Gambas program, and the variables within it, remain unchanged for all practical purposes. That's why variables that were seemingly packed with data a microsecond before apparently return back to their empty undeclared state immediately. They've not actually been touched.

One solution to this problem is to use the same solution cogier came up with, allowing a parent program to communicate to and from satellite programs, which was to use File.Save and File.Load. The problem with this in the case of Task_Test is: the structured class arrays I use are too complex to be handled by File.Save and File.Load. Object serialization is the solution, and a nifty one at that.

Thanks for any help. Here's the example project.
Attachments
Object_Serialization.tar.gz
(14.93 KiB) Downloaded 431 times
User avatar
Godzilla
Posts: 63
Joined: Wednesday 26th September 2018 11:20am

Re: Object serialization, allowing save and load of class structure data

Post by Godzilla »

Got it!

My mistake was I thought I'd have to somehow change the SaveValues and LoadValues functions to work with arrays. That turned out to be unnecessary. The attached project saves and loads a 3-field structured class array. And it even has a generate button to automatically populate fields, if you don't want to manually enter data into each textbox. The code would be more streamlined if I could remember how to make textboxes into arrays, lol. Tunnel vision. :roll:

Great! So, the next step is to implement this code into a Task/Fork process to make it much more useful. I'll put that in my other thread. But it will have to wait until tomorrow. I'm out of coding time for today. :|

I hope you find this code useful.
Attachments
Object_Serialization_Array.tar.gz
(16.46 KiB) Downloaded 364 times
User avatar
cogier
Site Admin
Posts: 1118
Joined: Wednesday 21st September 2016 2:22pm
Location: Guernsey, Channel Islands

Re: Object serialization, allowing save and load of class structure data

Post by cogier »

I looked up 'Object serialization' but I don't understand what it does. However you could simplify your code in places. Have a look at the attached.
Action.tar.gz
(14.66 KiB) Downloaded 377 times
The code would be more streamlined if I could remember how to make textboxes into arrays, lol. Tunnel vision. :roll:
Try this: -
Dim TextBoxes As TextBox[] = [TextBox1, TextBox2, TextBox3, TextBox4]
User avatar
Godzilla
Posts: 63
Joined: Wednesday 26th September 2018 11:20am

Re: Object serialization, allowing save and load of class structure data

Post by Godzilla »

Hey cogier,

Thanks for your reply. Once again, I neglected to take the time to explain what my project actually does. Kicking myself to not do that again.

In layman's terms, object serialization is basically File.Save and File.Load on steroids. Its a way of saving and loading of complex structures, instead of a single variable type.

The Object_Serialization_Array project demonstrates how save and load an array of a class variable. The class variable I designed has 5 fields, of various data types. In theory, it could be designed to have hundreds or even thousands of fields, of all data types. And its all neatly packed in just one super variable, and all saved and loaded by this method into one single speedy little binary file. I love it.

So i made an array out of this super variable, of 3 indexes [0] [1] [2]. There's 15 textboxes where data can be entered into these fields. The first column of 5 textboxes is for array index [0]. The 2nd column is array index [1]. And the 3rd column is array index [2].

Entering data into these textboxes (or pressing the Generate button), you can then click the Save button and all the array indexes, along with all the different variable types, gets saved into one single binary file. You can close the program, run it again, and click the Load button. And everything that had been saved earlier is instantly loaded back into a perfectly re-created class array, from which all its fields and indexes appear back into their respective textboxes, .

The ability to save and load complex things opens up a world of flexibility and possibilities, when sending data from a Fork back to its parent program, before the Fork vanishes with all the work its done.

Your way of filling textboxes in the project you provided, I must say, very clever! I didn't know about the Action property. I like it. I'm currently retooling my Object_Serialization_Array project to use your Textboxes array method, which I should have learned from your other code submissions. Its a much better way and I need rely on it much more.

My 100% working Task_Test in my other thread will have to be delayed yet again. But there's not enough hours in the day to do the things I love, sigh. Weekend can't get here soon enough!

And lastly, some object serialization background, for those interested:
Serialization is the process of converting an object into a stream of bytes in order to store the object or transmit it to memory, a database, or a file. Its main purpose is to save the state of an object in order to be able to recreate it when needed. The reverse process is called deserialization.
continued here https://docs.microsoft.com/en-us/dotnet ... alization/
User avatar
Godzilla
Posts: 63
Joined: Wednesday 26th September 2018 11:20am

Re: Object serialization, allowing save and load of class structure data

Post by Godzilla »

For the sake of completeness, I've taken Jussi Lahtinen's object serialization code and completed it by adding support for all the variables that he hadn't taken the time to include. They are Single, Float, Variant, Object, and Pointer. So now it includes the entire spectrum of Gambas variables. I haven't strictly tested each and every newly-added variable. But everything appears to be correct, and should work as expected.

The purpose is to save and load objects. It uses compact and fast binary files.

How to use, according to Jussi's instructions:
'This will save the object to file.

Dim MyObject As MyClass
Dim hFile As File

hFile = Open "~/Desktop/testtest" For Create
SaveValues(hFile, MyObject)
Close #hFile


'This will load the object from file.

hFile = Open "~/Desktop/testtest" For Read
LoadValues(hFile, MyObject)
Close #hFile 
This is the method I came up with to save an array, in this case a structured class array. But just modify as needed:
  FileObject = Open "/tmp/A_SubCategory_ByDate" For Write Create 
    For TheCounter = 0 To A_SubCategory_ByDate.Max
      SaveValues(FileObject, A_SubCategory_ByDate[TheCounter]) 
    Next
  Close #FileObject
This is the method I came up with to load an array, in this case a structured class array. But just modify as needed:
    FileObject = Open "/tmp/A_SubCategory_ByDate" For Read
    Do While Not Eof(FileObject)
      A_SubCategory_ByDate.Resize(A_SubCategory_ByDate.Count + 1)
      A_SubCategory_ByDate[TheCounter] = New DataStructure
      LoadValues(FileObject, A_SubCategory_ByDate[TheCounter])
      TheCounter = TheCounter + 1
    Loop
    Close #FileObject 
And here's the two functions that do the magic:
Public Function SaveValues(hStream As Stream, hObject As Object)

  Dim hCls As Class = Object.Class(hObject)
  Dim sTmp As String  
  
   
  For Each sTmp In hCls.Symbols.Sort(gb.Binary)
    If hCls[sTmp].Kind = Class.Variable Then
  
      Select Case hCls[sTmp].Type
  
      Case "h" 'short
        Write #hStream, Object.GetProperty(hObject, sTmp) As Short
  
      Case "b" 'boolean
        Write #hStream, Object.GetProperty(hObject, sTmp) As Boolean
  
      Case "c" 'byte
        Write #hStream, Object.GetProperty(hObject, sTmp) As Byte
  
      Case "i" 'integer
        Write #hStream, Object.GetProperty(hObject, sTmp) As Integer
  
      Case "l" 'long
        Write #hStream, Object.GetProperty(hObject, sTmp) As Long
  
      Case "s" 'string
        Write #hStream, Object.GetProperty(hObject, sTmp) As String
        
      Case "d" 'date
        Write #hStream, Object.GetProperty(hObject, sTmp) As Date
        
      Case "f" 'float
        Write #hStream, Object.GetProperty(hObject, sTmp) As Float
        
      Case "o" 'object
        Write #hStream, Object.GetProperty(hObject, sTmp) As Object
        
      Case "p" 'pointer
        Write #hStream, Object.GetProperty(hObject, sTmp) As Pointer
        
      Case "g" 'single
        Write #hStream, Object.GetProperty(hObject, sTmp) As Single
        
      Case "v" 'variant
        Write #hStream, Object.GetProperty(hObject, sTmp) As Variant
  
      Case Else
        Print "Missing variable type: " & hCls[sTmp].Type
        Error.Raise("Error! Missing variable type definition.")
      End Select    
    Endif
  Next
  

End

Public Function LoadValues(hStream As Stream, hObject As Object)

  Dim hCls As Class = Object.Class(hObject)
  Dim sTmp As String
  
  Dim h As Short
  Dim b As Boolean
  Dim c As Byte
  Dim i As Integer
  Dim l As Long
  Dim s As String
  Dim d As Date
  Dim f As Float
  Dim o As Object
  Dim p As Pointer
  Dim g As Single
  Dim v As Variant
  
  For Each sTmp In hCls.Symbols.Sort(gb.Binary)
    If hCls[sTmp].Kind = Class.Variable Then
  
      Select Case hCls[sTmp].Type
  
      Case "h" 'short
        h = Read #hStream As Short
        Object.SetProperty(hObject, sTmp, h)
  
      Case "b" 'boolean
        b = Read #hStream As Boolean
        Object.SetProperty(hObject, sTmp, b)
  
      Case "c" 'byte
        c = Read #hStream As Byte
        Object.SetProperty(hObject, sTmp, c)
  
      Case "i" 'integer
        i = Read #hStream As Integer
        Object.SetProperty(hObject, sTmp, i)
  
      Case "l" 'long
        l = Read #hStream As Long
        Object.SetProperty(hObject, sTmp, l)
  
      Case "s" 'string
        s = Read #hStream As String
        Object.SetProperty(hObject, sTmp, s)
  
      Case "d" 'date
        d = Read #hStream As Date
        Object.SetProperty(hObject, sTmp, d)
        
      Case "f" 'float
        f = Read #hStream As Float
        Object.SetProperty(hObject, sTmp, f)
        
      Case "o" 'object
        o = Read #hStream As Object
        Object.SetProperty(hObject, sTmp, o)
        
      Case "p" 'pointer
        p = Read #hStream As Pointer
        Object.SetProperty(hObject, sTmp, p)
        
      Case "g" 'single
        g = Read #hStream As Single
        Object.SetProperty(hObject, sTmp, g)
        
      Case "v" 'variant
        v = Read #hStream As Variant
        Object.SetProperty(hObject, sTmp, v)
  
      Case Else
        Print "Missing variable type: " & hCls[sTmp].Type
        Error.Raise("Error! Missing variable type definition.")
      End Select
  
    Endif
  Next

End
User avatar
Godzilla
Posts: 63
Joined: Wednesday 26th September 2018 11:20am

Re: Object serialization, allowing save and load of class structure data

Post by Godzilla »

Hey all,

I wanted to pass along some important info regarding object serialization/deserialization (S/D), for anyone who may be using it.

I don't know if its a bug, per se, but its something that resulted in seemingly random corruption of the binary files, in which serialized objects were being written to.

Normally, S/D works seamlessly and spectacularly well for me. But at random times, for some inexplicable reason, my binary files would somehow be saved as corrupted. This resulted in scrambled, unusable data being deserialized back into objects, and general mayhem. Along with strange "End Of File" errors in the middle of deserializing. Even though the loop specifically begins with "Do While Not Eof." Yes, when we have this kind of thing happening, we know something is seriously not right here!

So this has had me baffled for days, trying to figure out what the heck is going on. Well I just now figured it out, and I wanted to pass the information along to you ASAP.

My understanding of the default values of newly declared variables, as well as the proper way to reinitialize them, are as follows:
Short = 0
Boolean = False
Byte = 0
Integer = 0
Long = 0
String = Null
Date = Null
Float = 0.0
Object = Null
Pointer = 0
Single = 0.0
Variant = Null
All fine and good. But with S/D, there's an exception. My objects (structured class arrays) include Date variables. Now with what I use them for, under certain conditions the Date variables contain a date value. Other situations, they're unused and retain their default value of Null.

For whatever reason, when a Date value within an object has the value of Null, and that object is serialized, it wreaks havoc on the binary file. By the same token, my String values are sometimes not used, thus retaining their default Null value. But Null Strings do not appear to have any adverse effect on the binary files whatsoever. Neither do Integers whose values are 0, or any other variables I've used that happen to retain their default values.

Strange. But it is what it is.

A workaround for this situation, as I sit here contemplating (I don't have the time to test it tonight), would be to put a placeholder "dummy" date in place of Null dates, so that S/D can function normally. The dummy date would, ideally, never otherwise possibly be used in any other normal program operation.

The most seamless workaround to this problem would be to intercept Null Dates within the SaveValues function, and replace them with a dummy date. But since I don't know how that could be done (or if its even possible), the next best way would be to intercept Null Dates when calling SaveValues.
FileObject = Open "/tmp/A_SubCategory_ByDate" For Write Create 
  For TheCounter = 0 To A_SubCategory_ByDate.Max
    If SubCategory_ByDate[TheCounter].Date_Dat = Null Then
        SubCategory_ByDate[TheCounter].Date_Dat = CDate("7/4/1776")
    EndIf
    SaveValues(FileObject, A_SubCategory_ByDate[TheCounter]) 
  Next[
Close #FileObject
The LoadValues function can be changed from within, to intercept and correct the dummy date back to the Null value that its supposed to be:
Case "d" 'date
    d = Read #hStream As Date
    If d = CDate("7/4/1776") Then
       d = Null
    EndIf
    Object.SetProperty(hObject, sTmp, d)
Again, I haven't tested these workarounds yet. But you get the idea.

So I just wanted to throw this out there, for anyone who may be using S/D in their code. We don't want inexplicable mayhem randomly rearing its ugly head. :D Thanks for reading.
User avatar
gbWilly
Posts: 68
Joined: Friday 23rd September 2016 11:41am
Location: Netherlands
Contact:

Re: Object serialization, allowing save and load of class structure data

Post by gbWilly »

Hi Godzilla,

I must say I find this a quite interesting concept you've been working on.
Could you post the application with all added corrections so I can have a closer look at it all.
I might have some good use for it in one of my projects.

And thanks for figuring it all out ;)
gbWilly
- Dutch translation for Gambas3
- Gambas wiki content contributer


... there is always a Catch if things go wrong!
User avatar
sjsepan
Posts: 68
Joined: Saturday 12th October 2019 10:11pm
Location: Leeper, PA, USA
Contact:

Re: Object serialization, allowing save and load of class structure data

Post by sjsepan »

@Godzilla, This is another area that I'm glad someone is tackling. I used to do this in C# but am still too new in Gambas and am still catching up yet. This is huge (as in 'important'); Keep up the good work! 8-)
Steve
User avatar
Godzilla
Posts: 63
Joined: Wednesday 26th September 2018 11:20am

Re: Object serialization, allowing save and load of class structure data

Post by Godzilla »

gbWilly wrote: Wednesday 23rd October 2019 10:25am Hi Godzilla,

I must say I find this a quite interesting concept you've been working on.
Could you post the application with all added corrections so I can have a closer look at it all.
I might have some good use for it in one of my projects.

And thanks for figuring it all out ;)
Hey gbWilly, thank you for your reply.

I'm attaching a project to this message (modified from a project I posted earlier in this thread) called "Object_Serialization_Array_Error_Demo" which both demonstrates the error causing corruption of the binary save-file. As well as a checkbox that enables my workaround that both prevents save-file corruption, and allowing Null values within Object Date variables to safely be saved and loaded.

Also included on the form of this project is a large TextArea control containing instructions on how to both reproduce the error, and how to prevent the error (the specifics are in the code). Please pardon the eyesore of my program design. I try to cover all the bases.

Thank you again gbWilly for your interest. I think Serialization/Deserialization is an amazing tool that has so much potential to do so many great things. In fact, I'm already making great use of it. Again, I have to give Jussi Lahtinen credit for originally posting the basis of the S/D functions back in 2013.

I wish you the best of luck in the project you're working on in which S/D will come in handy.
Attachments
Object_Serialization_Array_Error_Demo.tar.gz
(18.6 KiB) Downloaded 385 times
User avatar
Godzilla
Posts: 63
Joined: Wednesday 26th September 2018 11:20am

Re: Object serialization, allowing save and load of class structure data

Post by Godzilla »

sjsepan wrote: Wednesday 23rd October 2019 8:24pm @Godzilla, This is another area that I'm glad someone is tackling. I used to do this in C# but am still too new in Gambas and am still catching up yet. This is huge (as in 'important'); Keep up the good work! 8-)
Steve
Hey Steve, thank you for your reply and interest.

Yes, S/D is really nothing new to other programming languages. But it seems to be something largely overlooked/unknown-about in the Gambas community. I think the realization for the need of powerful tools must precede the search for and appreciation of powerful tools.

I really think the SaveValues and LoadValues should be coded natively into Gambas, once any and all bugs have been found and squashed. But for all I know, maybe they're already included in one of the many Gambas components.

A few weeks ago I was unaware S/D even existed, or that multi-processing was even possible in Gambas. Being a power user, I was fascinated when cogier made me aware of the Task process, allowing multi-processing to be done. I was disappointed that these Task processes were unable to relay results of their processing back to the parent Gambas program.

S/D is a complete game changer. It works happily and seamlessly within Task processes, completing a half, and allowing for very powerful computing. I couldn't be happier with it. For me, to love one is to love the other. And to explore the possibilities of both.

I plan on exploring the limits of what can be done with both. I'll happily keep you and whoever else may be interested updated with any interesting things or methods related to S/D I may discover.

Thank you again Steve for your reply and your interest. And if you like, check out the new S/D-related demo project in my prior post in this thread, demonstrating both the S/D-related error and workaround I wrote about a few days ago.
Post Reply