Working with the CMLabs CoreLibrary
In this document you will find the following sections
+ Programming with the CoreLibrary
+
Working with DLLs and Shared
Objects
+ Creating third party (your own) CoreLibrary objects
To start using the CoreLibrary you will need to do three things:
How to do this will depend on your operating system and development environment. In most Unix based systems you will do this by editing the Makefile and in Microsoft Visual Studio for Windows you have to edit the project properties. Please see the documentation of your environment for including 1 and 2 above.
For point number 3 above you simply need to include the relevant header files into your own source code files. For example, if you wish to use the Dictionary or ObjectCollection classes you will need to include a line at the top saying
#include "Dictionaries.h"
and if you wish to use JStrings and JTime you say
#include "JString.h"
#include "JTime.h"
After this you can now use any object in the CoreLibrary anywhere in your code.
The following will provide examples of how to work with some of the more common objects. Remember that you can look at the full Doxygen documentation of all the classes and source files as well.
A JString is most often referenced by value and is easily declares such as
JString str = "mystring";
Almost every function taking a JString as a parameter expects a value, not a pointer, but some do pass a reference.
The JString class has a very large zoo of functionality commonly associated with Strings, such as searching for substrings, splitting and merging, replacing, you name it. It really is easier to just look at the JString Doxygen documentation, but here are a few examples.
JString text = "My test string";
Collection cols = text.split(" ");
The Collection cols now contains three entries, namely ‘My’, ‘test’ and ‘string’ – see later how to work with Collections. The same can be accomplished with
cols = text.splitOnWhiteSpaces();
and more splitting routines exist, such as
Dictionary dict = text.splitCommandLine();
You can extend the String with
text += " my extra text";
and you can extract substrings with
JString text2 = text.substring(2, 8);
You can also replace text inside the String with
text.replace("my", "your");
and most functions have case insensitive variations, such as
text.replaceIgnoreCase("my", "your");
Lastly, you can create JStrings much like printf statements with
int i = 4;
text = JString::format("Hello %d World\n", i);
Please have a look at the extensive list of methods in the Doxygen documentation.
There are several types of Collections, some completely text-based such as Collection and Dictionary, others object-based such as ObjectCollection and ObjectDictionary and even time-based such as TimeSeries.
The Collection is a simple collection of JStrings, which you use like this
Collection col;
col.add("Test 1");
col.add("Test 2");
JString text1 = col.get(0);
JString text2 = col.get(1);
You can ask how many extries any Collection has with
int c = col.getCount();
and use this in loops such as
JString text;
for (int n=0; n<col.getCount(); n++) {
text = col.get(n);
if (text.length() > 0) {
...
}
}
A Dictionary work similarly, but here you have named entries
Dictionary dict;
dict.put("My Entry 1", "Test 1");
dict.put("My Entry 2", "Test 2");
JString text1 = dict.get(0); // or
text1 = dict.get("My Entry 1");
JString text2 = dict.get(1); // or
text2 = dict.get("My Entry 2");
and in loops you do
JString key, value;
for (int n=0; n<dict.getCount(); n++) {
key = dict.getKey(n);
value = dict.get(n);
}
When using ObjectCollections and ObjectDictionary, the entries are pointers to Objects, which form the base for every single object in the CoreLibrary including JStrings. This means that you can add any object to a Collection, for example
ObjectDictionary dict;
dict.put("My Entry 1", new JTime());
dict.put("My Entry 2", new JTime());
JTime* t1 = dict.get(0); // or
t1 = dict.get("My Entry 1");
JTime* t2 = dict.get(1); // or
t2 = dict.get("My Entry 2");
JString key;
Object* value;
JTime* t;
for (int n=0; n<dict.getCount(); n++) {
key = dict.getKey(n);
value = dict.get(n); // or
t = (JTime*) dict.get(n);
if (t != NULL) {
...
}
}
This includes other collections, which means that you can build a hierarchy of collections of collections of ...
All collections can now sort, either by key or by value (as of CoreLibrary version 1.0). All four collection types mentioned above (Collection, Dictionary, ObjectCollection, ObjectDictionary) do not sort by default, and they will keep the order in which you added entries. You can resort them at any time and any number of times by using
bool
sortKeys();
bool
sortValues();
bool
sortKeysReverse();
bool
sortValuesReverse();
If you want to sort your entries automatically you should use one of the sorting versions of the four collections:
SortedCollection sorts by string value
SortedObjectCollection sorts by object value
SortedDictionary sorts by string key
SortedObjectDictionary sorts by object key
If you wish to change an existing non-sorting collection to a sorting collection you can set
myCollection.sorting = { SORTNONE | SORTBYKEY | SORTBYVALUE | SORTREVERSE };
Lastly, you may wish to change the way you retrieve from collections (both sorted and unsorted). For example, if you have a Dictionary with 20000 entries and you know that you normally look for entries near the bottom of the list rather than the top, you can set
myCollection.sorting |= FINDREVERSE
so every get(N) or get(String) will start looking from the bottom up rather than top down.
When you have an Object you can convert it to XML by calling the method
JTime t;
JString xml = t.toXML();
and later instantiate another object by
JTime t2(xml); // or
t2.fromXML(xml);
Usually, when you receive XML either across the network or from a file, you would normally parse the XML into a tree and work with the nodes directly. To parse XML from either a file or a string in memory you would do
XMLParser* parser
= new XMLParser();
parser->parseURL(filename);
// or
parser->parseXML(xml);
and when you were done traversing the tree you would
delete(parser);
The methods parseXML() and parseURL() return true if the parsing went well or false if an error occurred. In the case of errors you can ask for more information by
JString desc
= parser->verifyXML(xml);
which will tell you exactly which line and which tag it had a problem with including more information on what the problem was.
If the parsing went well you would obtain the root node in the tree with
XMLNode* rootnode = parser->getRootNode();
Every time one obtains a pointer to a node one should check whether that is NULL as a precaution. This includes every example below.
Each XMLNode has a tag (the name), zero or more parameters (xxx=”yyy”) and zero or more child nodes. The tag name can be found by
JString tagname = rootnode->getTag();
and the non-XML text content of the node can be obtained with
JString content = rootnode->getText();
All content XML will be present in the form of child nodes, but if one wished to force the issue one can get the full text content even if it is XML by
JString content = rootnode->getTextContent();
If you know that a node has a child tag called ‘mytag’ you can get it with
XMLNode* node = rootnode->getChildNode("mytag");
and if you just with to get all of the nodes you ask for the collection
//! Get the pointer to the children - don't delete it - and check for NULL!
ObjectCollection* children = rootnode->getChildTags();
which you can then iterate through
XMLNode* node;
for (int n=0; n<children->getCount(); n++) {
node = (XMLNode*) children->get(n);
if (node != NULL) {
if (node->getTag().equalsIgnoreCase("mytag")) {
...
}
}
}
To check if a tag has a parameter called myparm=”” you can do
JString parm;
if (node->hasAttribute("myparam")) {
parm = node->getAttribute("myparam");
...
}
and if you want them all in a dictionary
Dictionary params = node->getAttributes();
JString param, value;
for (int n=0; n<params.getCount(); n++) {
param = params.getKey(n);
value = params.get(n);
...
}
You don’t have to worry about deleting nodes or parameters at all as they are always pointers to nodes inside the parser, but remember not to delete your parser until you are done using all the nodes. This is especially important if you instantiate your parser with
XMLParser parser;
parser.parseXML(xml);
as it will be deleted when out of scope.
The JTime object always work with the highest possible precision of the operating system, which for Windows and Linux means 1 microsecond resolution. This means that you can do extremely accurate timings such as
JTime t1;
// Do something
JTime t2;
long dif = t2 - t1;
which will give you the millisecond difference between t2 and t1 or
dif = t2.microDifference(t1);
which will return the microsecond difference. Internally the second is always used for differencing, meaning that the first is merely the second / 1000.
There is a number of ways to print a time
//! Nice long date and time
JString print();
//! Only time part (hour, minute, second, ms)
JString printTime();
//! Only time part (hour, minute, second)
JString printTimeSec();
//! Only time part (hour, minute, second, ms)
JString printTimeMS();
//! Very short date print
JString printDateShort();
//! Sortable yyyymmdd
JString printDateSortable();
//! Nice long date
JString printDateLong();
//! Sortable date and time
JString printStamp();
//! HH:MM:SS.ms and days, months, years as needed
JString printDifTime();
The latter is used when instantiating a time from a difference, such as
JTime t3 = JTime(t2 - t1);
and will print a nicely formatted time which might be ‘2 ms’ or increasingly more information as needed up to the full format including years.
Lastly, when a JTime is created, it is always initialised to the time of creation. If you wish you can invalidate a JTime for later checking
JTime t1;
t1.setInvalid();
if (t1.isValid()) {
// it is not valid here
}
t1 = JTime();
if (t1.isValid()) {
// it is valid here
}
A Timer provides the ability to ask for a callback after a certain amount of time. To work with Timers one usually would create a TimerManager, which can create and manage any number of timers.
To initiate a timer callback one can use this TimerManager API
//! Create a String Timer without callback
JString createTimer(JString strData, long ms);
//! Create an Object Timer without callback
JString createTimer(Object* obj, long ms);
//! Create a String Timer with a function callback
JString createTimer(JString strData, long ms, void (*function)(JString id));
//! Create an Object Timer with a function callback
JString createTimer(Object* obj, long ms, void (*function)(Object* object));
//! Create a String Timer with a TimerReceiver callback
JString createTimer(JString strData, long ms, TimerReceiver* receiver);
//! Create an Object Timer with a TimerObjectReceiver callback
JString createTimer(Object* obj, long ms, TimerObjectReceiver* receiver);
Each of these functions give a String ID back which can be used to
//! Cancel Timer
bool cancel(JString id);
//! Wait for Timer
long waitFor(JString id);
//! How long left of Timer
long msLeft(JString id);
For the callback timers one has to provide a pointer to a function of the form
void (*function)(JString id));
void (*function)(Object* object));
or provide a pointer to a TimerReceiver or TimerObjectReceiver object.
The JFile object can work with files either with or without instantiation. If one is looking for a one-off file operation such as reading a whole file or writing a whole file, one can use the static JFile interfaces
static JString readAFileASCII(const JString& name);
static char* readAFileASCII(const JString& name, int &length);
static char* readAFileBinary(const JString& name, int &length);
static bool readAFileASCII(const JString& name, char* data, int &length);
static bool readAFileBinary(const JString& name, char* data, int &length);
static bool writeAFileASCII(const JString& name, const JString& text);
static bool writeAFileASCII(const JString& name, char* str, int length);
static bool writeAFileBinary(const JString& name, char* str, int length);
static bool appendToAFileASCII(const JString& name, const JString& text);
static bool appendToAFileASCII(const JString& name, char* str, int length);
static Collection* getFilesInADir(const JString& name);
static bool deleteAFile(const JString& name);
static bool deleteADir(const JString& name, bool recursive);
static unsigned long getAFileSize(const JString& name);
static bool createADir(const JString& fname);
static bool createADir(const JString& fname, bool recursive);
An example of the use of one of these could be reading a text file into a JString
JString str = JFile::readAFileASCII("mydir/myfile");
In all file operations one can use / or \ interchangeably both under Windows and UNIX, as they will automatically be replaced with the appropriate file separator.
To work more with one particular file or directory, one can instantiate a JFile object with a name and use the standard API to either create, read, write or delete a file or directory, or one can query if the file/dir exists (bool exists()) and whether it is a file or a dir (bool isADir(), bool isAFile())
A Queue is simply a first in, first out string or object queue, made safe for multithreading by semaphores and mutexes. One can add() a JString or an Object to a Queue and ObjectQueue, respectively, and one can
JString waitForNewEntryID(long ms);
bool waitForNewEntryToAppear(long ms);
or
Object* waitForNewEntry(long ms);
Object* waitForNewEntry(JString id, long ms);
Object* retrieveEntry(JString id);
Object* retrieveEntry(int pos);
Object* viewEntry(JString id);
Object* viewEntry(int pos);
and likewise for JString’s in the Queue.
A RequestQueue and ObjectRequestQueue are request managing objects, where one side can post a request
JString enterRequest(Object* req);
get a request ID back and be able to wait for the other side to answer the request
Object* waitForReply(JString id, long ms);
The other side can wait for incoming requests
bool waitForNewRequestToAppear(int ms);
JString waitForNewRequestID(int ms);
get the ID and the request itself
Object* getRequest(JString id);
and post the answer back with the ID
bool reply(JString id, Object* rep);
All of the above examples are equally valid for JStrings instead of Objects using the RequestQueue instead of the ObjectRequestQueue.
Any Object can inherit from JThread and by merely implementing the function
void run();
one can start, stop and interact with the thread using
bool start();
bool terminate();
bool suspend();
bool resume();
int getPriority();
bool setPriority(int pri);
One can ask
bool isRunning();
bool isThread();
and get statistics with
//! get stat on local thread
ThreadStat getLocalThreadStatistics();
//! reset stat on local thread
bool resetLocalThreadStatistics();
//! print stat on local thread
JString printLocalThreadStatistics();
//! get stat on own (calling) thread
ThreadStat getCallingThreadStatistics();
//! reset stat on own (calling) thread
bool resetCallingThreadStatistics();
//! print stat on own (calling) thread
JString printCallingThreadStatistics();
ThreadStat getCurrentCPUUsage();
ThreadStat getAverageCPUUsage();
Networking usually has three components, namely a Server, a Client and a Protocol for transmitting data. For TCP networking, the Server will listen on a port to which the Client will connect and start speaking the Protocol.
The Network object is a server implementation, which can be asked to listen to a port with
Network* network = new Network(receiver);
network->addProtocol(new NetMessageProtocol());
if (!network.init(1500))
return false;
network.start();
The receiver can be any object implementing the interface TCPReceiver, meaning they define the function
Message* netObjectReceive(Message* msg, NetworkConnection* con);
whereto any received Messages are delivered.
A NetworkConnection can be created to connect to the Network with
NetworkConnection* con = new NetworkConnection("myserver.net", 1500, new NetMessageProtocol(), NULL);
if (!con->isConnected())
return false;
Message* msg = new Message("from", "to", "Hello");
Message* returnMsg = con->sendReceiveObject(msg, 3000);
Any number of supported network protocols can be added to the Network server, including
NetMessageProtocol, NetHTTPProtocol and NetTelnetProtocol
and new ones can easily be created.
An advanced callback connection can be created by opening a second connection and
NetworkConnection* con = new NetworkConnection("myserver.net", 1500, new NetMessageProtocol(), NULL);
con->initializeAsReceiver(receiver);
if (!con->isConnected())
return false;
This way you tell the Network not to try to connect back to you in case it wants to send you unsolicited messages, but to use this second connection for such purposes. This is very useful when connecting through firewalls or routers to private networks.
The DynamicLoader can be used to load, manage and unload external libraries and their functions. If we have an external library called math.dll (Windows) or libmath.so (UNIX), one can load and unload the library with
bool loadLibrary(JString libname);
bool unloadLibrary(JString libname);
If the library contains the functions
int mathOne(Object* obj);
int mathTwo(Object* obj);
int mathThree(Object* obj);
one can load the functions using
DLLFunction getFunction(JString funcname);
and remember to cast it to the right format, such as
typedef int (* MyFunction)(Object* obj);
DynamicLoader loader;
loader.loadLibrary("math");
MyFunction func = (MyFunction*) loader.getFunction("mathOne");
One could also directly specify the library name in the function name
DynamicLoader loader;
MyFunction func = (MyFunction*) loader.getFunction("math::mathOne");
One can ask
bool didErrorHappen();
JString getLastErrorMessage();
CVML is part of the CAVIAR project and describes content in video sequences. An example of CVML is use can be found here.
To use CVML one needs to include the CVML header
#include "thirdparty/CVML.h"
The XML can be loaded from a file
JString xml = JFile::readAFileASCII(filename);
if (xml.length() == 0) {
printf("Could not read sequence file: %s\n", (char*) filename);
return false;
}
and parsed into a CVMLDataSet object
XMLParser parser;
if (!parser.parseXML(xml)) {
printf("Error parsing sequence file: %s\n%s\n", (char*) filename, (char*) parser.verifyXML(xml));
return false;
}
CVMLDataSet* newDataSet = new CVMLDataSet();
if (!newDataSet->fromXML(parser.getRootNode())) {
printf("Could not understand sequence file: %s\n", (char*) filename);
return false;
}
Each CVMLDataSet contains a number of CVMLFrames, which contain a number of CVMLObjects and CVMLGroups. A more detailed description of these classes can be found here.
All CoreLibrary objects share a vast set of basic functionalities, and if you wish to create your own objects it would be to your advantage to create them as part of the CoreLibrary using the Third Party Extension mechanism. The most important benefit from this is that all CoreLibrary objects are automatically converted from and to XML when sent across the network. An example could be:
Sender:
ObjectCollection* col
= new ObjectCollection();
col->add(new JTime());
col->add(new JString("Hello"));
col->add(new Bitmap(255, 122));
Message* msg = new Message("Me", "You",
"Hello World", col);
msg = sendReceiveMsgTo("You",
msg);
if ( (msg != NULL) && (msg->type.equals("Hello Yourself"))
) {
...
}
Receiver:
Message* msg = connection->receiveObject(msTimeout);
if ( (msg != NULL) && (msg->type.equals("Hello World"))
) {
ObjectCollection*
col = (ObjectCollection*) msg->getObject();
if (col
!= NULL) {
// col
now contains three objects: JTime, JString and Bitmap
// all re-instantiated automatically
}
}
You can extend the CoreLibrary with your own objects by putting them into the Third Party directories. There is one directory for the source files (/thirdparty) and one for the header files (/include/thirdparty).
This is how you create a new CoreLibrary object called MyObject in 9 easy steps:
1. Create your header file MyObject.h and place it in /include/thirdparty
2. MyObject.h should at least
#include "Object.h"
or any other CoreLibrary header
3. Your class should inherit from
class MyObject
: public Object {}
4. and has to implement this method:
Object* clone() const;
5. and to work with XML these methods
JString toXML();
bool
fromXML(XMLNode* node);
6. Then create your source file MyObject.cpp and place it in /thirdparty
7. Now add this line to the file /thirdparty/AutoXML.cheader
#include "thirdparty/MyObject.h"
8. and add this to the file /thirdparty/AutoXML.code
else if (tagName.equalsIgnoreCase("MyObject")) {
obj =
(Object*) new MyObject();
obj->fromXML(rootNode);
}
9. Recompile the CoreLibrary
There are a few notes to this procedure
else if (tagName.equalsIgnoreCase("MyObject")) {
obj =
(Object*) new MyObject(rootNode);
}
+ The clone() method should return a new instance of your object with exactly the same attributes and values. You should not, however, pass through pointers to objects, but rather make sure that this uses deep copying, so no memory is shared between the new and the old objects.
+ There are a number of additional methods which you can overwrite
1.
bool equals(const
Object* o2) const;
2.
int compare(const
Object* o2) const;
3.
JString print();
4.
JString toHTML();
1 is used to see if two objects are equal and should return true or false. If you do not overwrite this method the default way is to see if the objects have the same position in memory and are in fact the same instance.
2 is used when sorting objects, where you can return 0 for equal, 1 if this object is larger than the o2 and -1 if o2 is bigger. All collections support sorting, both by keys and values.
3 and 4 are used to create a custom String or HTML version of the object, for onscreen or in browser use. The default is to print the class name (“MyObject”);