Home

RimOS Programming

Updated on Oct,19, 2001
The following notes are just for beginning concepts on RIMOS. For details, please refer to RIMOS doc.

1. How to create very simple Rim project using Visual C++
2. Basic notes on RIMOS database
3. Basic notes on RIMOS user interface
4. How to load your DLL into Rim devices

1. Create very simple Rim project using Visual C++

Rim applications using Dll format with some specific options. This often causes some compiler errors for beginners because these options are not set by default for Dll projects created by Visual C++. For the sake of simplicity, the following example will use Release mode for building the application.
Create a empty Win32 Dynamic-Link Library project called RimSamp, choose the build mode RimSamp - Win32 Release ( item Set Active Project Configuration under menu Build ). Then go to Project Settings under menu Project to set the followings:
-For compiling options:
  • CPU 80386
  • Struct Member Alignment 2 bytes
  • Uncheck all three checkboxes of Enable Exception Handling, Enable Run-Time Type Information and Disable construction Displacement
  • Additional include directories needs to point to the include and include\internal of the SDK. I use Blackberry SDK 2.1 installed in D:\RIM21, and have this string in this option : D:\RIM21\include;D:\RIM21\include\internal.
  • -For Linking options:

  • Object/Library modules will have the followings: RimOS.lib OsEntry.obj Database.lib ribbon.lib UI32.lib libc.lib ( we don't need database in this sample, but we will so it will not hurt if we put the Database.lib in advance). Remember to clear all default values here and put into the above modules.
  • Check on Ignore All Default Libraries
  • Additional Libray Path has the path to the SDK library. It is D:\RIM21\lib in my case
  • Now we go to the more interesting thing : the same program source.

    #include <Pager.h>
    #include <UI32.h>
    #include <ribbon.h>
    
    class SampleScreen:public Screen
    {
    public:
    	UIEngine * m_puiengine;
    	SampleScreen( UIEngine * puiengine );
    	int HandleMessage();
    	Title	m_title;
    	Edit	m_edit;
    };
    
    char VersionPtr[] = "Sample";
    int	 AppStackSize = 1024*4;
    
    void PagerMain(void)
    {
     	UIEngine *puiengine;
    	puiengine = new UIEngine();
    	puiengine->Initialize();
    	SampleScreen * pscreen = new SampleScreen( puiengine );
    	RimTaskYield();
    	RibbonRegisterApplication( "&Rim Sample", NULL, 0, 0 );
    	pscreen->HandleMessage();
    }
    SampleScreen::SampleScreen( UIEngine *puiengine )
    {
    	m_puiengine = puiengine;
    	m_title.SetText("This is title");
    	AddLabel( m_title );
    	m_edit.SetLabel("Program:");
    	m_edit.SetBuffer(22,22);
    	m_edit.Append("Please Type Here");
    	AddField( m_edit );
    }
    int SampleScreen::HandleMessage()
    {
    	m_puiengine->ProcessScreen(*this);
    	MESSAGE msg;
    	int result;
    	while(1)
    	{
    		RimGetMessage(&msg);
    		if( msg.Device == DEVICE_KEYPAD )
    		{
    			result = m_puiengine->HandleInput(msg); 
    			if ( result == UNHANDLED)               
    			{
    				if (msg.Event == KEY_DOWN && msg.SubMsg == KEY_ESCAPE ) 
    			               RibbonShowRibbon();
    			}
    		}
    	}
    }
    
    We will have a look on how this program works before going into details of the codes.
    In the above C++ project, create a new C++ file call RimSamp.cpp then paste the following code into it. Compile your project and load the created dll into your Rim. You will see an icon having the picture of the Rim with a question mark inside and named Rim Sample. Run that sample application you will se a screen that has two lines: This is the title, and Program: Please Type Here. Type in some characters until you see a message Field Full.

    Now we know how it works, now let's back to the source code
    The main entry to a C program is main, for Windows WinMain, and for RimOS PagerMain. This is where the Rim application will start.

    The first three lines is to initialize UIEngine. This object must be created and initialize in order to have the user interface screen. All Screen-derived classes will need this object. RimOS has a basic class Screen to help applications to deal with user interface. And this class will work along with UIEngine object. Our class SampleScreen is a Screen-derived class. Its instructor sets up two GUI objects : m_title ( of class Title )for the title of the screen, and m_edit ( of class Edit ) for an edit object. It is the later object that allows us to type in some characters.SetLabel is to set the lable of an Edit object. SetBuffer is to declare the buffer size. We declare here 22 characters. When we type in and the dialog Field Full appears, we know that we have already typed in all the reserved size.AddLabel is to add a title, and AddField is to add other GUI objects.

    In some cases when intializing objects, their constructor may require to initilize some other tasks. For example, in one of my application, the main screen will turn the radio system on. It is clearly that radio system is not inside my application, and it needs a chance to be started. That 's why we need to have RimTaskYield(). Calling to this api will give other tasks chance to run.

    Now RibbonRegisterApplication. We can see a list of icons and their names on the Rim screen. That's one of ribbon functions. Calling to RibbonRegisterApplication will let RimOS knows our application icon as well as its name, knows where to put the icon on the screen ... We can easily see that the first parameter will specified application name. The 2nd parameter, that is NULL in our application, is the pointer to the icon bitmap. Because of its null value, RimOS displays a default one that has the question mark. The 3rd parameter is the apllication data, we don't care about it right now, and it will be safe to set it to be zero. The last parameter is the position of the icon on the Rim screen. A lowerer value means the icon will be put in the left of applications that has higher value for this parameter. If more than one applications have the same value for this parm, which one is loaded first will be in the left of the others.

    Now we go to another interesting part: the message loop. If you have ever programmed for Windows, this is very similar. Events ( such as clicking the wheel, typing a key, receiving a radio packet . . . ) will be sent to application via messages. The application will keep checking incoming messages by calling RimGetMessage. If no message is available, the OS will block the application and yield control to other applications. Besides RimYieldTask, now we know that RimGetMessage also helps to yield cpu control.RimGetMessage can be called from anywhere in the application. Therefore handling RimGetMessage in each Screen-derived class is pretty useful in coding.

    RimGetMessage requires only one parameter: a pointer to an instance of type MESSAGE. The structure MESSAGE is defined as follows ( in Rim.h of the SDK ):

    /* Message Type */
    typedef struct {
        DWORD   Device;         // WHO CAUSED THE EVENT
        DWORD   Event;          // WHAT TYPE OF EVENT TOOK PLACE
        DWORD   SubMsg;         // ANY SUB MESSAGE (OPTIONAL)
    
        DWORD   Length;         // LENGTH OF DATA  (OPTIONAL)
        char   *DataPtr;        // POINTER TO DATA (OPTIONAL)
    
        DWORD   Data[2];        // DATA STORAGE (OPTIONAL)
    } MESSAGE;
    
    Member Device lets us know which device the event is for Event describes the event that causes this messages to be sent. All the other fields are optional, and depends on value of Device and Event.In our sample application, we are interested to when the user clicks on the Escape, that is for message having Device to be DEVICE_KEYPAD and Event to be KEY_DOWN.

    What HandleInput( MESSAGE msg ) ( a method of UIEngine ) is for ? It helps our application to carry out a lot of default actions. For example: the focus is on an edit field, and the user types in some normal characters, HandleInput will receive those characters and put them into that edit field. We don't need to do that action. The return value of HandleInput lets us know how the OS processes that message. In our above sample, Escape will not be processed in an Edit field, and HandleInput returns UNHANDLED. But when we type, say A, it will put an A into Edit field, and returns CONTINUED. If we don't call HandleInput, then we are able to click Escape to exit, but we can not type anything into edit field with the above code.

    2. Basic notes on RIMOS database

    2.1 RIMOS database with DatabaseRecord

    Databases on the RimOS are stored in flash memory. This is an important feature to remember because applications can access databases directly as RAM, but it can not write directly to databases, oe an error will generate.

    The first class we need to know of for database is the class Database. This class is defined in Database.h ins the SDK. For each database, we need to initialize a Database object, for example :

    static Database  dbunit( "UNIT");
    
    The first parameter is the name of the database. (Each database must have a name, haven't it ? )
    In fact there are five parameters for Database constructor, but right now, we can use the default values of the next last four parameters( See Note ).

    Database saves records in object of type DatabaseRecord, also defined in Database.h. Besides DatabaseRecord class, there is also FlashObject class to save records as well. The big difference between these two classes is DatabaseRecord object is used for variable-length records, while FlashOjbect for fix-length records. We will talk about FlashObject later.

    DatabaseRecord object needs to know which Database it will work with. It requires a Database object to construct DatabaseRecord. The following is a simple base class DbTable and a DbTable-derived class Unit to implement database on RimOS for the purpose of explaning the database APIs :

    /* declaration of classes and their implementation */
     1. class DbTable
     2. {
     3. public:
     4.	    Database *m_pdb;
     5. public:
     6.	    DbTable( Database &database ) { m_pdb = &database;};
     7.	    char * const GetFieldData( int recno, DbFieldTag fieldno );
     8.	    BOOL  UpdateFieldData( int recno, DbFieldTag fieldno , char * const pdata);
     9.	    int	  GetNumberOfRecords();
    10.	
    11.	~DbTable(){};
    12.};
    13. #define field_unt_pk      1       /* the first field of the table  */
    14. #define field_unt_wksfk   2       /* the second field of the table */
    15. #define field_unt_desc    3       /* the third field of the table  */
    16.
    17. class Unit: public DbTable
    18. {
    19. public:
    20.	    Unit( Database &database ): DbTable( database ) {};
    21.	    BOOL AddRecord( char * const unt_pk, char * const unt_wksfk, char * const unt_desc );
    22. };
    23. char * const DbTable::GetFieldData( int recno, DbFieldTag fieldno )
    24. {
    25.	if( recno >= m_pdb->get_num_records() ) 
    26.		return NULL;
    27.
    28.	DbRecordHandle rhandle = m_pdb->get_nth_record( recno );
    29.	if( rhandle == DB_NULL_HANDLE )
    30.		return NULL;
    	
    31.	DatabaseRecord rec( *m_pdb, rhandle );
    32.	DbFieldHandle fhandle = rec.get_first_field_handle( fieldno );
    33.	return ( char * const ) rec.get_field_data_ptr( fhandle );
    34.
    35. }
    36.
    37. BOOL  DbTable::UpdateFieldData( int recno, DbFieldTag fieldno , char * const pdata)
    38. {
    39.	if( recno >= m_pdb->get_num_records() ) 
    40.		return FALSE;
    41.	DbRecordHandle rhandle = m_pdb->get_nth_record( recno );
    42.	if( rhandle == DB_NULL_HANDLE )
    43.		return FALSE;
    44.	DatabaseRecord rec( *m_pdb, rhandle );
    45.	DbFieldHandle fhandle = rec.get_first_field_handle( fieldno );
    46.	rec.update_field( fhandle, pdata );
    47.	rec.save();
    48.	return TRUE;
    49. }
    50. BOOL Unit::AddRecord( char * const unt_pk,char * const unt_wksfk, char * const unt_desc )
    51. {
    52.
    53.	DatabaseRecord rec(*m_pdb);
    54.	if( 
    55.	rec.add_field( field_unt_pk, unt_pk ) == DB_INVALID_FIELD_HANDLE ||
    56.	rec.add_field( field_unt_wksfk, unt_wksfk ) == DB_INVALID_FIELD_HANDLE ||
    57.	rec.add_field( field_unt_desc, unt_desc ) == DB_INVALID_FIELD_HANDLE )
    58. 	return FALSE;
    59.	rec.save();
    60.	return TRUE;
    61. }
    62. /* usage of the above implementation & declaration */
    63. static Database  dbunit( "UNIT");
    64. Unit tbunit( dbunit );
    
    Let's explain:
    Look at line 63: we declare a database called dbunit here, and its name is UNIT. And at line 64, we declare our table tbunit based on the database UNIT. Out table tbunit encapsulates the object Database dbunit because all of our operations will need the object Database.

    To add a record, we will use an object of type DatabaseRecord ( line 53 ), then we will call DatabaseRecord 's methos add_field. In simple format, this method has 2 parameters, the first one is field tag, it is a field id, the second parameter is the pointer to value need to be saved This id is necessary for reading the field later. The method AddRecord of Unit add three fields for each record. If we use only two parameters, RimOS will assume that the second parameter is a string, and the number of bytes to be saved is the length of this string. This method will return DB_INVALID_FIELD_HANDLE if an error occurs.

    The second method of DatabaseRecord we use here is save ( line 59 ). This methos has no parameter, and its purpose is to commit to the record data into flash memory.

    To read a field of a given record, we need to retrieve the handle of the record. Line 28 shows us a way to get this handle by using method get_nth_record( int recno ) of an object Database. Having the the handle, we can build the object DatabaseRecord from this handle. Line 31 shows us the second constructor of DatabaseRecord(Database * pdb, DatabaseRecordHandle h). This will build an object corresponding to the record specified by DatabaseRecordHandle h of the Database pointer pdb.

    Already having the DatabaseRecord object for the expected record, we now get the handle of the expected field by using method get_first_field_handle of this object ( line 32 ). Eventually we can get the data from this handle by get_field_data_ptr ( line 33 ).

    Similarly, line 46 shows us how to update a field using update_field. Remember, to update field, the record must already exist.

    A rather handy method of Database is get_num_records. Its name is already an explanation.

    Note:
    The full syntax of the Database constructor is:

    Database::Database( char * name, 
                        DatabaseProgressNotify * init_progrss_notify_ptr = NULL,
                        byte init_current_version = 0,
                        int max_reserve_record = 100,
                        int default_record_length = 100 );
    
    Enough flash memory will be reserved for max_reserve_record of average length of default_record_length. This is approximately 10Kb. For my Rim 957, its Flash memory size is up to 5Mb so 10kb is not a big deal. But it may be a problem for Rim with smaller Flash. If you don't have enough Flash space, you may need consider to set the default values of the last two parameters to be smaller.

    2.2 RIMOS Database with FlashObject

    ( In this example, I use the string function RIMStrCpy from StringUtils at Rim and Blackberry Developer Resources )

    All the concepts about Database class will be applied the same. The difference is we will use another set of Database-class method that helps working with FlashObject class.

    The idea behind FlashObject class is to write fixed-size data to the database, instead of variable-length data speciified by DatabaseRecord. To use FlashObject, we will need to build two classes derived from FlashObject and FlashObjectData. FlashObject plays the role of record object, while FlashObjectData the fixed-size data the embedded in FlashObject. The followings are the listing of PersonData, a FlashObjectData-derived class, and PersonObject, a FlashObject-derived class. PersonData ( line 6 - 12 )declares three fields that we want for the table :First Name, Last Name and Age. That's all. PersonObject is derived so that it can to override the virtual method virtual int get_data_length() const ( line 34 ). It is this method returns to the OS that how long the data ( structure ) for this record will be. In our example, it returns the length of the two arrays ( for firstname, lastname ) and an integer ( for age);

    Constructor of FlashObject ( and then PersonObject ) requires 3 parameters:

  • the database that holds the data
  • the record handle of the corresponding record
  • and
  • the data ( PersonData in this example )
  • The 2nd parameter needs to be a valid handle for reading ( line 92 ) or saving ( line 73 ) data, and DB_NULL_HANDLE for adding a new record (line 59 ).

    The 3rd parameter can be NULL in the case that we want to read only ( line 92 ). In case that we want to read and update, then this must be an address of a FlasObjectData-derived object. Remember that Flash memory is read-only, therefore before updating the data, we need to load them into RAM first. We do this by calling methos load of FlashObject. When adding a new one, or updating an existing one, we need to commit the data from RAM to Flash memory by calling save of FlashObject.

    Listing of Table.h
     1.#ifndef _FLSHOBJ_TABLE_H_
     2.#define _FLSHOBJ_TABLE_H_
     3.#include 
     4.#include 
     5.
     6.class PersonData: public FlashObjectData
     7.{
     8.public:
     9.	int   age;
    10.	char firstname[20];
    11.	char lastname[20];
    12. };
    13.
    14. class DbTable
    15. {
    16. public:
    17. 	Database *m_pdb;
    18. public:
    19. 	DbTable( Database &database ) { m_pdb = &database;};
    20. 	PersonData * GetRecordData( int recno  );
    21. 	BOOL UpdatePersonRecordData( int recno,  char * const  firstname,char * const lastname, int age );
    22. 	int	  GetNumberOfRecords();
    23. 	void Initialize();
    24. 	~DbTable(){};
    25. };
    26.
    27. class PersonObject: public FlashObject
    28. {
    29. public:
    30.
    31. 	// constructor
    32. 	PersonObject( Database & db, DbRecordHandle h , PersonData * pdata): FlashObject( db, h, pdata ){};
    33.     // get_data_length MUST be overidden in oder to return the correct data length
    34. 	int get_data_length() const { return (40 + sizeof(int));};
    35.
    36. 	PersonData * get_data_ptr() { return (PersonData*) FlashObject::get_data_ptr();};
    37. };
    38.
    39. class Person: public DbTable
    40. {
    41. public:
    42. 	Person( Database &database ): DbTable( database ) {};
    43. 	BOOL AddRecord( char * const firstname, char * const lastname, int age );
    44. };
    45. #endif
    

    //Listing of table.cpp 46. #include 47. #include 48. #include "StringUtil.h" 49. #include "table.h" 50. 51. BOOL Person::AddRecord( char * const firstname, char * const lastname, int age ) 52. { 53. 54. PersonData persondata; 55. persondata.initialize(); 56. RIMStrCpy( persondata.firstname, firstname ); 57. RIMStrCpy( persondata.lastname, lastname ); 58. persondata.age = age; 59. PersonObject rec(*m_pdb, DB_NULL_HANDLE, &persondata); 60. return rec.save(); 61. } 62. 63. BOOL DbTable::UpdatePersonRecordData( int recno, char * const firstname, char * const lastname, int age) 64. { 65. if( recno >= m_pdb->get_num_records() ) 66. return FALSE; 67. 68. DbRecordHandle rhandle = m_pdb->get_nth_record( recno ); 69. if( rhandle == DB_NULL_HANDLE ) 70. return FALSE; 71. 72. PersonData data; 73. PersonObject rec( *m_pdb, rhandle, &data); 74. if (!rec.load() ) return FALSE; 75. rec.get_data_ptr(); 76. RIMStrCpy( data.firstname, firstname ); 77. RIMStrCpy( data.lastname, lastname ); 78. data.age = age; 79. rec.save(); 80. return TRUE; 81. } 82. 83. PersonData* DbTable::GetRecordData( int recno ) 84. { 85. if( recno >= m_pdb->get_num_records() ) 86. return NULL; 87. 88. DbRecordHandle rhandle = m_pdb->get_nth_record( recno ); 89. if( rhandle == DB_NULL_HANDLE ) 90. return NULL; 91. 92. PersonObject rec( *m_pdb, rhandle, NULL ); 93. return rec.get_data_ptr(); 94. } 95. void DbTable::Initialize() 96. { 97. if( m_pdb == NULL ) 98. return; 99. m_pdb->delete_all_records(); 100. } 101. int DbTable::GetNumberOfRecords() 102. { 103. return m_pdb->get_num_records(); 104. }

    3. Basic notes on RIMOS user interface

    [under constrcution]

    4. How to load your DLL into your Rim device

    You can load your Dll into the Rim using the GUI tool form Blackberry CD. In my case, I prefer the command-line tool programmer that is included in the SDK. In the following examples, all of the dll are in the same directory with programmer.

    To load the Dll using programmer tool:
    C>programmer -p2 load myapp.dll
    with -p2 specifying COM2 as the port connecting to your craddle. Omitting this parameter will make programmer understand as -p1.

    Some other usages:
    - To wipe out the data area ( you will looose your data ): C>programmer wipe -F
    - To wipe out the applications ( you will need to reload applications ): C>programmer wipe -A
    - To load the basic applications to be ablle to use your RIM:
    C>programmer load pager957.bin ( ui32.dll database.dll transport_mdp.dll ribbon.dll cryptoblock.dll autotext.dll serialdbaccess.dll english.dll )

    Counter

    Home