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
-For Linking options:
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.
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.
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 ? )
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: 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.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 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.#include4.#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. }
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 )