Scripting Scope


Davarius

Recommended Posts

After reading in the general posts I saw a post where it was mentioned that classes can be created in DAQ. Upon further investigation I found an instance of this in one of the sample programs which contained a class of variables, but also a short copy function inside of the class definition.

Code Snippet from sample:

class chansettings
	local string name
	local modfunc
	local string regtype
	local tagnum
	local logme
	local sigfigs
	local slope
	local intercept
	local input // flag, calced by UpdateChannels.  0 if output, 1 if input

   function CopyFrom(obfrom)
	  name = obfrom.name
	  modfunc = obfrom.modfunc
	  regtype = obfrom.regtype
	  tagnum = obfrom.tagnum
	  logme = obfrom.logme
	  sigfigs = obfrom.sigfigs
	  slope = obfrom.slope
	  intercept = obfrom.intercept
	  input = obfrom.input
   endfunction
endclass

The insight gained in just this one snippet has forged a bridge between DAQ scripting and the Coding I would typically do in Standard C or C++. I know DAQ has a "C" basis but no syntactical references except for the basic tools available from the manual (which I span through daily). I had a feeling that there was more that i could do and this proves it.

Questions-

The code Snippet:

If you were to define other functions inside a class do they need to be short or can they contain for loops etc.

General:

What is the scope of daq factory in reference to "standard programming"?

There is the ability to create external dll's to do certain things but what else is available internally to daq? Can you declare pointers?

An extended/advanced list of syntactical references would be very useful. The key things to optimizing any code or program are the tools you have available and how you use them.

Before I post I always check through the form and examples to see if I can find a solution, However in some cases its better to just have the answer rather than having to search for it, in others it helps the learning process. I find that sometimes scripting in DAQ is like doing an internet search without knowing the "keywords" to find what you want to search for (although the program itself is easy to use and the manual/forum responses are a huge help).

Link to comment
Share on other sites

I think that much programming reference has come down to internet searches. I program in a number of different languages and can never remember the exact syntax of all the functions I use so am constantly doing a web search. Its much easier than trying to fit a stack of books on my desk. This is a little why we kept the user manual short (as if 440 pages is short). Most people really want an answer to a specific question or application and its simply impossible (or would be 4400 pages), with the reach of DAQFactory, to write a manual to cover it all. This forum provides an excellent way to ask and share these questions and for us to answer them as they come.

I should mention that there are a number of DAQFactory features that are plain out not documented. OOP in DAQFactory is one. The reason is that we don't consider it complete. The biggest issue is that you can step through a member function using the debugger. Also, line numbers for errors are listed from the first line of the member function, not the first line of the sequence, so you have to manually count.

That said, classes are used in a number of applications that we've done for customers and if you get buy those two limitations, work really well. So, now to your specific questions:

1) DAQFactory is actually not C based, but rather just uses some similar syntactical features. For example, if statements are formatted as if(), but unlike C, we use endif instead of {} to define the block. In fact, one could argue that its java based, or javascript based since all three really have similar syntax.

2) Functions inside a class can be as long winded as you want them and have loops. In fact, you can use the StartLocalThread() function to even fire off your own threads dynamically within an object.

3) scope: global variables are just that, global in scope. So are channels and sequences. Other internal variables and functions are usually put under their own namespace, for example, the file or db functions. private variables are private to a function or sequence, whether that function is in a class or not. Local variables are the same as public member variables of a class. There are no private or protected member variables. You can access them from within the class by simply referencing them by name, or outside the class by referencing the object, a dot, then the variable. Since components, user protocols and user devices are essentially objects, these can all have local variables as well.

4) "There is the ability to create external dll's to do certain things but what else is available internally to daq?" I'm not sure what things you want to do, so perhaps just ask as things come up. If it seems like something is hard to do in DAQFactory, just post it on the web and we'll tell you if there is an easier way.

5) You can't declare pointers, however, DAQFactory objects are handled pretty much identically to the way javascript does. It was sheer coincidence that this happened, but I suppose there are enough languages out there that we'd accidently overlap with at least one of them.

Anyhow, when you instantiate the object with new(): new(chansettings), you are returned a reference to the object. Typically you'll want to store that in a variable, so x = new(chansettings). You access the members of the object through that variable: x.copyfrom(y). If you assign that variable to another: y = x, you are not copying the object, but rather copying the reference, so x.somevar and y.somevar actually point to the same variable. This means, then, that if you pass x to a function, you are actually passing a reference to the object you instantiated, so really a pointer to that object. So, technically, if you want to have a variable (i.e. scalar or array) and pass it as by reference (as a pointer), you can create a class that has a single member variable, create an instance of it and pass it instead of the variable itself.

DAQFactory handles all the garbage collection for you and deletes your instantiated object when all the variables holding references to it either go out of scope, or are assigned other values. So:

x = new(chansettings)

x = 3

will create a chansettings instance and then in the next line destroy it because x got assigned to something else and there are no other references to it.

This is why there is that copyfrom() function. You can't do y = x to actually make a copy of an object instance. Instead you need to manually create a new instance and then copy over all the member variables.

That's a good intro. I've attached the docs we wrote but never released for OOP in DAQFactory. I'm posting it with the understanding by everyone that uses it that this is not an officially released feature, so if you find a quirk, please report it, but we may not fix it until the next big release. Anyhow, here it is:

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

  • 4 years later...

I spent time coding different approaches to my control problems. Now I'm trying to make functional code and running into basic issues. Please get me pointed in the right direction. In an attempt to resolve these things for myself I tried creating some simple test sequences, but I'm not able to make them work.

 

First created the following sequence called Testing

 

class GlobalClass
  
   function Mult2 (value)
      ? value * 2
   endfunction
   
   function Mult10 (value)
      ? value * 10
   endfunction
   
endclass

global f = new(GlobalClass)

 

I start the sequence and in the command window try the following:

? f

Object(s)

f.Mult2(50)

C1000 Channel or function not found: Line 1

 

So then I came back here to the forum and tried entering some of your code from above in a new sequence called Test2

 

class Person
  local string name
  local string address
  local string zip

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

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

 

If I enter ? People on the command line, it returns Objects()

 

if I enter People[0].PrintMe(), it returns

{"Frank Smith","George Johnson"}
{"123 Main St","123 Charles St"}

{"12345","54321"}

 

In short, the functions don't seem to be functioning. What am I doing wrong?

Link to comment
Share on other sites

As I've mentioned, OOP is undocumented because of two issues, one is how it reports errors, the other is that you can't have [] and () in the same symbol specification, only the last one is used.  So, your statement:

 

People[0].PrintMe()

 

gets reduced to:

 

people.PrintMe()

 

which calls that function on each element in the array.

 

You need create a reference to the first element first:

 

private temp = people[0]

temp.printme()

Link to comment
Share on other sites

  • 1 month later...

Does DAQFactory scripting language include any functions to identify the class of an object or the parent or properties like GetClass(), or GetParentClass() or GetProperties() or PropertyExist() or ClassExist()? I'm looking at an instance where GetClass() might be useful and thought there might times when some related functions would also be useful.

Link to comment
Share on other sites

No.  Though you could of course create your own.  Note also that DAQFactory is not strictly typed, and that applies to objects too.  So, if you have an array of objects that do not have the same parent, but have the same name for a function or member variable, you can cycle through the array and reference it by that name and it'll work fine without having to know the object type.

 

This is different from C++ which is strictly type checked, but the same as JavaScript.

Link to comment
Share on other sites

I believe most of the functions I asked about are available in JavaScript. I was hoping maybe there were some functions like these in DAQFactory.

 

Here is an example of why I'm asking and something I think could be very useful in DAQFactory. If you had an object built as follows:

 

class MyRecipeClass

  local string Name

  local string Batch

  local Water

  local Coloring

  local Hardner

  local MixTime

  local HeatTime

end class

 

And you want to create an input form to have an operator enter the parameters for each of those properties. It would be quite handy to be able to reference the property names and use them as headers for the input fields instead of having to manually reenter them.

 

In JavaScript I believe there is a function Object.getOwnPropertyNames(). Is there a way to access property names in DAQFactory?

 

If in DAQFactory you could dynamically create an array of input fields with a function like this you could point a number of different classes at the page you are using to collect the input data. You could get what you wanted without having to hand construct each page individually, and if there were changes in the properties needed, just change the class definition and you would be done.

 

Your input page for the above would look something like this

 

Product Formation

Name [                                     ]

Batch [                                     ]

Water [          ]

.... and so on

 

With maybe a button at the bottom labeled "Start Product Formulatioin" to start the control sequence based on input values entered.

 

The input field headers in this case are the property names from the class definition.

Link to comment
Share on other sites

There is no function for getting a list of member variables.  I would just create a common member function that returns that list. A little more upkeep, but more flexible.  It's what you'd have to do in C++.

 

The problem with getOwnPropertyNames() is that it returns functions too, since functions are technically just a data type in Javascript.   

Link to comment
Share on other sites

Archived

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