Class OnCreate() problem?


Indy500

Recommended Posts

I took this out of your OOP document:

class Person

   local string name
   local string address
   local string zip

   function PrintMe()
	  ? name
	  ? address
	  ? zip
   endfunction

   function OnCreate()
	  name = "Not Entered"
	  address = "Not Entered"
	  zip = "Not Entered"
   endfunction

   function OnDestroy()
	  ? name + " has been destroyed!"
   endfunction

endclass

and I run it with:

System.SequencePrint = 1

private People = new(Person)

People.PrintMe()

People.name = "David"
People.address = "Indy"
People.zip = "46278"

People.PrintMe()

System.SequencePrint = 0

But the output is three blank lines and then:

David

Indy

46278

Why isn't the OnCreate() working?

Also, is there an update to the OOP document?

Thanks!

Link to comment
Share on other sites

This is the reason OOP docs aren't in the full DF docs. There are a few small quirks and the examples aren't 100% accurate. In this case, the issue is that OnCreate() is called before the three local variables are instantiated, so they don't exist to assign values to. To initialize local variables, do it in the declaration:

local string name = "Not entered'

Link to comment
Share on other sites

Can you tell me where to find the documentation on OOP Please?

I appreciate it's not part of the "Official" documentation, but I can't find any documentation anywhere on it.

Like Indy500 I have lots of programming experience and would like to know what is available in DF and make use of it - but I have only the very few snippets scattered through various forum posts. Makes it kindof difficult to know where to start.

Rod.

Link to comment
Share on other sites

Its somewhere in this forum, but like you, I can't find it, so here it is again:

Object Oriented Programming

DAQFactory supports a reasonable implementation of object oriented programming. You can create objects containing member variables and functions, derive them from other objects, and of course instantiate them. So in OOP terms, it supports inheritance, and polymorphism, encourages modularity, but does not have strong encapsulation features as members are all publicly viewable.

Classes as structures:

At the basic level, an object is just a structure, meaning a collection of member variables. This is useful for storing groups of data. For example, you could create an object to store a person's name and address. But first you have to define the class:

class Person
  local string name
  local string address
  local string zip
endclass

A class on its own does nothing but define the structure. To be able to use it you must instantiate the class. An instantiated class definition is called an object, and its stored in a variable just like any number. So, to create a variable to store a person's name and address after creating the above class definition you might do:

global Frank = new(Person)

The new() function instantiates the given class and returns an instance of it. In this case we are storing the instance in the variable Frank. Now we can set Frank's name and address member variables:

Frank.name = "Frank Smith"
Frank.address = "123 Main St"
Frank.zip = "12345"

Notice how we use the dot notation similar to many of the system variables and functions to access the member variables of our object. Now lets say we want to keep Frank's information, but add information for George. First, we'll move our Frank object to a new, more generic, variable name:

global People = Frank

This sets the variable People to our already instantiated object. This does not make a copy of Frank, but simply creates another reference to the same object. So if you did:

People.name = "Frank Jones"
? Frank.name

you would get "Frank Jones". Changing People.name also changes Frank.name because the refer to the same object. Now that People refers to our object, we can set Frank equal to something else, say Frank = 0, to remove this reference. If we remove all references to our object, DAQFactory will automatically delete the object. But now we are going to add another Person object to our list of People:

People[1] = new(Person)
People[1].name = "George Johnson"
People[1].address = "123 Charles St"
People[1].zip = "54321"

Here we created a new instance of the Person class and put it in the second array element of Person. The first array element, [0], still contains the object with Frank's information. We then set all the member variables for our new object. You can see from this example, then, how to create array's of objects and access their members. Note that Person doesn't have to have the same object, but could containing different class types. This could then be used to implement polymorphism, though remember that DAQFactory doesn't have any strict type casting and so if you want to implement polymorphism you'll need to make sure that all the objects have the same base parent class, or at least the same member names.

Now, technically, the member variables of Person are actually arrays, so we could have implemented this as arrays within a single object:

People = new(Person)
People.name = {"Frank Smith","George Johnson"}
People.address = {"123 Main St","123 Charles St"}
People.zip = {"12345","54321"}

Referencing these internal array elements is simply a matter of placement of the subsetting:

? People.name[1] will display "George Johnson"

The real flexibility comes when you do both. So, you could have one instance of the class Person that contains all the people that live on Main or Charles street and store that in People[0], and then a second instance of all the people that live on York Rd and store that in People[1], etc. If you really wanted to get complex, you can of course store object references in member variables of other objects!

Inheritance:

You can create what are called derived classes that inherit the features of the parent class. So, if you have the class Person like we created above, and want to create a new class that has all the features of Person, but also includes phone numbers you could do:

class PersonPhone parent Person
  local string phone
endclass

Now, if you create an object of type PersonPhone, your object will have the member variables name, address, zip and phone. This becomes much more powerful when we start adding member functions.

Initialization of member variables:

You can create default values for your member variables two ways. You can use the OnCreate() member function that we'll talk about later, or you can simply initialize them in the class definition:

class PersonPhone parent Person
  local string phone = "Not Entered"
endclass

Member functions:

The real power of object oriented programming comes out when you start to add member functions to your objects. This takes a structure and makes it into an object that can actually do something other than just store data. Lets continue with our person example and assume we are only going to store one person for each Person derived object we create. In other words, the member variables will not be arrays themselves. Now lets make it so our object actually does something with this information. Lets have it print its contents:

class Person
  local string name
  local string address
  local string zip

  function PrintMe()
	? name
	? address
	? zip
  endfunction 
endclass

Now lets say we created two instances of this object like we did above and put those instances into People[0] and People[1]. If we did:

People[0].PrintMe()

we would see in the Command / Alert:

Frank Smith

123 Main St

12345

and if we did:

People[1].PrintMe()

we would see:

George Jones

123 Charles St

54321

This is a pretty basic example, but hopefully you can see how this works. The functions are just like sequence functions and can take parameters and return values if desired. You can, of course, have multiple member functions in your class, just bracket each with function / endfunction. If your class derives from another class, you can call the functions of the parent class just like they were functions of your class. If your class has a member function with the same name as one the parent class' member functions, your class is said to have overridden the parent class function and only your function is called. This is really where the power of polymorphism comes in:

class Person
  local string name
  local string address
  local string zip

  function PrintMe()
	? name
	? address
	? zip
  endfunction 
endclass

class PersonPhone parent Person
  local string phone

  function PrintMe()
	? name
	? address
	? zip
	? phone
  endfunction 
endclass

People[0] = new(Person)
People[0].name = "Frank Smith"
People[0].address = "123 Main St"
People[0].zip = "12345"

People[1] = new(PersonPhone)
People[1].name = "George Johnson"
People[1].address = "123 Charles St"
People[1].zip = "54321"
People[1].phone = "555-1212"

for (private.x = 0, x < numrows(People), x++)
 People[x].PrintMe()
endfor

The above code will print this to the command/alert window:

FrankSmith

123 Main St

12345

George Johnson

123 Charles St

54321

555-1212

You can see that the PersonPhone class overrode the PrintMe() function of the parent Person class and printed the phone number as well, but we didn't have to specify which version to use inside the for loop.

Create / Destroy:

There are also two built in functions you can use to initialize and de-initialize your object. If you have a member function called OnCreate(), it will be called when the object is first instantiated (i.e. when new() is called with your class). If you have a member function called OnDestroy(), it will be called when the last reference to your object is lost and your object is deleted. So, you might do:

class Person
  local string name
  local string address
  local string zip

  function PrintMe()
	? name
	? address
	? zip
  endfunction 

  function OnCreate()
	name = "Not Entered"
	address = "Not Entered"
	zip = "Not Entered"
  endfunction

  function OnDestroy()
	? name + " has been destroyed!"
  endfunction
endclass

If your class is derived from other classes, the OnCreate() functions of all the parents are called in order from the top level parent to the current class. Likewise with OnDestroy(), but in reverse, so the current class OnDestroy() is called first, then its parent, then the parent's parent, etc.

Some random DAQFactory OOP notes:

* Object references must be stored in numeric variables, not string variables.

* Class definitions are created at compile time. That said, the simplest way to ensure that the sequence is compiled and therefore the class defined is to actually run the code. Running class definition code won't do anything, and really should affect performance either.

* You cannot combine scalars and object references in the same array. If People = 0 and you do People[1] = new(Person) then the entire People array will be assumed to be object references. Since People[0] is still 0, which is certainly an invalid reference, you will get errors trying to access People[0]. Of course you can then assign People[0] to an object. Likewise the other way. If People[0] = new(Person) and you do People[1] = 0, you will lose the object reference in People[0].

* When an object reference is passed to a function, that reference is copied, just like ref1 = ref2, and so it basically appears that the object is passed by reference. This is the opposite of regular variables which are always passed by value. In fact, if you need to pass a regular variable by reference, create a basic class with one member variable, instantiate it and pass that to the function.

* Object references are technically just a number, however, you cannot perform any math function on them. In fact, the only functions you can perform on them are == and !=, and of course the various functions that don't affect the value itself, such as InsertTime, ShiftTime, Point, etc. Doing ref1 == ref2 will return 1 if both ref1 and ref2 point to the same object. Of course member variables of objects are like any other variable.

* Like the rest of DAQFactory, you can change class definitions on the fly. However, once a class has been instantiated into an object, any changes to the local variable declarations will not affect the instantiated object. You will have to reinstantiate the object to pickup the changes. However, as we'll see later, changes to member functions of a class will affect all instantiated objects immediately without having to recreate the objects. "Immediately" is not quite completely accurate though. If an object is currently executing a member function and you change the class definition for that function while it is executing that function, the update will not take effect until the function has ended. This is similar to changes in sequence code. The reason changes in local variables require reinstantiation and member function changes don't is that member variables are created when the class is instantiated using the pattern of the class definition, but are stored in the object. Member functions are only stored in the class definition and a copy only made when the function is called. That said, one way to add a local variable without having to reinstantiate all your objects is to declare it inside a member function instead of in the general class and then call that new function.

* Although many of the built in DAQFactory functions appear to be in objects, such as File., DB., Email., Component., etc., you cannot derive from these objects. You can only derive from your own objects.

* Objects are referenced counted, so remain in memory until the last reference is lost. A reference is lost either by going out of scope (a private variable), setting the reference to another reference or scalar, doing ClearGlobals(), or of course quitting DAQFactory, starting a new document, or opening another document. A reference is created by new(), setting a reference equal to another, i.e. ref1 = ref2, or by passing the reference to a function.

* Because objects are deleted at DAQFactory's convenience, OnDestroy() may not be called immediately after the last reference to the object is lost. Depending on processor load, it may take a while, and even at low processor loads, it will most likely take 100-500ms.

* There currently is no way to call the parent member function of an overridden function. Also, you cannot call OnCreate() or OnDestory() yourself manually. These functions only get called when the object is created and destroyed. If you want to be able to call the creation or destruction code manually, create a separate member function and have OnCreate() or OnDestroy() call that function as well.

* While you certainly can pass a reference into a function, returning an object reference is not possible if the object is created inside the function and stored as a private variable:

	  function newclass()
	   private nc = new(MyClass)
	   return(nc)
	  endfunction

This won't work because the private goes out of scope before the return() function finishes and so the new object becomes completely dereferenced and is automatically deleted by DAQFactory. You could get around this by using a global, but that would completely destroy modularity, one of the key reasons for creating a class in the first place.

* In order to copy an object, that is create a new object with the same local variables, you will need to manually transfer the locals from your existing object to the new object. We recommend creating a copy member function. For example, we could add this to our Person class:

	  function CopyTo(newclass)
		  newclass.name = name
		  newclass.address = address
		  newclass.zip = zip
	  endfunction

Then, if we wanted to make a copy, we'd do something like this:

	  NewCopy = new(Person)
	  People[1].CopyTo(NewCopy)

Remember, simply doing NewCopy = People[1] will simply make NewCopy contain another reference to the same object that People[1] refers to, which is not the same thing.

Link to comment
Share on other sites

And here are the docs for db.QueryToClass(), which, truthfully, is a much better way to query a database than using Query() and recordsets:

QueryToClass(dshandle, SQL String, [class instance]):

this function performs the given query on the dataset opened with Open() or OpenEx(). The SQL string should be a SELECT command. If a class instance is not provided, the function returns a custom object containing the complete query results. In the class member variables are created for each field name in the query results, plus two extra variables: FieldNames, which contains an array of strings listing all the fields of the query, and RecordCount, which contains the number of records returned from the query, which could be 0. So, for example, if you did:

global myset = QueryToClass(dshandle, "SELECT Name, Address FROM myTable")

then myset.FieldNames would equal {"Name","Address"}, myset.Name would contain an array containing the all the rows for the field Name, etc.

For more advanced users using classes, you can provide an existing class instance (object) instead of having QueryToClass() create one for you. This allows you to use classes with member functions that presumably could work on the fields queried. So:

myObject = new(MyClassType)
QueryToClass(dshandle, "SELECT Name, Address FROM myTable", myObject)

will create the same member variables (if necessary) as the other version of QueryToClass(), but will put them into myObject. Note that we provided the myObject instance and not the MyClassType class definition. The object must be instantiated already.

Link to comment
Share on other sites

  • 4 years later...

Is there a way to put an array of a defined class into another class definition, IOW

class MyClass1
   local var1
   local var2
   endclass

class MyClass2
   local instClass1[10] = new(MyClass1)
   local var3
   local var4
   endclass

such that you could reference

 

MyClass2Var.instClass1[3].var2

 

If not, I guess you could make the parent class an array itself, but that doesn't seem very OOPy.

Link to comment
Share on other sites

The problem is that this line:

local instClass1[10] = new(MyClass1)

Only puts the new class in the 11th element of the array. The other 10 elements get 0. The other problem is that the new() is executed when the class is declared. When it is instantiated, the object reference is copied, not the object.

You need to write a for() loop in onCreate() to instantiate all 11 array elements.

Link to comment
Share on other sites

  • 2 months later...

It is not clear to me why this produces an error. Replace the MYTYPE in the class definition and no error, but trying to use MYTYPE even after it was defined produces a compiler error.

 

define MYTYPE = 3

class xyzclass
   local ThisType = MYTYPE
endclass

Link to comment
Share on other sites

It works fine for me once MYType is defined.

 

However, you have to understand how the compiler works.  The define in DAQFactory is a statement like any other and only actual defines MYTYPE as 3 when the sequence is executed.  It is not a compiler directive like C++ #define.  Really, the only difference between define and global in DAQFactory is that define is readonly (though the value can be changed by doing define MYTYPE = 4 or some new value).  

 

The local variables in a class, however, if initialized like you did, get evaluated during compilation because DAQFactory stores the result of the expression in the class definition, not the expression itself.  But, since you haven't run the sequence yet because you can't compile it, you haven't defined MYTYPE and the expression can't be evaluated.  Here's an example showing this:

 

class xyzclass

   local theTime = systime()

endclass

 

global x = new(xyzclass)

delay(1)

global y = new(xyzclass)

? x.theTime == y.theTime

 

The result is 1 (true) because systime() is evaluated at compile time, not when the class is instantiated.  If you want to initialize a member variable at runtime, not compile time, you need to use onCreate.

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.