• Welcome to Theos PowerBasic Museum 2017.

PB Objects (global scope?)

Started by Paul Squires, September 01, 2008, 04:20:39 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Paul Squires

I want to model a real world scenario - one that I will encounter in my next project. I need to know if PB9 can do this otherwise I will be forced to use my old-style techniques.

Here is what I am looking to do:
(1) Create an object to hold customer data.
(2) Create a global TYPE that holds various dword elements that contain pointers to linked lists. Basically, I could have linked lists to hold customer data, supplier data, employee data, etc... There could really be any number of linked lists and each linked list will hold pointers to objects.
(3) In PBMain(), there will be a call to another function to query a database and load data into objects that are in turn held in the linked lists.
(4) Anywhere else in the program I should be able to iterate the customer linked list and retrieve the pointer to the object thereby allowing me to access the object.

I have attached some preliminary code to show what I am trying to accomplish. At this point I am running into problems. :)  Is there an easier or better way to do this?

(Edit: Deleted the zip file and uploaded the three source files)


Paul Squires
FireFly Visual Designer SQLitening Database System JellyFish Pro Editor
http://www.planetsquires.com

Theo Gottwald

Normally Objects should be perfect to modell "Real World Scenarios".

I'd not use a GLOBAL TYPE, but an OBJECT for this.
Then you access the DATA Global via PROPERTIES /SET/GET

Why should it not be possible?

José Roca

 
Can't unzip the attached file. Anyway, objects aren't the best solution for everything. Remembers me when macros where introduced: everybody tried to use them to replace procedures and functions. Unless it is for practicing, just use them when they are the best solution or when you have to write servers.

Theo Gottwald

#3
To me, an Object with some dynamic string-arays seems perfect for this:

Quoteto hold customer data, supplier data, employee data, etc

anyway better then a linked list. Faster or not ... It will befast enough anyway.
And it will be very readyble using GET/SET PROPERTIES.

I think that this sort of Applications are very good to be "Objectified", because you can have all Data inside one Object.
And there inside Arrays with dynamic strings.

Of course its true, that a lot of other things may become better readable when using objects, even if they are not needed.
But at the price of some speed and also they may get less compact when Objects are used without beeing needed.

Because if you really want pure Speed, then Arrays beat Linked Lists because of the way they are organized in Memory.
In this case you would just use GLOBAL Arrays with dynamic strings.

Paul Squires

I think that objects would be a perfect solution for this scenario (if I can get it to work) :)  Different elements of a "customer" should lend itself to objects such as name, id, address, etc... Likewise, invoices related to a customer would make good objects.

Hopefully you can nudge me in the right direction.

Right now I have two main problems.
(1) How do I create multiple objects from the same Interface definition? Check out the "CreateCustomers" function in the source code to see what it is I am trying to do.
(2) Once I have the object created, I want to be able to access that object from anywhere in my project. It seems like the only ways I can do this is to either have a GLOBAL gCustomer() AS CustomerInterface array -or- manually allocate memory for each customer and handle the memory block myself. However, the few tests I have tried to MemAlloc memory to hold the Class has failed because I can't see how to get the size of the Class (SIZEOF does not work).


Paul Squires
FireFly Visual Designer SQLitening Database System JellyFish Pro Editor
http://www.planetsquires.com

José Roca

#5
 
Quote
(1) How do I create multiple objects from the same Interface definition? Check out the "CreateCustomers" function in the source code to see what it is I am trying to do.

Using multiple object variables.


   Dim cCust As CustomerInterface
   Dim cCust2 As CustomerInterface

   cCust = Class "cCustomer"
   cCust2 = Class "cCustomer"



Function PBMain() As Long

   Local pListItem As Dword
   Local nData     As Dword


   ' Simulate calling a function that would query a database and
   ' load customer data into objects that are stored in a global
   ' linked list.
   CreateCustomers

   ? "List Count =" & Str$(List_GetCount(gMyApp.pListCust))


   Dim cCust As CustomerInterface
   cCust = Class "cCustomer"

   pListItem = List_GetFirst( gMyApp.pListCust )
   Do Until pListItem = 0
      List_GetItemInfo pListItem, "", nData

      [color=red][b]Poke Dword, VarPtr(cCust), nData[/b][/color]

      ? cCust.CustName

      pListItem = List_GetNext( pListItem )
   Loop

   ? "done"

End Function


Holy cow!

NEVER, NEVER, NEVER do something like that, unless you like memory leaks.

Use this instead:


      cCust = NOTHING
      Poke Dword, VarPtr(cCust), nData
      cCust.AddRef


Don't attempt to manipulate objects with pokes unless you fully understand how reference count works.

Paul Squires

Your .AddRef has triggered something in my brain! I should have a working solution in a few minutes.
Paul Squires
FireFly Visual Designer SQLitening Database System JellyFish Pro Editor
http://www.planetsquires.com

Paul Squires

Hi José,

If you would be so kind to test the attached code I would greatly appreciate it.

BTW, is there a way that I can check the reference count for an object?

Paul Squires
FireFly Visual Designer SQLitening Database System JellyFish Pro Editor
http://www.planetsquires.com

José Roca

#8
Lets see...


Function CreateCustomers() As Long
   Dim cCust As CustomerInterface

   ' Create the linked list that will hold all
   ' of the pointers to the customer objects.
   gMyApp.pListCust = List_Create(%FALSE, %FALSE)

   cCust = Nothing
   cCust = Class "cCustomer"
   cCust.AddRef
   cCust.CustID   = "1000"
   cCust.CustName = "Paul Squires"
   cCust.CustBal  = 1000.99
   List_Add gMyApp.pListCust, "", ObjPtr(cCust)


   cCust = Nothing
   cCust = Class "cCustomer"
   cCust.AddRef
   cCust.CustID   = "2000"
   cCust.CustName = "Tammy Squires"
   cCust.CustBal  = 2000.99
   List_Add gMyApp.pListCust, "", ObjPtr(cCust)

End Function


Both instances of the class are destroyed. The first by cCust = Nothing; the second by cCust going out of scope. So, the stored pointers will point to objects that no longer exist.


Function FreeCustomers() As Long
   Local pListItem As Dword
   Local nData     As Dword

   Dim cCust As CustomerInterface
   cCust = Class "cCustomer"

   pListItem = List_GetFirst( gMyApp.pListCust )
   Do Until pListItem = 0
      List_GetItemInfo pListItem, "", nData

      ' Simply retrieve one of the previously created customer
      ' objects and display the customer name.
      Poke Dword, VarPtr(cCust), nData

      cCust = Nothing

      pListItem = List_GetNext( pListItem )
   Loop

End Function



cCust = Class "cCustomer"


Creates an instance of the cCustomer class.


Poke Dword, VarPtr(cCust), nData


Overwrites the pointer stored in cCust, so the object created with cCust = Class "cCustomer" will never be released.


cCust = Nothing


Will call the Release method with the pointer that you have poked, unbalancing the reference count of that object, or destroying it (if it existed) if it had a reference count of 1.

You can do something like this:


Local oldPtr AS DWORD
oldPtr = OBJPTR(cCust)
Poke Dword, VarPtr(cCust), nData
...
Poke Dword, VarPtr(cCust), oldPtr


Quote
BTW, is there a way that I can check the reference count for an object?

The only way is the following:


DIM nCount AS DWORD
nCount = <pObj>.AddRef
nCount = <pObj>.Release
? nCount


Paul Squires

Hi José,

Thanks for the info - I appreciate it. :)

So, I am not getting a warm and fuzzy feeling that I can dynamically create objects on-the-fly for use globally throughout the program in a manner that I would like. The only solution that has worked for me so far is to create and manage a global array of interfaces (e.g. GLOBAL gCustomer() AS CustomerInterface). That will work but it is a little limited. For example, say I have 10 open companies then I would need 10 separate gCustomer() like arrays or switch to a two dimensional gCustomer() array (and all of these need to be defined at design time). I guess that I've gotten so used to creating TYPE's on-the-fly through using MemAlloc and pointers that I was hoping to apply the same concepts to the new classes.

The following code is another attempt to hack this. It creates new objects based on a global interface. The object pointer is then copied to a local variable where the properties of the object are set. The pointer to the object is saved in the linked list for later use. It seems to work - at least it does display "Paul Squires" and "Tammy Squires" when it is run.

Global gcCust As CustomerInterface

Function NewCustomerPointer() As Dword
   gcCust = Class "cCustomer"
   gcCust.AddRef
   Function = ObjPtr(gcCust)
End Function


'//
'// 
'//
Function CreateCustomers() As Long
   Dim pCust As Dword
   Dim cCust As CustomerInterface
   cCust = Class "cCustomer"
   
   ' Create the linked list that will hold all
   ' of the pointers to the customer objects.
   gMyApp.pListCust = List_Create(%FALSE, %FALSE)
   
   pCust = NewCustomerPointer
   Poke Dword, VarPtr(cCust), pCust
   cCust.CustID   = "1000"
   cCust.CustName = "Paul Squires"
   cCust.CustBal  = 1000.99
   List_Add gMyApp.pListCust, "", pCust


   pCust = NewCustomerPointer
   Poke Dword, VarPtr(cCust), pCust
   cCust.CustID   = "2000"
   cCust.CustName = "Tammy Squires"
   cCust.CustBal  = 2000.99
   List_Add gMyApp.pListCust, "", pCust

   cCust = Nothing
   
End Function


Paul Squires
FireFly Visual Designer SQLitening Database System JellyFish Pro Editor
http://www.planetsquires.com

Paul Squires

I have attached my latest effort. It saves and restores object pointers per Jose's previous suggestion. Everything appears to work okay. All critiques are welcome.  :)

Paul Squires
FireFly Visual Designer SQLitening Database System JellyFish Pro Editor
http://www.planetsquires.com

José Roca

 
As long as you don't advice to use this technique to people that doesn't understand how reference count works...

See what will happen with your previous example (the one that doesn't restore pointers):


Dim cCust As CustomerInterface
cCust = Class "cCustomer"

'pCust = NewCustomerPointer
' --- equal to:
gcCust = Class "cCustomer"
gcCust.AddRef
pCust = ObjPtr(gcCust)
Function = ObjPtr(gcCust)
' ---


Now gcCust has a reference count of 2

1 When you use gcCust = Class "cCustomer"
2 When you use gcCust.AddRef


Poke Dword, VarPtr(cCust), pCust


Now cCust holds a reference to gcCust. Therefore, the object created with cCust = Class "cCustomer" will never be released

...


'pCust = NewCustomerPointer
' --- equal to:
gcCust = Class "cCustomer"
gcCust.AddRef
pCust = ObjPtr(gcCust)
Function = ObjPtr(gcCust)
' ---


PB will call gcCust.Release when you assign the resulting reference of gcCust = Class "cCustomer" to it, but as it has a reference of 2, after Release is called will still have a reference of 1, therefore it will never be destroyed.

Now gcCust (a new object, not the previous one) has again a reference count of 2

1 When you use gcCust = Class "cCustomer"
2 When you use gcCust.AddRef


Poke Dword, VarPtr(cCust), pCust


Now cCust holds a reference to gcCust


cCust = Nothing


PB will call cCust.Release, but as it holds a reference to gcCust, the object created with cCust = Class "cCustomer" will never be released, nor gcCust, because it has a reference count of 2, and after cCust = Nothing still has a reference of 1.

You can't simply poke around...

Paul Squires

Hi José,

Thanks again, buddy!

The techniques that I have used so far are ugly and hackish. I am now working on an example where I create a "Company" object which has methods to create "Customer", "Supplier" and "Employee" objects and store them within arrays in the "Company" object. So far, this looks like a better approach. I will post a working example when I'm done. I guess it all just needs a change in the way of thinking. One thing is for sure that I won't be using Peek/Poke to manipulate these objects! ;D

Will be back soon.....
Paul Squires
FireFly Visual Designer SQLitening Database System JellyFish Pro Editor
http://www.planetsquires.com

Paul Squires

Okay, I have a skeleton program created with a start to dealing with objects working from the top down starting with a "Company" object. The company object in turn handles arrays of created objects for customers, suppliers, employees, etc... Right now, I am only working with the customer object. From the Customer object I should be able to include child objects in it with things like "invoice" objects, etc... as business logic dictates.

However, in the attached code I am getting an array not dimensioned error when I attempt to compile (on Line 85). Maybe I am having a brain freeze but I can't see  why the error is being thrown. Yesterday, I used an array in the linked list object and never got an array not dimensioned error. I am a little stumped.

Anyone out there with better eyes than mine today?
Paul Squires
FireFly Visual Designer SQLitening Database System JellyFish Pro Editor
http://www.planetsquires.com

José Roca

#14
 
Add the word INSTANCE as the scope descriptor.

Quote
   Class Method Create()
      ' Initialize the customer array
      Dim m_pCustomers(0) As CustomerInterface
   End Method

Change it to:


   Class Method Create()
      ' Initialize the customer array
      Dim m_pCustomers(0) As INSTANCE CustomerInterface
   End Method


Quote
      Method AddCustomer() As CustomerInterface
         Incr m_nCustomerCount
         ReDim Preserve m_pCustomers( m_nCustomerCount ) As CustomerInterface
         m_pCustomers(m_nCustomerCount) = Class "clsCustomer"
         
         ? Str$(UBound(m_pCustomers)) & "  " & FuncName$
         
         Method = m_pCustomers(m_nCustomerCount)  ' Return a reference to the new customer object
      End Method

Change it to:


      Method AddCustomer() As CustomerInterface
         Incr m_nCustomerCount
         ReDim Preserve m_pCustomers( m_nCustomerCount ) As INSTANCE CustomerInterface
         m_pCustomers(m_nCustomerCount) = Class "clsCustomer"
         
         ? Str$(UBound(m_pCustomers)) & "  " & FuncName$
         
         Method = m_pCustomers(m_nCustomerCount)  ' Return a reference to the new customer object
      End Method


P.S. If you ever use my new include files, you will have to change the name of your memory.inc file to something else.