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)
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?
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.
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.
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).
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.
Your .AddRef has triggered something in my brain! I should have a working solution in a few minutes.
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?
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
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
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. :)
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...
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.....
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?
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.
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,
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.
:)
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.
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!
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.
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.