Object serialization, allowing save and load of class structure data

Post

Posted
Rating:
#1 (In Topic #319)
Avatar
Regular
Godzilla is in the usergroup ‘Regular’
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.

Attachment
Online now: No Back to the top

Post

Posted
Rating:
#2
Avatar
Regular
Godzilla is in the usergroup ‘Regular’
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.

Attachment
Online now: No Back to the top

Post

Posted
Rating:
#3
Avatar
Guru
cogier is in the usergroup ‘Guru’
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.

Attachment

The code would be more streamlined if I could remember how to make textboxes into arrays, lol. Tunnel vision. :roll:

Try this: -

Code (gambas)

  1. Dim TextBoxes As TextBox[] = [TextBox1, TextBox2, TextBox3, TextBox4]
Online now: Yes Back to the top

Post

Posted
Rating:
#4
Avatar
Regular
Godzilla is in the usergroup ‘Regular’
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 Serialization - Visual Basic | Microsoft Learn
Online now: No Back to the top

Post

Posted
Rating:
#5
Avatar
Regular
Godzilla is in the usergroup ‘Regular’
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:

Code (gambas)

  1. 'This will save the object to file.
  2.  
  3. Dim MyObject As MyClass
  4. Dim hFile As File
  5.  
  6. hFile = Open "~/Desktop/testtest" For Create
  7. SaveValues(hFile, MyObject)
  8. Close #hFile
  9.  
  10.  
  11. 'This will load the object from file.
  12.  
  13. hFile = Open "~/Desktop/testtest" For Read
  14. LoadValues(hFile, MyObject)
  15. 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:

Code (gambas)

  1.   FileObject = Open "/tmp/A_SubCategory_ByDate" For Write Create
  2.     For TheCounter = 0 To A_SubCategory_ByDate.Max
  3.       SaveValues(FileObject, A_SubCategory_ByDate[TheCounter])
  4.     Next
  5.   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:

Code (gambas)

  1.     FileObject = Open "/tmp/A_SubCategory_ByDate" For Read
  2.     Do While Not Eof(FileObject)
  3.       A_SubCategory_ByDate.Resize(A_SubCategory_ByDate.Count + 1)
  4.       A_SubCategory_ByDate[TheCounter] = New DataStructure
  5.       LoadValues(FileObject, A_SubCategory_ByDate[TheCounter])
  6.       TheCounter = TheCounter + 1
  7.     Loop
  8.     Close #FileObject

And here's the two functions that do the magic:

Code (gambas)

  1. Public Function SaveValues(hStream As Stream, hObject As Object)
  2.  
  3.   Dim hCls As Class = Object.Class(hObject)
  4.   Dim sTmp As String  
  5.  
  6.    
  7.   For Each sTmp In hCls.Symbols.Sort(gb.Binary)
  8.     If hCls[sTmp].Kind = Class.Variable Then
  9.  
  10.       Select Case hCls[sTmp].Type
  11.  
  12.       Case "h" 'short
  13.         Write #hStream, Object.GetProperty(hObject, sTmp) As Short
  14.  
  15.       Case "b" 'boolean
  16.         Write #hStream, Object.GetProperty(hObject, sTmp) As Boolean
  17.  
  18.       Case "c" 'byte
  19.         Write #hStream, Object.GetProperty(hObject, sTmp) As Byte
  20.  
  21.       Case "i" 'integer
  22.         Write #hStream, Object.GetProperty(hObject, sTmp) As Integer
  23.  
  24.       Case "l" 'long
  25.         Write #hStream, Object.GetProperty(hObject, sTmp) As Long
  26.  
  27.       Case "s" 'string
  28.         Write #hStream, Object.GetProperty(hObject, sTmp) As String
  29.        
  30.       Case "d" 'date
  31.         Write #hStream, Object.GetProperty(hObject, sTmp) As Date
  32.        
  33.       Case "f" 'float
  34.         Write #hStream, Object.GetProperty(hObject, sTmp) As Float
  35.        
  36.       Case "o" 'object
  37.         Write #hStream, Object.GetProperty(hObject, sTmp) As Object
  38.        
  39.       Case "p" 'pointer
  40.         Write #hStream, Object.GetProperty(hObject, sTmp) As Pointer
  41.        
  42.       Case "g" 'single
  43.         Write #hStream, Object.GetProperty(hObject, sTmp) As Single
  44.        
  45.       Case "v" 'variant
  46.         Write #hStream, Object.GetProperty(hObject, sTmp) As Variant
  47.  
  48.       Case Else
  49.         Print "Missing variable type: " & hCls[sTmp].Type
  50.         Error.Raise("Error! Missing variable type definition.")
  51.       End Select    
  52.     Endif
  53.   Next
  54.  
  55.  
  56.  
  57. Public Function LoadValues(hStream As Stream, hObject As Object)
  58.  
  59.   Dim hCls As Class = Object.Class(hObject)
  60.   Dim sTmp As String
  61.  
  62.   Dim h As Short
  63.   Dim c As Byte
  64.   Dim l As Long
  65.   Dim d As Date
  66.   Dim f As Float
  67.  
  68.   For Each sTmp In hCls.Symbols.Sort(gb.Binary)
  69.     If hCls[sTmp].Kind = Class.Variable Then
  70.  
  71.       Select Case hCls[sTmp].Type
  72.  
  73.       Case "h" 'short
  74.         h = Read #hStream As Short
  75.         Object.SetProperty(hObject, sTmp, h)
  76.  
  77.       Case "b" 'boolean
  78.         b = Read #hStream As Boolean
  79.         Object.SetProperty(hObject, sTmp, b)
  80.  
  81.       Case "c" 'byte
  82.         c = Read #hStream As Byte
  83.         Object.SetProperty(hObject, sTmp, c)
  84.  
  85.       Case "i" 'integer
  86.         i = Read #hStream As Integer
  87.         Object.SetProperty(hObject, sTmp, i)
  88.  
  89.       Case "l" 'long
  90.         l = Read #hStream As Long
  91.         Object.SetProperty(hObject, sTmp, l)
  92.  
  93.       Case "s" 'string
  94.         s = Read #hStream As String
  95.         Object.SetProperty(hObject, sTmp, s)
  96.  
  97.       Case "d" 'date
  98.         d = Read #hStream As Date
  99.         Object.SetProperty(hObject, sTmp, d)
  100.        
  101.       Case "f" 'float
  102.         f = Read #hStream As Float
  103.         Object.SetProperty(hObject, sTmp, f)
  104.        
  105.       Case "o" 'object
  106.         o = Read #hStream As Object
  107.         Object.SetProperty(hObject, sTmp, o)
  108.        
  109.       Case "p" 'pointer
  110.         p = Read #hStream As Pointer
  111.         Object.SetProperty(hObject, sTmp, p)
  112.        
  113.       Case "g" 'single
  114.         g = Read #hStream As Single
  115.         Object.SetProperty(hObject, sTmp, g)
  116.        
  117.       Case "v" 'variant
  118.         v = Read #hStream As Variant
  119.         Object.SetProperty(hObject, sTmp, v)
  120.  
  121.       Case Else
  122.         Print "Missing variable type: " & hCls[sTmp].Type
  123.         Error.Raise("Error! Missing variable type definition.")
  124.       End Select
  125.  
  126.     Endif
  127.   Next
  128.  
Online now: No Back to the top

Post

Posted
Rating:
#6
Avatar
Regular
Godzilla is in the usergroup ‘Regular’
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:

Code (gambas)


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.

Code (gambas)

  1. FileObject = Open "/tmp/A_SubCategory_ByDate" For Write Create
  2.   For TheCounter = 0 To A_SubCategory_ByDate.Max
  3.     If SubCategory_ByDate[TheCounter].Date_Dat = Null Then
  4.         SubCategory_ByDate[TheCounter].Date_Dat = CDate("7/4/1776")
  5.     EndIf
  6.     SaveValues(FileObject, A_SubCategory_ByDate[TheCounter])
  7.   Next[
  8. 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:

Code (gambas)

  1. Case "d" 'date
  2.     d = Read #hStream As Date
  3.     If d = CDate("7/4/1776") Then
  4.        d = Null
  5.     EndIf
  6.     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.
Online now: No Back to the top

Post

Posted
Rating:
#7
Avatar
Administrator
gbWilly is in the usergroup ‘unknown’
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
- Gambas Dutch translator
- Gambas wiki content contributor
- Gambas debian/ubuntu package recipe contributor
- GambOS, a distro for learning Gambas and more…
- Gambas3 Debian/Ubuntu repositories


… there is always a Catch if things go wrong!
Online now: No Back to the top

Post

Posted
Rating:
#8
Avatar
Regular
sjsepan is in the usergroup ‘Regular’
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
Online now: No Back to the top

Post

Posted
Rating:
#9
Avatar
Regular
Godzilla is in the usergroup ‘Regular’

gbWilly said

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.

Attachment
Online now: No Back to the top

Post

Posted
Rating:
#10
Avatar
Regular
Godzilla is in the usergroup ‘Regular’

sjsepan said

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.
Online now: No Back to the top
1 guest and 0 members have just viewed this.