Theos PowerBasic Museum 2017

Archive => Discussion - Legacy Software (PBWIN 9.0+/PBCC 5.0+) => Topic started by: Paul Squires on September 01, 2008, 04:20:39 AM

Title: PB Objects (global scope?)
Post by: Paul Squires on September 01, 2008, 04:20:39 AM
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)


Title: Re: PB Objects (global scope?)
Post by: Theo Gottwald on September 01, 2008, 10:11:25 AM
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?
Title: Re: PB Objects (global scope?)
Post by: José Roca on September 01, 2008, 11:14:49 AM
 
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.
Title: Re: PB Objects (global scope?)
Post by: Theo Gottwald on September 01, 2008, 12:22:14 PM
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.
Title: Re: PB Objects (global scope?)
Post by: Paul Squires on September 01, 2008, 01:23:57 PM
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).


Title: Re: PB Objects (global scope?)
Post by: José Roca on September 01, 2008, 02:31:05 PM
 
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.
Title: Re: PB Objects (global scope?)
Post by: Paul Squires on September 01, 2008, 03:24:50 PM
Your .AddRef has triggered something in my brain! I should have a working solution in a few minutes.
Title: Re: PB Objects (global scope?)
Post by: Paul Squires on September 01, 2008, 03:52:38 PM
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?

Title: Re: PB Objects (global scope?)
Post by: José Roca on September 01, 2008, 04:22:49 PM
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

Title: Re: PB Objects (global scope?)
Post by: Paul Squires on September 01, 2008, 04:47:47 PM
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


Title: Re: PB Objects (global scope?)
Post by: Paul Squires on September 01, 2008, 05:01:14 PM
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.  :)

Title: Re: PB Objects (global scope?)
Post by: José Roca on September 01, 2008, 05:53:07 PM
 
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...
Title: Re: PB Objects (global scope?)
Post by: Paul Squires on September 01, 2008, 06:50:14 PM
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.....
Title: Re: PB Objects (global scope?)
Post by: Paul Squires on September 01, 2008, 08:27:05 PM
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?
Title: Re: PB Objects (global scope?)
Post by: José Roca on September 01, 2008, 08:47:36 PM
 
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.
Title: Re: PB Objects (global scope?)
Post by: Paul Squires on September 01, 2008, 09:19:35 PM
Thanks José !  I would have stared at that code for hours and hours and not have realized to add INSTANCE.

Now I can continue on where I left off.

Thanks,
Title: Re: PB Objects (global scope?)
Post by: Paul Squires on September 01, 2008, 11:41:03 PM
After struggling most of today, but thanks to the help of José I am finally very comfortable working with PB objects in an OOP setting. Personally, I will not be retro-fitting any of my older or existing programs with PB objects because I think it would be cleaner to start off a project totally object based. It is really a different mind set.

I have learned several lessons today:

(1) Allow objects to handle objects rather than dealing with OBJPTR's and POKE.
(2) Some PB functions do not work on PB objects. For example, SIZEOF and pointers.
(3) Arrays in an object can be resized via REDIM PRESERVE but the ARRAY INSERT/DELETE functions do not work. You need to DIM/AT over the array. For example:


         Dim nArray(UBound(m_pChildNodes)) As Dword At VarPtr(m_pChildNodes(0))
         Array Insert nArray(nPosition)


(4) I need to plug into José's brain.

:)
Title: Re: PB Objects (global scope?)
Post by: Dominic Mitchell on September 02, 2008, 02:33:36 AM
Well, I have to admit I don't agree with your approach one bit.
In COM, the hierarchy I would use for a linked-list would look something like the following:

   Server
     |
    \|/
INodes(collection object)
     |
    \|/
   INode
   
                                                                 

INodes inherits IDispatch and implements an IEnumXXX object. It also has the standard methods
Count, Item, NewEnum, Add, Remove and Clear.   
Relatively easy to implement using low-level COM, and can be used from any development environment
when incorporated in an OCX.
         
By the way, I see no reason for a node to reference count another node.
Title: Re: PB Objects (global scope?)
Post by: Paul Squires on September 02, 2008, 03:01:43 AM
So Dominic, if I understand you correctly, I should be using a COM collection instead of manually trying to track the objects via an array?

BTW, I am no longer using the linked list approach. I am using standard arrays in my object to store any child objects of that parent object. I will forego using those arrays if you think implementing the Collection approach is better. Please advise.

Thanks!
Title: Re: PB Objects (global scope?)
Post by: Dominic Mitchell on September 02, 2008, 05:13:16 AM
I guess it depends on the intended users.  If it is only meant for the PowerBASIC
audience, then I guess it is okay. As an OCX released to the wider development community,
I don't see how this approach is going to fly.
Take a look at how objects are organized in apps such as Excel, Word and Rainer Morgen's
RMChart. That's no accident.

We are dealing with two separate things here. The first is the objects exposed by the server,
and the second is the manipulation of the data by these objects.  The method use to process
the data is immaterial.  Use the method you think is best(array or linked-list).
Can this hierarchy be built using PB9 COM?  I don't know, because I don't have PB9 yet.

I will order PB9 this week, because I am very curious to see the calibre of the type libraries it
produces.  This type of simple problem should be a good test case.
Title: Re: PB Objects (global scope?)
Post by: Paul Squires on September 02, 2008, 01:09:26 PM
Thanks Dominic, I appreciate the info.

To be honest, I am only learning PB Objects and getting to know some of the workings of COM. The last time I did anything with Collections was back in my Visual Basic days about 8 years ago.

At this point, I am only tracking the child objects for my own use. It is not something that will be exposed as a library for others to use. If it was, then I definitely see your point that it should be standard like other libraries out there. My use of objects at this point is in preparation for my next project (an accounting/billing/invoicing program).

Good luck when you get PB9. I am enjoying it so far and as soon as I practice more I will get more comfortable with it.

Now get off that bike of yours and order PB9  :)   Actually, if your weather has been as crappy as ours, I doubt you've been biking much at all.