Professional Documents
Culture Documents
Symbian Programing
Symbian Programing
Ra đời với sự hậu thuẫn của những “ông lớn” trong làng
điện thoại di động, hệ điều hành Symbian (Symbian OS)
đang ngày càng chứng tỏ khả năng của mình. Những chiếc
smartphone chạy trên nền Symbian OS ngày càng được ưa
chuộng và doanh số của các hãng ở thị phần này liên tiếp
tăng mạnh.
* Mở mà đóng
- Symbian không phải là một hệ điều hành mở như Linux trên máy vi tính hay
MobiLinux trên điện thoại di động. Tuy nhiên, mã nguồn của Symbian OS được sẵn
sàng cung cấp cho bất cứ nhà sản xuất điện thoại di động hay thiết kế chương trình
ứng dụng nào có nhu cầu. Bên cạnh đó, người dùng cũng không thể can thiệp để sửa
chữa mã nguồn của máy. Chỉ duy nhất các ứng dụng viết bằng ngôn ngữ lập trình
Java mới có thể can thiệp một phần nào đó vào các bước vận hành của hệ điều hành
này.
Ở phần trên, tôi nói qua về Symbian OS. Trước khi nói về lập trình Symbian, tôi xin
đưa ra một vài tip nhỏ giúp bạn dễ dàng hơn trong việc học lập trình trên Symbian:
- Bạn đọc các sách về lập trình Symbian. Nếu bạn mới học, hãy bắt đầu với cuốn
"Wiley S60 Programming - A Tutorial Guide (Apr - 2007).pdf” – Bạn có thể
download ở một trong các địa chỉ sau: Tại Rapidshare hoặc Tại Megaupload.
- Bạn xem các example của SDK. Có rất nhiều ví dụ hữu ích trong SDK về nhiều vấn
đề khác nhau.
- Bạn có thể tra cứu trong Help của SDK
- Bạn có thể tìm và tham khảo ở trang NewLC, một forum khá nổi tiếng dành cho lập
trình Symbian. Bạn sẽ tìm thấy rất nhiều thứ mình cần ở trang này.
-…
Sau đây tôi xin giới thiệu loạt bài để giúp các bạn có thể lập trình được với hệ điều
hành này. Ở đây chủ yếu tôi nói tới phiên bản Series 60 dành cho rất nhiều máy điện
thoại hiện nay.
- Quản lý các tệp thực thi trên Symbian
- Cấu trúc Project trên Symbian OS
- Hướng dẫn cài đặt và lập trình Symbian S60 3rd với Visual Studio 2005
- Hướng dẫn cài đặt và lập trình Symbian S60 1st & 2nd với Visual Studio 6.0
- Cấu trúc chương trình trên Symbian S60
- Quy ước và cách đặt tên khi lập trình với Symbian OS
- Xử lý ngoại lệ trên Symbian (Leave-Symbian exeption)
- An toàn hơn với Cleanup stack
- Khởi tạo hai pha (Two-phase construction) trong Symbian
- Sử dụng chuỗi (Descriptor) trên Symbian
- Một số thao tác vẽ cơ bản với đồ họa trong Symbian
- Sử dụng font và vẽ chữ trên Symbian
- Xử lý phím nhấn
- Sự kiện cho Menu
- Một số lỗi liên quan đến lập trình và cài đặt trên Symbian
- Một số mã lỗi trong Symbian OS
...
Một đối tượng hay một file trong Symbian có thể có một, hai, ba hay không cần
UID.
- Để sử dụng thuận tiện trong việc tương tác và chuyển đổi dữ liệu với các hệ thống
khác, hệ điều hành cho phép không cần sử dụng UID. Khi không có UID, Symbian sẽ
phân biệt dựa vào quy ước đặt tên.
- Ứng dụng thực thi .exe thường chỉ có UID1 với giá trị KExecutableImageUid.
- Ứng dụng DLL: Các ứng dụng này có UID1 là KDynamicLibraryUid. Với các thư
viện dùng chung .dll, UID2 sẽ là KSharedLibraryUid. Với các thư viện polymorphic,
UID2 sẽ có nhiều giá trị khác nhau tùy từng loại. UID3 thì các DLL hầu như không
cần, chỉ có các loại thư viện polymorphic là cần đến.
- Đối với các loại tài liệu thì UID1 là KDirectFileStoreLayoutUid hoặc
KPermanentFileStoreLayoutUid ứng với tài liệu độc lập và tài liệu cơ sở dữ liệu.
UID2 và UID3 phụ thuộc ứng dụng mà tài liệu phục vụ.
=> Vì UID là giá trị được sử dụng để phân biệt nên cần sự chính xác. Đối với UID3
dùng trong ứng dụng đồ họa, trong quá trình phát triển, có thể sử dụng một giá trị bất
kỳ trong khoảng 0x01000000 và 0x0fffffff. Nhưng khi cài ứng dụng vào điện thoại thì
nhất định đó phải là con số được cấp chính xác và duy nhất.
Với:
target-file: Tên file đích, thường viết luôn phần đuôi và nằm trong thư mục ứng dụng.
source-path: Đường dẫn đến nơi chứa file tài nguyên mà aif cần
resource: Tên các file tài nguyên mà aif cần với tên đầy đủ.
color-depth: Đặc tả cho tất cả các file bitmap và ở dạng [c][digit] với c là color bitmap
và "digit" thể hiện độ sâu.
Ví dụ:
aif helloworld.aif \helloworld\aif\ helloaif.rss c8 hello.bmp
hellom.bmp
- Target:
target filename.ext
Ví dụ:
target HelloWorld.app
- TargetType:
Mẫu khai báo:
targettype target-type
Với target-type được hỗ trợ bao gồm: ani, app, ctl, dll, ecomiic, epocexe, exe, exedll,
fsy, kdll, kext, klib, ldd, lib, mda, mdl, notifier, opx, pdd, pdl rdl, var, wlog.
Ví dụ:
targettype app
- UID:
Mẫu khai báo:
- TargetPath:
Mẫu khai báo:
targetpath target-path
Ví dụ:
targetpath \system\apps\HelloWorld
- SourcePath:
Mẫu khai báo:
sourcepath directory
Có thể có nhiều sourcepath nhưng đối với file mã và tài nguyên công cụ biên dịch chỉ
quan tâm đến trong khai báo sourcepath cuối cùng.
Ví dụ:
sourcepath ..\group
- Source:
Mẫu khai báo:
source source-file-list
Ví dụ:
source HelloWorld_Main.cpp
- UserInclude:
Mẫu khai báo:
userinclude directory-list
Ví dụ:
userinclude ..\inc
- SystemInclude:
Mẫu khai báo:
systeminclude directory-list
Ví dụ:
systeminclude \epoc32\include
- Resource:
Mẫu khai báo:
resource resource-file-list
resource HelloWorld.rss
- Library:
Mẫu khai báo:
library filename-list
Ví dụ:
TARGET HelloWorld.app
TARGETTYPE app
UID 0x100039CE 0x10004299
TARGETPATH \system\apps\HelloWorld
SOURCEPATH .
SOURCE HelloWorld_Main.cpp
SOURCE HelloWorld_Application.cpp
SOURCE HelloWorld_Document.cpp
SOURCE HelloWorld_AppUi.cpp
SOURCE HelloWorld_AppView.cpp
USERINCLUDE .
SYSTEMINCLUDE \epoc32\include
RESOURCE HelloWorld.rss
LIBRARY euser.lib apparc.lib cone.lib eikcore.lib
AIF helloworld.aif \helloworld\aif\ helloaif.rss c8 hello.bmp
hellom.bmp
* File mô tả thành phần bld.inf
File này luôn luôn có tên là bld.inf. Nó liệt kê danh sách các file project (thường chỉ
1), file xuất, các nền hệ thống và các file xuất với phần kiểm tra. Nó được công cụ
bldmake thực thi để tạo ra file bó abld.bat và các file thực thi khác. Thông thường
chỉ có khai báo prj_mmpfiles cho các file project được sử dụng.
Ví dụ: Với project HelloWorld trên, file bld.inf có cấu trúc như sau:
// Project files
prj_mmpfiles
HelloWorld.mmp
Chú ý:
- Mỗi câu khai báo xuất hiện trên một hàng riêng
- Sử dụng cách ghi chú của C++ cho phần ghi chú(// hoặc /* */)
- Các file nên khai báo đầy đủ phần mở rộng.
- Dấu \ được sử dụng để xác định sự liên tục dòng (dòng dưới cũng thuộc câu lệnh với
dòng trên, do dài được cắt xuống), do đó khi khai báo đường dẫn, dấu \ sau cùng phải
bỏ đi. Ví dụ nên viết SYSTEMINCLUDE \epoc32\include chứ không phải là
SYSTEMINCLUDE \epoc32\include\.
Hướng dẫn cài đặt và lập trình Symbian S60 3rd với VS2005
Dòng Symbian S60 3rd là hệ điều hành cho rất nhiều con điện thoại mạnh như Nokia
3250, Nokia E60, Nokia N91/N91 ME, N95, N95-8GB, N6290, N71, N73, N73 ME,
N75, N76, N92, N93, N93i, N6110 navigator, N5700 Xpress Music, N6120 classic,
N6121 classic, N81-MicroSD, N81-8GB, E50, E65, E90, E61/E61i, Nokia E63,
Nokia E70, Nokia E71, Nokia N80, Nokia N90, Nokia N77, Nokia N82... Vì vậy tôi
ưu tiên hướng dẫn cài đặt để lập trình cho dòng này trước.
Để cài đặt Symbian S60 3rd Edition tích hợp với IDE Microsoft Visual Studio
2005, thì đầu tiên bạn phải download Carbide.vs 3.0, bạn có tìm trên mạng chương
trình này hoặc download từ một trong hai địa chỉ sau:
- Download tại Rapidshare
- Download tại Megaupload
Sau khi download xong, bạn chạy chương trình này. Chương trình sẽ hướng bạn chi
tiết cần cài những gì và cả địa chỉ để download những thứ này. Cụ thể bạn phải cài đặt
những thứ sau:
1. Bạn cài Active Perl 5.6.1. Bạn hãy download tại địa chỉ Download Active Perl
5.6.1.638. Tôi cài nó vào thư mục C:\Symbian\Perl
2. Bạn cài SDK S60 3rd Edition. Bạn download SDK S60 tại địa chỉ Download SDK
S60. Ở đây tôi download và cài bản 3rd Edition, Maintenance Release. Tôi cài vào
thư mục C:\Symbian\9.1. Trong khi cài đặt, chương trình có hỏi bạn có cài đặt CSL
ARM Q1C Toolchain, thì bạn nhấn Yes để cài đặt nó. Và tôi cài nó vào thư mục
C:\Symbian\CSL Arm Toolchain
3. Bạn cài Java Runtime. Bạn download chương trình này trực tiếp tại trang chủ của
Java Sun
4. Bạn cài Visual Studio 2005. Sau khi cài xong Visual Studio 2005, bạn phải cài
Visual Studio 2005 SP1.
=> Các chương trình trên, bạn cài cái nào trước cũng được. Sau khi đã cài hết bạn
chạy lại Carbide.vs 3.0 và cài đặt bình thường. Ở đây tôi cài đặt vào thư mục
C:\Symbian\Carbide_vs30
Sau khi cài đặt xong, bạn restart lại máy tính.
Bây giờ bạn bật Visual Studio 2005, khi đó Carbide.vs 3.0 sẽ tự động chạy, nó hỏi
bạn đăng ký hoặc dùng 14 ngày. Nếu bạn lập trình bạn nên đăng ký luôn nhưng để
đăng ký được bạn phải đăng ký account trên trang Nokia Forum. Để đăng ký bạn chỉ
cần chọn "I don't have serial number" rồi nhấn Next. Nếu không có mạng, bạn chọn "I
don't have Internet Connection" nhấn Next và làm theo hướng dẫn. Nếu bạn có mạng
bạn chọn "I have Internet Connection" và nhấn next, nó sẽ tự động link đến trang để
lấy key, bạn đăng nhập xong, bạn sẽ nhập được Serial Number. Bạn quay lại
Carbide.vs và nhập Username (Là Username mà bạn đăng nhập
http://www.forum.nokia.com) và nhập Serial Number vào để đăng ký.
Như vậy bạn đã hoàn thành cài đặt. Bây giờ bạn thử tạo Project Symbian mới. Bạn
vào File/New/Project, trong Visual C++ bạn chọn Symbian. Bạn chọn New
Symbian OS Project trong mục chọn Template, đánh tên Project (HelloWorld
chẳng hạn) và chọn thư mục. Các Project của Symbian tôi để trong thư mục
C:\Symbian\Projects. Bạn nhấn Next, và chọn SDK (S60 3.0 Maint) rồi nhấn OK.
Bây giờ xuất hiện hộp thoại, bạn chọn:
- Project type: Symbian 9
- Project template: S60 3rd Ed. Hello World Application
- Bạn chọn S60 3.0 Maint, bạn chọn cả:
- WINSCW (Để lập trình và chạy trên máy ảo, luôn được chọn)
- GCCE (Để bạn build file sis chạy trên máy thật)
Bây giờ bạn nhấn Next để tùy chình thêm hoặc Finish để hoàn thành.
Bây giờ bạn để chế độ Debug, build và chạy thử. Máy ảo tự động được bật, nhưng
chương trình không tự động chạy được. Bạn phải vào mục Installations, bạn thấy
chương trình HelloWorld mà bạn vừa viết. Bạn hãy chạy thử xem.
Bây giờ bạn chọn chế độ Release, build lại Project. Trong thư mục Project của bạn,
bạn sẽ nhận được file HelloWorld.SIS trong thư muc sis. Bạn chỉ cần Sign file sis
này là có thể cài được trên máy thật sử dụng Symbian 3rd.
Bạn nên tìm hiểu và tham khảo các ví dụ của SDK trong thư mục:
C:\Symbian\9.1\S60_3rd_MR\Examples.
Dòng Symbian S60 3rd và Symbian UIQ 3.x được thiết kế với chế độ bảo mật cao
hơn hẳn các dòng Symbian S60 1st và 2rd. Dòng này yêu cầu ứng dụng của bạn phải
được Sign trước khi bạn cài đặt nó lên máy thật. Với mỗi ứng dụng tương ứng với
một IMEI, bạn có thể vào trang SymbianSigned.com để Sign, và bạn phải chờ đợi để
nhận được ứng dụng đã được sign. Nếu bạn đã cài SDK, tôi xin hướng dẫn bạn tự ký
ứng dụng của mình (self-sign Symbian software) và ứng dụng của bạn đã sign sẽ chạy
trên tất cả các máy Symbian S60 3rd và Symbian UIQ 3.x mà không phân biệt từng
IMEI khác nhau. Bây giờ tôi hướng dẫn cách sign thông qua việc sign ứng dụng
HelloWorld.SIS của chúng ta:
- Đầu tiên, bạn bật hộp thoại Run, đánh cmd rồi Enter để vào màn hình Command
Line.
- Trong màn hình Command, bạn đánh lệnh:
cd \
cd "C:\Symbian\Projects\HelloWorld\HelloWorld\sis"
Bạn di chuyển chuột trên màn hình command line để lấy dữ liệu ngẫu nhiên cho việc
tạo tệp certificate.
- Bây giờ bạn đánh lệnh sau để thực hiện Sign tệp SIS:
Chú ý:
- Bạn làm tương tự để signed cho ứng dụng khác.
- Bạn có thể dùng trực tiếp tệp helloworld.cer và helloworld.key để sign cho ứng
dụng khác, khi đó bạn chỉ cần dùng lệnh signsis mà thôi. Bạn chỉ phải thay tên file sis
dùng để sign và file sis tạo ra sau khi sign.
- Chứng chỉ của bạn (.cer và .key) chỉ có thời hạn trong một năm, kể từ lúc bạn tạo
hai tệp này.
- Với các ứng dụng tự sign, khi cài đặt trên máy thì sẽ có cảnh báo Warning. Bạn
chọn Continue để cài đặt.
Hướng dẫn cài đặt và lập trình Symbian S60 1st & 2nd với VC6
Để lập trình cho các dòng Symbian đời cũ như S60 1st và S60 2nd, nếu bạn không sử
dụng các hàm nào đặc biệt thì bạn nên cài bản 1st Edition, FP1, Wins. Khi đó ứng
dụng của bạn sẽ tương thích với tất cả các máy sử dụng dòng Symbian S60 1st và
2nd.
Để cài đặt và lập trình cho dòng này, bạn thực hiện như sau:
1. Bạn cài đặt ActivePerl-5.6.1 (Nếu bạn chưa cài). Bạn hãy download tại địa chỉ
Download Active Perl 5.6.1.638. Tôi cài nó vào thư mục C:\Symbian\Perl
2. Bạn cài đặt S60 SDK 1st Edition. Bạn download SDK S60 tại địa chỉ Download
SDK S60. Ở đây tôi download và cài bản 1st Edition, FP1, Wins. Tôi cài vào thư
mục C:\Symbian\6.1.
3. Bạn cài Visual Studio 6.0 để làm IDE lập trình. IDE này tôi cài vào thư mục mặc
định.
cd \
cd c:\symbian\projects\helloworld
bldmake bldfiles
khi đó tệp ABLD.BAT sẽ được tạo trong thư mục project helloworld.
- Bây giờ bạn đánh tiếp lệnh:
abld makefile vc6
để tạo ra tệp Project (HelloWorld.dsw) trên VC6. Bạn đừng đóng màn hình
Command Line vội. Tí nữa bạn sẽ còn phải dùng tiếp.
- Bây giờ bạn search file “helloworld.dsw” trong thư mục cài đặt SDK
(C:\Symbian\6.1), chính xác thì nó ở trong thư mục
C:\Symbian\6.1\Series60\Epoc32\BUILD\SYMBIAN\PROJECTS\HELLOWOR
LD\HELLOWORLD\WINS
- Bạn mở tệp “helloworld.dsw” bằng Visual Studio 6.0, bạn chon chế độ Debug
(Release cũng vậy thôi), và nhấn F7 để build thử. Không lỗi gì đúng không bạn. Bây
giờ bạn ấn Ctrl+F5 để chạy thử chương trình trên máy ảo. Nếu nó xuất hiện hộp thoại
thì bạn chỉ đường dẫn đến tệp epoc.exe cho nó:
+ Nếu dịch Debug bạn chỉ đến
C:\Symbian\6.1\Series60\Epoc32\Release\wins\udeb\epoc.exe
+ Nếu dịch Release thì bạn chỉ đến thư mục
C:\Symbian\6.1\Series60\Epoc32\Release\wins\urel\epoc.exe
Bạn nhấn OK, máy ảo sẽ được bật lên. Bạn dùng phím mũi tên di chuyển xuống dưới,
bạn sẽ thấy ứng dụng helloworld mà bạn vừa build. Bạn hãy chọn nó để chạy thử:
- Chạy máy ảo vậy là okie. Bây giờ bạn sẽ build để chạy máy thật. Bạn đánh lệnh sau:
- Bạn copy hai tệp này vào thư mục nào đó trong ổ C:, chẳng hạn tôi copy vào thư
mục C:\Symbian\Apps\helloworld
-Trong thư mục C:\Symbian\Apps\helloworld bạn tạo thêm tệp helloworld.pkg như
sau:
; helloworld.pkg
;
- Sau đó bạn đánh lệnh sau để vào thư mục chứa các tệp này. Sau đó đánh các lệnh
sau:
cd \
cd "C:\Symbian\Apps\helloworld"
makesis helloworld.pkg
Bây giờ bạn đã có file helloworld.SIS để cài đặt và chạy trên máy thật.
=> Vậy là Okie rồi. Chúng ta đã cài đặt và chạy thử thành công.
Sau đây là chương trình Helloworld đã được viết và đóng gói lại thành file *sis
Bây giờ chúng ta đi sau phân tích cấu trúc của một chương trình trên Symbian S60
thông qua ví dụ HelloWorld ở trên. Đây là ứng dụng dạng GUI có đuôi .APP. Để
thống nhất tên lớp thì trên Symbian S60 3rd, bạn tạo Project với tên là Example chứ
không phải HelloWorld như trước nữa.
Chúng ta thấy rằng, ứng dụng HelloWorld bao gồm các lớp như sau:
- Lớp CExampleApplication: Đây chính là lớp Application và kế thừa từ
CEikApplication (trên Symbian S60 1st) hoặc lớp CAknApplication (trong
Symbian S60 3rd). Lớp này chịu trách nhiệm thiết lập và thực thi ứng dụng.
- Lớp CExampleDocument: Đây là lớp Document của ứng dụng. Lớp này được kế
thừa từ lớp CEikDocument (trên Symbian S60 1st) hoặc lớp CAknDocument (trên
Symbian S60 3rd). Lớp này được tạo bởi lớp Application, nó quản lý dữ liệu của ứng
dụng. Lớp này cũng có nhiệm vụ khởi tạo lớp giao diện ứng dụng AppUi.
- Lớp CExampleAppUi: Đây là giao diện ứng dụng AppUi. Lớp này kế thừa từ
CEikAppUi (Symbian S60 1st) hoặc CAknAppUi (Symbian S60 3rd), và có nhiệm
vụ điều khiển các sự kiện. Lớp này sẽ nhận các sự kiện như chọn menu, phím bấm, …
và gửi sự kiện này tới các View và Container.
Lớp này điều khiển sự kiện chọn menu thông qua hàm
- Lớp CExampleAppView: Đây chính là view để hiển thị ra màn hình, nó kế thừa từ
lớp CCoeControl. Bạn hãy thực hiện thao tác vẽ trong hàm:
Như vậy chúng ta thấy cấu trúc ứng dụng GUI trên Symbian S60 như sau:
Khi ứng dụng được chọn thực thi, chương trình apprun.exe sẽ hoạt động với tên ứng
dụng và tên file ứng dụng làm tham số. Chương trình apprun sẽ sử dụng kiến trúc
ứng dụng APPARC để nạp ứng dụng qua việc kiểm tra UID2 là KUiApp
(0x100039ce) và tạo đối tượng ứng dụng đồ họa qua hàm NewApplication(). Các
hàm này bao gồm hàm [color=#0000FF]NewApplication()[/color] và hàm
E32Dll(TdllReason) (một hàm chỉ cài đặt, không sử dụng). Từ khóa EXPORT_C để
báo hàm NewApplication là đầu vào của một DLL.
Khi NewApplication() được gọi, đối tượng CExampleApplication sẽ được tạo, đối
tượng này tạo CExampleDocument, và lớp CExampleDocument sẽ tạo lớp giao
diện ứng dụng CExampleAppUi. Khi lớp CExampleAppUi được tạo, hàm khởi tạo
ConstructL của AppUi sẽ được gọi, và trong hàm này lớp CExampleAppView được
tạo thông qua lệnh:
iAppView = CExampleAppView::NewL(ClientRect());
Khi view được tạo, ngoài các hàm khởi tạo ContructL, nó gọi thêm hàm
để điều khiển sự kiện cho menu. Nhưng trong ứng dụng trên Symbian S60 1st của
chúng ta không có menu nên bạn không thể thoát khỏi ứng dụng. Bây giờ tôi thực
hiện điều khiển sự kiện phím nhấn để thực hiện thoát khỏi ứng dụng khi nhấn phím
trái. Ở đây tôi sử dụng hàm HandleWsEventL để điều khiển nhận sự kiện phím trái để
thoát khỏi ứng dụng. Để thực hiện được điều này, bạn làm như sau:
- Bạn thêm khai báo vào phần khai báo của lớp CExampleAppUi:
Bây giờ bạn chạy ứng dụng, và dùng phím chức năng bên trái để thoát khỏi ứng dụng.
Việc bạn bắt sự kiện phím trên Symbian OS 3rd cũng tương tự.
Mô hình MVC và Symbian có mối quan hệ rất mật thiết, nó là mô hình thiết kế chủ
đạo trong Symbian. Nếu để ý kỹ bạn sẽ thấy lớp document là hiện thân của model,
lớp AppView chính là view, còn AppUi sẽ đóng vai trò là controller trong mô hình
MVC. Với những ứng dụng phức tạp thì sẽ có nhiều lớp đảm nhận một thành phần
trong MVC.
Không những vậy, hầu hết các control trong Symbian đều được thiết kế theo mô hình
MVC. Nắm bắt được điều này, bạn sẽ dễ dàng thao tác với các control trong
Symbian. Tôi nhớ là có khá nhiều người mới lập trình Symbian khi làm quen với
listbox đều đặt câu hỏi: "Làm sao để lấy dữ liệu một item trong listbox đây bởi trong
lớp CEikListbox không thể tìm thấy hàm nào đảm nhận việc này". Đó là vì listbox
trong Symbian cũng được thiết kế theo mô hình MVC nên nếu muốn lấy dữ liệu, bạn
phải đến lớp MListBoxModel thông qua hàm Model() trong lớp CEikListbox.
Quy ước và cách đặt tên khi lập trình với Symbian OS
Symbian đưa ra một số quy ước trong lập trình. Một số quy ước bạn không nhất thiết
phải theo nhưng nhưng một số thì bạn nên tuân thủ để phục vụ cho việc lập trình của
bạn thuận lợi, tránh sai sót và dễ nâng cấp sau này.
1. Tên lớp
Symbian sử dụng các quy ước đặt tên sau để xác định đặc tính cơ bản của một lớp:
- Lớp T: Lớp đơn giản (tựa như typedef) thường xây dựng từ các kiểu dữ liệu cơ sở
hay kết hợp chúng lại, có thể so sánh lớp T với struct đơn giản bao gồm các dữ liệu
public. Nó không có destructor (có thể có constructor nhưng hiếm) và thường được
lưu trên stack, có thể lưu trên heap. Kiểu liệt kê (enum) cũng thường khai báo dưới
dạng lớp T. Ví dụ: TInt, TBool, TPoint, TDes, TMonthsOfYear ...
- Lớp C: Lớp có constructor và destructor và tất cả đều là dẫn xuất từ CBase. Các đối
tượng của chúng được tạo bằng new và luôn được lưu trữ trên heap. Ví dụ:
CConsoleBase, CActive,...
- Lớp R: Lớp R (đại diện cho Resource), thường đại diện cho một loại tài nguyên,
quản lý một sesion kết nối với một server phục vụ một tài nguyên. Đối tượng lớp R
thường có một hàm khởi tạo (Open() hoặc Create() hay Initialize()) và một hàm kết
thúc (Close() hay Reset()) để giải phóng tài nguyên. Quên gọi hàm kết thúc khi dùng
đối tượng lớp R là một lỗi thường gặp và kết quả là sẽ bị leak bộ nhớ. Nó có thể được
lưu trên heap, nhưng thường là trên stack. Ví dụ: RFile, RTimer, RWindow,...
- Lớp M (Mix-ins): Lớp ảo (abstract) giống như interface trong Java, nó chỉ bao
gồm các phương thức ảo rỗng và không có dữ liệu cũng như constructor. Việc kế thừa
nhiều lớp trong Symbian là cho phép tuy nhiên phải theo quy tắc là kế thừa chính từ 1
lớp C (bắt buộc phải có và viết đầu tiên) và nhiều lớp M. Ví dụ:
MGraphicsDeviceMap, MGameViewCmdHandler,...
=> Việc phân biệt giữa T, C và R lớp là rất quan trọng, nó ảnh hưởng tới việc giải
phóng bộ nhớ khi sử dụng cũng như cách thức xử lý các đối tượng thuộc các lớp này.
Ngoài ra trong Symbian còn có các lớp tĩnh (static) phục vụ cho một số chức năng
riêng như lớp User hay lớp Mem. Một ngoại lệ khác là lớp HBufC, chúng ta sẽ nói
đến nó trong bài viết về sử dụng xâu trên Symbian.
2 Tên dữ liệu
Tương tự, Symbian cũng dùng chữ cái đầu để phân biệt các loại dữ liệu:
- Hằng liệt kê (Enumerated constant): Bắt đầu với ký tự E, nó đại diện cho một giá
trị hằng trong một dãy liệt kê. Nó có thể là một phần của lớp T. Ví dụ: (ETrue,
EFalse) của TBool hay EMonday là một thành phần của TDayOfWeek.
- Hằng (constant): Bắt đầu với ký tự K, thường được dùng trong các khai báo
#define hay các giá trị hằng do Symbian quy định. Ví dụ: KMaxFileName hay
KErrNone.
- Biến thành phần (member variable): Bắt đầu với chữ cái i (instance), được dùng
khi sử dụng các biến động là thành viên của một lớp. Đây là quy ước quan trọng,
dùng cho việc hủy vùng nhớ trên heap của các đối tượng này trong destructor. Tôi
thường chỉ dùng quy ước này nếu biến này sẽ được lưu trên heap, còn trên stack thì
không. Ví dụ: iDevice, iX, …
- Tham số (argument): Bắt đầu bằng chữ a (argument), được dùng khi các biến làm
tham số. Ví dụ: aDevice, aX, …
- Macro: Không có quy ước đặc biệt. Tất cả đều viết hoa và dùng dấu gạch dưới để
phân tách từ. Ví dụ: IMPORT_C, _TEST_INVARIANT, _ASSERT_ALWAYS,
v.v…
- Biến cục bộ (automatic): Chữ cái đầu nên viết thường.
- Biến toàn cục (global): Nên viết hoa chữ cái đầu nhưng để tránh nhầm lẫn nên bắt
đầu tên bằng chữ cái “g”. Tuy nhiên trên Symbian không khuyến khích dùng biến
toàn cục.
3. Tên hàm
Tên hàm bắt đầu bằng ký tự hoa. Khác với 2 trường hợp trên, quy ước đặt tên hàm lại
dựa trên ký tự cuối cùng:
- Hàm không ngắt giữa chừng (non-leaving function): Đó là hàm mà trong quá
trình thực thi nó đều diễn ra suông sẻ, chi tiết tôi sẽ nói sau. Ví dụ: Draw() hay
Intersects().
- Hàm ngắt giữa chừng (leaving function): Là hàm bị ngắt ngang vì một lý do nào
đó: lỗi, thiếu tài nguyên, ... Hàm này kết thúc bằng ký tự L. Ví dụ: DrawL() hay
RunL().
- Hàm LC: Kết thúc với cặp ký tự LC. Các hàm này trong lòng nó có khai báo một
đối tượng mới, và có đặt đối tượng này lên cleanup stack (ngăn xếp chứa các đối
tượng cần xóa khi có ngắt xảy ra, sẽ nói rõ sau) và có khả năng xuất hiện ngắt trong
khối xử lý hàm. Bạn lưu ý là sau khi gọi hàm này sẽ phải gọi Cleanup:PopAnd
Destroy(), lý do tôi sẽ nói trong phần về cleanup stack, nếu quên gọi nó chắc chắn bạn
sẽ bị lỗi 3 mà không hiểu tại sao. Ví dụ: AllocLC(), CreateLC(), OpenLC() hay
NewLC(),...
- Các hàm Get và Set: Trong trường hợp đơn giản thường là các hàm thành viên của
một lớp. Set dùng cho việc xác lập giá trị cho một biến thành viên của lớp. Get được
dùng cho các hàm sẽ trả về giá trị trên tham số. Khi hàm có giá trị trả về thì thường
không dùng Get.
Ví dụ: SetThing(aData); GetThing(aData); nhưng iData = Thing();
void Example()
{
.........
.........
}
và
void Example(){
.........
.........
}
void Example()
->{
->.........
->........
->}
Xử lý ngoại lệ trên Symbian (Leave-Symbian exeption)
Vì vậy Symbian đã đưa ra một cơ chế quản lý lỗi cho riêng mình được biết dưới tên
gọi "leave". Do đó tuy Symbian sử dụng cú pháp C++ nhưng không hề có từ khóa try,
catch hay throw đâu, các bạn nên chú ý điều này.
Trong một môi trường mà tài nguyên hạn hẹp như Symbian thì một cơ chế bắt lỗi hiệu
quả và ít tốn kém sẽ rất cần thiết. "Leave" đã đáp ứng điều này. Cơ chế hoạt động
của "leave" như sau: "Khi xảy ra một lỗi nào đó (thiếu bộ nhớ để cấp phát, thiếu vùng
nhớ để ghi, lỗi trong truyền thông hay thiếu năng lượng cho các tài nguyên,...) thì
hàm đang hoạt động sẽ bị ngắt lại, quyền điều khiển sẽ được chuyển đến phần chỉ thị
sửa lỗi".
Xét về mặt cú pháp thì cơ chế "leave" này khá tương đồng với cơ chế của C++. Hàm
đang thực thi bị ngắt bởi một cuộc gọi đến hàm User::Leave() hay
User::LeaveIfError() khá giống với throw trong C++ còn 2 marco TRAP và TRAPD
trên Symbian thì tương đồng với try và catch trên C++.
Ví dụ:
TInt result;
TRAP(result, MyLeaveL());
if (KErrNone==result)
{
//Code
}
User::LeaveIfError(result);
2. Hàm leave
Như tôi đã nói trong phần quy ước trên Symbian, hàm có thể leave thì sẽ kết thúc
bằng chữ L. Một hàm có thể leave nếu nó:
- Gọi hàm có thể leave mà không được gọi kèm với các trap harness như TRAP hay
TRAPD.
- Gọi một trong các hàm hệ thống đảm nhận leave như User::Leave() hay
User::LeaveIfError(),...
- Có dùng toán tử new(Eleave).
Chắc có lẽ có nhiều bạn sẽ thắc mắc tại sao tôi lại quá chú trọng đến "leave" như vậy.
Thật ra "leave" là một khái niệm rất cơ bản trên Symbian bởi vì:
- Thứ nhất, nguồn tài nguyên trên Symbian khá hạn hẹp nên lỗi thiếu tài nguyên hay
xảy ra
- Thứ 2, nếu bạn không chú ý kỹ đến nó, nhất là phần thế nào là một hàm leave thì
bạn sẽ gặp phải lỗi rất lớn trong lập trình trên Symbian: gây ra "leak" bộ nhớ.
Khi hàm của bạn có leave xảy ra thì tại thời điểm leave, điều khiển sẽ được chuyển
đến phần xử lý lỗi, lúc này vùng stack cho hàm có leave này sẽ được giải phóng, các
biến khai báo cục bộ trong hàm này sẽ bị xóa đi. Đối với các biến khai báo kiểu T trên
stack thì không sao nhưng đối với các biến kiểu C khai báo trên heap hay các biến
kiểu R thì đây là vấn đề nghiêm trọng. Bởi lẽ theo đúng quy trình thực thi, nếu không
có gì xảy ra thì vào cuối hàm, chúng ta sẽ hủy vùng nhớ đối tượng trên heap qua toán
tử delete hay gọi hàm Close() cho các biến kiểu R nhưng nếu giữa chừng hàm bị ngắt
trước khi ta gọi các hàm hủy này thì rõ ràng các đối tượng này sẽ không được giải
phóng hoàn toàn, tạo ra leak (lỗ hổng) trên bộ nhớ.
Leak bộ nhớ là vùng nhớ trên heap thực sự không được sử dụng nhưng hệ điều hành
nghĩ là nó đang sử dụng và sẽ không sử dụng vùng nhớ này để cấp phát cho các đối
tượng khác. Leak bộ nhớ thường do bạn cấp phát động bộ nhớ (sử dụng hàm new, ...)
mà không giải phóng nó (hàm delete,...). Bị leak bộ nhớ sẽ gây ra sự lãng phí tài
nguyên bộ nhớ, đặc biệt trên thiết bị giới hạn về tài nguyên như di động thì là cả một
vấn đề.
Ví dụ:
void UnsafeFunctionL()
{
CExClass* test = CExClass::NewL(); //Hàm có thể leave
test->FunctionMayLeaveL();
delete test;
}
Vậy bây giờ ta phải làm sao đây để luôn đảm bảo không bị lỗ hổng trên bộ nhớ khi có
leave xảy ra? Symbian đã đưa ra khái niệm mới là Cleanup Stack. Cleanup stack là
một ngăn xếp có nhiệm vụ giải phóng các vùng nhớ cấp cho các đối tượng được đưa
vào chúng trước đó khi leave xảy ra.
Ví dụ: Dùng hàm trên với cleanup stack:
void SafeFunctionL()
{
CExClass* test = CExClass::NewL(); //Hàm có thể leave
CleanupStack:: push(test);
test->FunctionMayLeaveL();
CleanupStack:: pop(test);
delete test;
}
Lúc này nếu có leave xảy ra thì chúng ta vẫn không sợ bị lủng bộ nhớ vì cleanup stack
đã giải phóng vùng nhớ cho biến test giùm chúng ta rồi nhờ hàm:
CleanupStack::push(test).
Một số lưu ý:
- Nếu leave không xảy ra thì chúng ta phải lấy đối tượng cần hủy ra khỏi cleanup
stack qua hàm CleanupStack::pop(test) (chúng ta có thể dùng nhiều cách pop khác
nhau, chi tiết các bạn xem qua lớp CleanupStack).
- Với những hàm kế thúc bằng LC, nghĩa là có thể leave và đã có push lên cleanup
stack rồi, nên sau khi gọi hàm này, bạn phải gọi hàm CleanupStack::pop() hoặc
CleanupStack::popAndDestroy() nếu không sẽ bị lỗi. Lỗi này tôi cũng đã nói trong
phần quy ước rồi, các bạn chú ý nhé, hay bị lỗi này lắm đó.
- Đối với các đối tượng kế thừa các lớp khác ngoài lớp C thì khi hủy cleanup stack
chỉ có thể hủy vùng nhớ mà không thể gọi destructor như đối với lớp C được nên
Sym bian đề xuất một số hàm push khác cho phù hợp: CleanupReleasePushL() để
chỉ giải phóng vùng nhớ (đối tượng lớp T), CleanupDeletePushL() để chỉ thực thi
destructor (đối tượng lớp M) hay CleanupClosrPushL() để giải phóng tài nguyên
cấp cho các đối tượng lớp R.
Leave và Cleanup stack là một cặp bài trùng tạo nên sự an toàn cho lập trình trên
Symbian. Đây là 2 khái niệm rất cơ bản, nếu không hiểu vè nó, trong khi lập trình có
thể bạn sẽ gặp lỗi mà không biết đường sửa hay có thể gặp vài lỗi rất ngớ ngẫn.
Đến đây chắc bạn đã thấy là Symbian hỗ trợ quản lý bộ nhớ tốt như thế nào, đảm bảo
cả trong điều kiện lỗi vẫn không bị lủng bộ nhớ. Lý do khiến Symbian rất chú trọng
đến việc này là so với PC, điện thoại di động có bộ nhớ không lớn bằng hơn nữa các
ứng dụng trên điện thoại đôi khi phải chạy hàng tháng, thậm chí hàng năm (nếu ta
không tắt máy).
Từ bài cleanup stack, có người thắc mắc là có phải tất cả các lớp đều có hàm NewL()
và NewLC() để khởi tạo đối tượng không. Nhận thấy có một phần quan trọng đi liền
sau leave và cleanup stack là khởi tạo 2 pha (two-phase construction) nên tôi sẽ xin
nói về nó luôn.
Hệ thống sẽ làm gì với code trên: "Đầu tiên, một vùng nhớ sẽ được cấp trên heap cho
đối tượng lớp CExam nhớ hàm new, rồi sau đó constructor của lớp CExam sẽ được
gọi để hoàn tất việc khởi tạo đối tượng. Điều gì sẽ xảy ra nếu hàm constructor của
lớp này bị leave, rõ ràng là bộ nhớ sẽ bị lủng do heap đã cấp một vùng cho đối tượng
foo rồi và như vậy vùng nhớ này sẽ bị "mồ côi" trên heap".
=> Lúc này ắt hẳn bạn sẽ nghĩ ngay đến cleanup stack, thế nhưng tiếc thay trong
trường hợp này lại không dùng được. Tai sao ư? Tại vì để làm được điều đó thì hàm
CleanupStack::push phải đặt trong lòng toán tử new, điều này có thể sao?!
Vì vậy Symbian đưa ra một luật là: "constructor không được phép leave". Nhưng đôi
khi trong phần khởi tạo của chúng ta lại có phần có thể leave thì sao, chẳng hạn như
cấp phát bộ nhớ hay tạo một session truy cập tài nguyên. Trong tình huống đó, khởi
tạo 2 pha (two-phase construction) sẽ giúp bạn:
- Pha 1: Phần constructor đơn giản, không leave. Phần này sẽ được gọi liền ngay sau
khi toán tử new được gọi.
- Pha 2: Một hàm khác sẽ đảm nhận việc hoàn tất khởi tạo, trên Symbian thường đặt
tên là ConstructL(), hàm này có thể leave.
Và bây giờ thì ta đã yên tâm là bất cứ có gì xảy ra, bộ nhớ vẫn nguyên vẹn.
2. NewL() và NewLC()
Nhưng cách trên bất tiện ở chỗ là khi khởi tạo 1 đối tượng lại phải gọi 2 pha với 4
hàm, vừa bất tiện vừa dễ quên. Do đó Symbian tiếp tục đưa ra một khái niệm nữa để
giúp cho lập trình viên chúng ta tránh được sự hay quên và dài dòng này. Trong lớp
CExam, chúng ta tạo thêm 2 hàm tĩnh (có từ khóa static đằng trước phần khai báo
thông thường) NewL() và NewLC() như sau:
CExam* CExam::NewLC()
{
CExam* me = new (Eleave) CExam(); // Pha 1
CleanupStack::push(me);
exam->ContructL(); //Pha 2
return me;
}
CExam* CExam::NewL()
{
CExam* me = CExam::NewLC();
CleanupStack:: pop (me);
return me;
}
Và lúc này việc khai báo đối tượng của bạn sẽ vừa dễ dàng lại đảm bảo:
Lưu ý: Ở trên các bạn thấy tôi luôn xài Eleave sau toán tử new. Nhờ nó mà nếu
không cấp phát được, leave sẽ xảy ra. Nếu không có nó rõ ràng sau hàm new bạn phải
kiểm tra xem có cấp phát thành công không, rất mất công lại làm code phức tạp thêm.
CExam* exam = new CExam();
if (NULL != exam)
{
// Cấp phát thành công
}
Tóm lại: Nếu trong hàm constructor mà không có gì để gây ra leave cả, thì các bạn
cứ xài như đã từng xài trước đây, nghĩa là dùng code như C++ vậy. Nếu trong hàm
constructor này mà có leave thì bạn mới phải cần đến two-phase construction.
Descriptor được xây dựng theo hướng tối ưu hóa vùng nhớ cấp phát, điều rất cần trên
các môi trường bộ nhớ thấp như Symbian. Nhưng cũng chính vì điều này mà
descriptor làm đau đầu khá nhiều lập trình viên về việc sử dụng chúng.
Từ version 5.0 trở về trước, Symbian chưa hỗ trợ Unicode nên descriptor chỉ hỗ trợ
chuỗi với ký tự 8 bit, nhưng từ v5.1 trở về sau thì descriptor trên Symbian hỗ trợ cả 8
bit và 16 bit (Unicode). Một điểm đặc biệt nữa là descriptor không sử dụng ký tự đặc
biệt để kết thúc chuỗi như trên C/C++ hay Java, nên dữ liệu chúng chứa có thể là dữ
liệu nhị phân. Việc sử dụng chung descriptor cho lưu trữ ký tự và nhị phân rõ ràng sẽ
làm nhỏ gọn lại Symbian và tiện lợi cho người dùng. Lưu ý là khi muốn lưu trữ nhị
phân, bạn phải khai báo descriptor ở dạng 8 bit.
Hình vẽ sau cho cho ta thấy được kiến trúc của Descriptor trên Symbian:
Trên vùng nhớ , một descriptor được lưu trữ như sau:
- 4 byte đầu lưu trữ chiều dài (thật ra chỉ 28 bit trong số 32 bit (4 byte) là lưu trữ
chiều dài chuỗi, do đó chuỗi tối đa mà ta có thể chứa trong descriptor là 2^28 byte,
256 MB, 4 bit cao còn lại chứa kích thước của đối tượng descriptor, đây là cơ sở để
xác định kiểu descriptor. Với 4 bit chúng ta có thể phân biệt 16 kiểu descriptor nhưng
trên Symbian chỉ có 5 loại).
- Phần tiếp theo sẽ lưu trữ dữ liệu descriptor hay con trỏ chỉ đến vùng lưu trữ thật sự.
Lấy chiều dài thông qua hàm Length(), trong khi truy cập dữ liệu thì nhở vào hàm
Ptr(). Đây là 2 hàm ảo quan trọng nhất, từ đây TDesC sẽ cài đặt tất cả các hàm liên
quan cho descriptor như truy cập dữ liệu, so sánh chuỗi, tìm kiếm, ...
Đây là lớp trừu tượng nên nó không thể khởi tạo, vì vậy khi sử dụng lớp này làm tham
số cho hàm bạn sử dụng cú pháp dạng const TDesC& để cung cấp các truy cập chỉ
đọc tới dữ liệu của Descriptor.
TDes định nghĩa một loạt các phương thức phục vụ cho việc thay đổi dữ liệu như
thêm, chèn, hay định dạng dữ liệu,... Một đặc điểm đáng lưu ý là một khi đã khai báo
chiều dài tối đa thì không sao thay đổi được, do TDes và các lớp kế thừa từ nó đều
không có hàm phục vụ cho việc cấp thêm bộ nhớ nên khi thêm dữ liệu, bạn phải lưu ý
là dữ liệu thật sự lưu không bao giờ được vượt quá max length, nếu không bạn sẽ gặp
phải lỗi.
TDesC, TDes (TDes16, TDes8 - Hiện nay TDes luôn là TDes16). Đây là các lớp cơ
sở của tất cả các lớp descriptor khác, chúng chủ yếu cung cấp các hàm thao tác cho
các descriptor hằng cũng như động khác hơn là phục vụ cho mục đích lưu trữ dữ liệu
như các lớp dẫn xuất khác. Do đó, bạn sẽ thấy chúng chủ yếu ở vai trò làm tham số
hay kết quả trả về nhằm mục đích phục vụ cho lập trình tránh bị phụ thuộc loại
descriptor cụ thể (đặc điểm độc đáo của lập trình hướng đối tượng). Một điểm nữa
củng cố cho điều trên là bạn không thể khai báo trực tiếp một đối tượng kiểu TDesC
hay [color=#BF0000]TDes[/color] được do constructor của chúng là hàm protected.
Literals cung cấp cách đơn giản để định nghĩa xâu trong chương trình. Chúng ta có
thể định nghĩa và sử dụng như sau:
_LIT(KMyName, "Helen");
TBuf<KMaxItemLength> myName;
myName.Append(KMaxItemLength);
Literals rất dễ thay đổi nếu bạn định nghĩa chúng ở đầu file .cpp, chúng chỉ đơn giản
như định nghĩa marco.
Cũng giống như ở trên, với khai báo _L, một vùng dữ liệu trên file chương trình được
dùng để chứa chuỗi khai báo, nhưng khác với ở trên, chúng không có tên, không có gì
để nắm giữ, điều khiển chúng.
myName.Append(_L("Helen"));
Trong trường hợp này, hệ thống sẽ tạo một pointer descriptor là TPtrC trên stack
(chúng ta sẽ nghiên cứu TPtr sau) tạm thời đảm nhận việc kiểm soát và xử lý chuỗi dữ
liệu này. . Ví dụ, một khi ứng dụng lỗi, để test xem có phải hàm này gây ra hay
không, trên UIQ chúng ta có thể làm theo cách sau: ngay sau hàm nghi ngờ, chúng ta
đặt hàm sau:
User::InfoPrint(_L("Pass"));
Nếu dòng chữ "Pass" hiện lên màn hình thì rõ ràng lỗi chắc chắn nằm sau hàm này.
Cách viết này dễ hơn rất nhiều so với:
_LIT(KPass, "Pass");
User::InfoPrint(KPass));
_S macro gần giống với _L, tuy nhiên nó không yêu cầu tạo đối tượng tạm TPtr mà
sẽ cho phép sử dụng chuỗi trực tiếp như trên C.
=> Hiện nay, Symbian đã đề xuất bỏ kiểu khai báo này do tốn thêm stack cho đối
tượng tạm TPtr và chi phí khởi tạo nó. Tuy nhiên nhờ ưu điểm là giảm code lại khỏi
phải đặt tên nên chúng vẫn thường được dùng với mục đích test. _LIT cần ít bộ nhớ
hơn nếu xâu được sử dụng nhiều hơn 1 lần và Symbian khuyến khích sử dụng _LIT
trong việc tạo các ứng dụng sử dụng hiệu quả vùng nhớ.
* Buffer descriptor:
Đây là descriptor mà chuỗi dữ liệu chứa trong đối tượng descriptor. Chúng được chia
làm 2 loại dữ trên vùng nhớ lưu trữ: stack và heap.
- Stack buffer descriptor: Chúng có thể là descriptor hằng hoặc động. Chúng kế
thừa từ TBufCBase và TBufBase gồm TBufC<n> và TBuf<n>. TBufC phục vụ cho
các descriptor hằng, còn TBuf cho descriptor động, n ở đây chính là khai báo cho
chiều dài tối đa của chuỗi. Chúng được so sánh với char [] trên C. Ví dụ:
Mã:
_LIT(KHello, "Hello World!");
TBufC<12> aHelloBufC(KHello);
TBuf<15> aHelloBuf(KHello);
Lưu ý: Do kích thước stack cấp cho một ứng dụng là rất nhỏ nên chuỗi cấp trên stack
cũng chỉ nên cấp cho các chuỗi nhỏ, thường dưới 128 byte, nếu lớn hơn nên cấp trên
heap.
- Heap buffer descriptor: Loại descriptor này ra đời để phục vụ cho các descriptor
có kích thước lớn, không thể lưu trên stack được, và đặc biệt hữu dụng trong các
trường hợp không biết rõ kích thước tại thời điểm biên dịch. Nó được dùng chủ yếu
cho mục đích lưu trữ dữ liệu trong các thao tác xử lý file hay trên các kênh truyền
thông: hồng ngoại hay bluetooth. Đại diện cho descriptor này là HBufC (HBufC16
hay HBufC8), nhưng thường được sử dụng thông qua con trỏ, HBufC*. Bạn có thể
dùng hàm NewL() để tạo các đối tượng descriptor, lưu ý là sau khi dùng xong phải
hủy chúng đi vì chúng được cấp trên heap. Ví dụ:
Mã:
_LIT(KHello, "Hello World!");
HBufC* iHelloBufC = HBufC::NewL(20);
*iHelloBufC = KHello; //Copy nội dung KHello vào iHellBufC
Kí tự ‘C’ xác định descriptor là hằng và không thể chỉnh sửa được. Nếu bạn cần
chỉnh sửa descriptor này bạn có thể sử dụng hàm Des(), hàm này khởi tạo một con trỏ
trỏ tới descriptor này, là con trỏ dạng TPtr và có thể chỉnh sửa được dữ liệu. Ví dụ:
Mã:
_LIT(KMessage, "Hello");
HBufC* aMessage = HBufC8::NewL(KMaxItemLength);
aMessage->Des().Append(KMessage);
Để có thể thao tác với xâu có kích thước lớn hơn, bạn có thể sử dụng ReAllocL() để
mở rộng kích thước của Desciptor nhưng khi đó có thể xảy ra lỗi tràn hay gây ra một
panic.
Mã:
aMessage = aMessage->ReAllocL(25);
Bình thường các descriptor cấp phát trên vùng heap có thể khởi tạo bằng New() hoặc
NewLC(). Nhưng nếu descriptor này đã tồn tại, thì có thể dùng các phương thức
Alloc(), AllocL(), AllocLC() để tạo descriptor mới.
Mã:
TBuf<KMaxLength> temp;
temp.Append(_L("Hello Mum"));
HBufC* hPtr;
hPtr = temp.AllocL();
* Pointer descriptor: Đây là con trỏ trỏ tới Descriptor khác. Chúng bao gồm 2 loại
hằng và động được so sánh với const char* và char*, được thể hiện qua 2 lớp TPtrC
và TPtr. Ví dụ:
Mã:
_LIT(KHello, "Hello World!");
TPtrC aHelloBufC(KHello);
TPtr aHelloBuf(KHello);
- TPtrC là kiểu hằng, kí tự ‘C’ trong TPtrC là viết tắt của từ Constant, có nghĩa là
không thể thay đổi. Con trỏ này có hai tham số là độ dài và địa chỉ:
- Trong khi đó TPtr có thêm tham số xác định độ dài tối đa Descriptor cho phép
chỉnh sửa (Max length)
Mã:
TBufC<KMaxLength> descriptorOne;
descriptorOne = _L("One");
TBufC<KMaxLength> descriptorTwo;
descriptorTwo = _L("Two");
TInt result;
result = descriptorOne.Compare(descriptorTwo);
Biến result có giá trị -5 nhưng hiếm khi ta quan tâm tới giá trị này mà ta chỉ quan tâm
tới dấu của giá trị trả về của hàm so sánh.
- Find(), FindC() và FindF(): Tìm kiếm xâu con. Giá trị trả về là một số nguyên xác
định offset tới thể hiện đầu tiên của xâu tìm được. Nếu không tìm thấy, giá trị
KErrNotFound được trả về. Các hàm này luôn tìm từ vị trí bắt đầu của xâu, để tìm từ
một vị trí khác bạn sử dụng thêm các hàm Left(), Mid(), Right().
- Left(TInt aLength): Trả về một TPtrC tới phần tận cùng bên trái của descriptor.
- Right(TInt aLength): Trả về một TPtrC tới phần tận cùng bên phải của descriptor.
- Mid(TInt aPos): Trả về một TPtrC tới một vùng xác định của xâu, bắt đầu từ vị trí
aPos và kết thúc ở cuối xâu.
- Mid(TInt aPos, TInt aLength): Trả về một TPtrC tới một vùng xác định của xâu, bắt
đầu từ vị trí aPos với độ dài xác định bởi aLength.
- Length(): Trả về số kí tự trong xâu.
- Size(): Trả về số byte của Descriptor.
- Locate(), LocateC() và LocateF(): Tương tự như phương thức Find() nhưng để tìm
kí tự đơn chứ không phải tìm một xâu. Vị trí kí tự đầu tiên chính là vị trí đầu của xâu.
Ví dụ sau trả về giá trị 1 trong biến result:
Mã
TBufC<KMaxLength>descriptorOne;
descriptorOne = _L("One");
TChar aChar;
aChar = 'n';
result = descriptorOne.Locate(aChar);
- LocateReverse(): Hàm này tương tự như Locate() nhưng tìm từ vị trí cuối xâu. Nếu
không tìm thấy thì giá trị KErrNotFound được trả về.
+ Toán tử !=, <, >, <=, >=, == và []: Giúp đơn giản trong so sánh xâu. Các toán tử này
được định nghĩa trong TDesC. Trong TBufC đưa ra toán tử =, mặc dù đây là lớp
hằng.
Mã
TBufC<5> descriptorOne;
descriptorOne = _L("One");
TBufC<5> descriptorTwo;
descriptorTwo = _L("Two");
if (descriptorOne <= descriptorTwo)
descriptorTwo = descriptorOne;
Nếu độ dài của descriptor mới và dữ liệu hiện tại của aDescriptor mà lớn hơn
KMaxLength, ứng dụng sẽ sinh ra USER 11 PANIC.
- Capitalize(), UpperCase() và LowerCase(): Tên của hàm đã thể hiện rõ chức năng
của các hàm này.
- Copy(): Hàm này copy từ descriptor nguồn tới descriptor đích, và thay thế hết các
dữ liệu đã tồn tại.
- Delete(): Xóa các vùng con xác định của xâu. Ví dụ sau khi thực hiện lệnh sau thì
aDescriptor chứa “Hello Peace”:
Mã
TBuf<20> aDescriptor;
aDescriptor.Append(_L("Hello World Peace"));
TChar aChar = 'W';
TInt aPos = aDescriptor.Locate(aChar);
TInt aLength = 6;
aDescriptor.Delete(aPos, aLength);
- Fill(): Hàm này điền đầy descriptor bằng một kí tự nào đó hay là kí tự có mã 0.
- Format(): Sao chép dữ liệu đã được định dạng vào descriptor. Chi tiết xem thêm tài
liệu SDK.
Mã:
aDescriptor.Format(_L("%S\t%S"), aDes1, aDes2);
- Num(TInt aNum): Chuyển số nguyên sang xâu và đưa vào descriptor. Nếu
descriptor đã có dữ liệu thì nó thay thế dữ liệu đã tồn tại.
Mỗi vùng màn hình chính là đối tượng CCoeControl, được xem như control mà bạn
có thể vẽ nội dung bất kỳ lên vùng màn hình mà nó chiếm. Phương thức Draw() kế
thừa từ CCoeControl cho phép truy cập tới vùng màn hình được định nghĩa bởi
TRect. Tất cả các lớp kế thừa từ CCoeControl đều có hàm Draw(), và bạn chồng
hàm này để thực hiện các thao tác vẽ.
Một control cần một cửa sổ để vẽ, và vùng cửa sổ này được thiết lập trong hàm
ConstructL(). Và để sử dụng CCoeControl bạn cũng cần include tệp “coecontrol.h”
và thêm link “cone.lib”. Ví dụ sau tạo một lớp kế thừa từ CCoeControl và thực hiện
vẽ lên vùng màn hình của nó:
Mã:
class CStartContainer : public CCoeControl
{
public:
CStartContainer();
CStartContainer();
private:
void ConstructL(const TRect& aRect);
// from CCoeControl
void Draw(const TRect& aRect) const;
};
Hàm ConstructL() cần truyền kích thước cửa sổ, control, vùng mà ứng dụng cần truy
cập. Bạn có thể sử dụng hàm ClientRect() để lấy kích thước vùng client của ứng
dụng, và hàm ApplicationRect() để lấy kích thước của vùng ứng dụng.
Bạn truyền AppUi()->ApplicationRect() trong hàm ConstructL() để truy cập tới
toàn bộ vùng màn hình. Dĩ nhiên bạn có thể tùy biến vùng kích thước này:
Mã:
Để vẽ lên màn hình, một ngữ cảnh đồ họa (Graphics Context - GC) được lấy ra. Và
tất cả việc vẽ đều thực hiện trong ngữ cảnh đồ họa này, CWindowGc.
Mã
void CStartContainer::Draw(const TRect& aRect) const
{
CWindowGc& gc = SystemGc();
gc.Clear(aRect);
...
}
- Hàm SystemGc() trả về ngữ cảnh đồ họa chuẩn cho biến gc. Và vùng control phải
được xóa trước khi bạn thực hiện thao tác vẽ trên nó.
- Hàm Draw(), nó là hằng chứ không phải là leave, vì thế bạn không thể tạo các phần
tử động trong hàm Draw() và bạn cũng không thể sửa trạng thái bất kỳ dữ liệu nào
trong hàm này. Sau khi bạn thay đổi trạng thái bạn muốn cập nhật lại vùng vẽ bạn nên
gọi hàm DrawNow(), hàm này sẽ tự động gọi hàm Draw() của chính nó.
Để thực hiện vẽ hình theo ý của bạn, bạn phải thiết lập các thông số bút vẽ trước khi
thực hiện vẽ. Dạng và kích thước của bút vẽ cũng có thể thay đổi và được sử dụng để
vẽ các hình. Đoạn mã sau thiết lập kiểu bút vẽ là dash và kích thước tới giá trị aSize:
Mã
gc.SetPenStyle(CGraphicsContext::EDashedPen);
gc.SetPenSize(aSize);
Bút vẽ sử dụng để vẽ các đường, còn chổi vẽ để tô các vùng. Kiểu liệt kê
TBrushStyle được sử dụng trong hàm SetBrushStyle. Màu của chổi vẽ được thiết
lập thông qua hàm SetBrushColor().
Các phương thức vẽ khác như DrawEllipse() và DrawPolygon(), chúng sử dụng chổi
vẽ để tô vùng và bút vẽ để vẽ đường biên.
Tham số vị trí là tương đối với Control vì vậy TPoint(0,0) là góc trên bên trái vùng
màn hình của control. Vị trí này có thể khác nhau trên các thiết bị khác nhau vì vậy
bạn phải rất cẩn thận. Nhưng thật may mắn có một số hàm như hàm
CGraphicsContext::ECenter giúp can lề giữa đoạn văn bản. Một vài hàm khác giúp
bạn thay đổi font và màu của văn bản.
gc.SetPenColor(KRgbRed);
KRgbBlack
KRgbDarkGray
KRgbDarkRed
KRgbDarkGreen
KRgbDarkYellow
KRgbDarkBlue
KRgbDarkMagenta
KRgbDarkCyan
KRgbRed
KRgbGreen
KRgbYellow
KRgbBlue
KRgbMagenta
KRgbCyan
KRgbGray
KRgbWhite
gc.UseFont(iCoeEnv->NormalFont());
Nhớ rằng một khi bạn hoàn thành việc sử dụng font, bạn phải gọi hàm:
gc.DiscardFont();
Việc bố trí các phần tử trên màn hình với các font khác nhau là công việc khá phức
tạp. Một lựa chọn đó là không sử dụng cách vẽ trực tiếp mà sử dụng lớp CRichText.
Lớp này giúp bạn dễ dàng định dạng chữ và chèn ảnh.
_LIT(KFontName, "Verdana");
CFont* iUseFont = NULL;
TFontSpec myFontSpec(KFontName, 18);
TFontStyle myFontStyle(EPostureUpright, EStrokeWeightNormal,
EPrintPosSubscript);
myFontSpec.iFontStyle = myFontStyle;
iCoeEnv->ScreenDevice()-
>GetNearestFontInPixels(iUseFont,myFontSpec);
iFbsGc->UseFont(iUseFont);
_LIT(KTextExample, "\x43h\xE0o m\x1EEBng \x62\x1EA1n th\x61m
\x64i\x1EC5n \x111\xE0n MyShop\x34Vn"); // Xâu "Chào mừng bạn
tham diễn đàn MyShop4Vn"
iFbsGc->DrawText(KTextExample, TPoint(5,80));
iFbsGc->DiscardFont();
iCoeEnv->ScreenDevice()->ReleaseFont(iUseFont);
Chú ý:
- Để có thể sử dụng font Verdana và hiển thị được tiếng việt bạn phải copy hai tệp
verdana.ttf và FREETYPE.DLL vào thư mục c\system\fonts.
- Hàm UseFont khá chậm nên bạn nên dùng nó ngay trong hàm khởi tạo. Khi sử dụng
hàm này, thì phải dùng thêm hàm DiscardFont() để loại bỏ font khỏi ngữ cảnh đồ
hoạ. Nếu bạn sử dụng nhiều font vẽ cùng lúc thì tốt nhất bạn nên sử dụng hàm
UseFontNoDuplicate (iFbsGc->UseFontNoDuplicate((const CFbsBitGcFont *)
iUseFont);), khi sử dụng hàm này bạn ko cần sử dụng hàm DiscardFont nữa.
Đầu tiên bạn phải khai báo các biến cần dùng (nên là biến thành viên của lớp AppUi):
Khi khởi động bạn load font này lên bằng đoạn mã:
TFileName fntFilename(KFontFile);
TInt nRes = CEikonEnv::Static()->ScreenDevice()-
>AddFile(fntFilename, iFontID);
if (nRes==KErrNone)
{
// Load được font
// Tạo biến CFont để sử dụng font này
TFontSpec myFontSpec(KFontName, 12);
TFontStyle myFontStyle(EPostureUpright, EStrokeWeightNormal,
EPrintPosNormal);
myFontSpec.iFontStyle = myFontStyle;
iCoeEnv->ScreenDevice()-
>GetNearestFontInPixels(iUseFont,myFontSpec);
}
else
{
// Không load được tệp font
// ...
}
Bây giờ bạn có thể sử dụng font chữ này vẽ bình thường. Khi kết thúc ứng dụng, bạn
phải unload font này khỏi hệ thống bằng đoạn mã:
if (iUseFont)
iCoeEnv->ScreenDevice()->ReleaseFont(iUseFont);
if (iFontID!=0)
CEikonEnv::Static()->ScreenDevice()->RemoveFile(iFontID);
* Chú ý:
- Bạn có thể dùng tiện ích "Easy GDR Creator" để tạo font gdr.
- Bạn có thể dùng tiện ích "KVT Symbian Font Converter" để chuyển từ font *.ttf
sang *.gdr
- Các tiện ích này bạn tìm trên mạng, rất nhiều và rất dễ tìm
- Hiển thị bằng font *.gdr thực sự không được đẹp lắm
- Bạn có thể dùng đoạn mã trên để load và hiển thị font *.ttf. Tôi đã thử trên dòng
Symbian S60 3rd, chạy rất okie.
iFontNum = iCoeEnv->ScreenDevice()->NumTypefaces();
if (iFontNum>0)
iArrFonts = new(ELeave)CArrayFixFlat<MyFontName>(iFontNum);
if (iArrFonts)
{
MyFontName myFontName;
TTypefaceSupport myTypefaceSupport;
for(TInt i=0; i<iFontNum; i++)
{
iCoeEnv->ScreenDevice()-
>TypefaceSupport(myTypefaceSupport, i);
myFontName = myTypefaceSupport.iTypeface.iName.Des();
iArrFonts->AppendL(myFontName);
}
}
Toàn bộ font tìm được sẽ lưu vào mảng iArrFonts để tiện cho việc sử dụng. Sau khi
liệt kê bạn có thể hiển thị bằng cách vẽ lên màn hình hoặc hiển thị bằng hộp thoại
Information.
Xử lý phím nhấn
Có nhiều cách khác nhau để tương tác với các ứng dụng trên điện thoại di động. Hình
sau thể hiện một số cách mà người dùng có thể tương tác với điện thoại di động:
Phương thức vào thông dụng là thông qua các phím trên điện thoại, bao gồm ba vùng
phím:
- Keypad: Các phím số từ 0 đến 9, phím *, #
- Soft keys
- Navigation key: Phím định hướng, bao gồm phím lên, xuống, trái, phải và phím OK
Ngoài ra người dùng có nhiều cách khác để tương tác với điện thoại. Vì điện thoại
được thiết kế với mục đích chính là đàm thoại, vì thế giọng nói là phương thức vào
chính. Gần đây các nhà phát triển quan tâm tới các ứng dụng khởi động bằng giọng
nói và chắc chắn các ứng dụng loại này sẽ trở thành thông dụng trong tương lai không
xa. Ngoài ra, sử dụng hồng ngoại cũng như Bluetooth cũng cho phép vào ra dữ liệu
với điện thoại. Ở đây chúng ta quan tâm tới phương thức vào thông dụng nhất, đó là
sử dụng phím.
Trong lớp CoeControl có cài đặt một phương thức ảo OfferKeyEventL() để điều
khiển sự kiện nhấn phím, nó được framework gọi mỗi khi sự kiện nhấn phím xảy ra.
Vì vậy để bắt sự kiện phím nhấn, bạn phải kế thừa từ lớp CoeControl và định nghĩa
phương thức OfferKeyEventL().
#include <coecntrl.h>
class CKeyPressContainer : public CCoeControl,
MCoeControlObserver
{
...
TKeyResponse OfferKeyEventL(const TKeyEvent& aKeyEvent,
TEventCode aType);
...
}
Khi sự kiện phím xảy ra, phương thức OfferKeyEventL() của tất cả các điều khiển
trong stack được gọi. Khi phương thức này được gọi, nó có thể xử lý sự kiện hoặc bỏ
qua nó thông qua giá trị trả về của hàm này: Trả về TKeyResponse:
EKeyWasNotConsumed nếu sự kiện bỏ qua và trả về EKeyWasConsumed nếu sự
kiện được xử lý.
Khi sự kiện phím xảy ra, đầu tiên bạn phải kiểm tra xem đó là sự kiện phím nào: phím
nhấn (Key down), phím nhả (Key up) hay sự kiện nhấn phím (Key press). Nếu bạn
chỉ quan tâm tới sự kiện phím nhấn, bạn có thể sử dụng đoạn mã:
TKeyResponse CKeyPressContainer::OfferKeyEventL(const
TKeyEvent& aKeyEvent, TEventCode aType)
{
if (aType != EEventKey)
return EKeyWasNotConsumed;
else
{
if (aKeyEvent.iCode == '5')
{
...
return EKeyWasConsumed;
}
}
}
Tiếp theo bạn phải kiểm tra các tham số của sự kiện phím trả về thông qua tham số có
kiểu TKeyEvent, đây là cấu trúc gồm 4 trường:
- iCode (định nghĩa trong TKeyCode): Mã của phím nhấn trong sự kiện Key Press.
- iModifiers (định nghĩa trong TEventModifier): Trạng thái phím sửa và thiết bị trỏ
- iScanCode (định nghĩa trong TStdScanCode): Mã scan của phím, sử dụng trong
các sự kiện Key Down và Key Up.
Bạn cũng có thể sinh ra một sự kiện phím bằng cách gán các trường trong cấu trúc
TKeyEvent, sau đó gọi hàm OfferKeyEventL() để giả sự kiện:
TKeyEvent myEvent;
myEvent.iCode = EEventKeyDown;
myEvent.iModifiers = EAllModifiers;
myEvent.iRepeats = 0;
myEvent.iScanCode = EAllModifiers;
OfferKeyEventL(myEvent, EEventKey);
Phương thức HandleCommandL() được sử dụng để điều khiển menu chọn và các
lệnh khác được định nghĩa trong tệp resource. Nó là hàm ảo định nghĩa trong
CEikAppUi (hoặc CAknAppUi trong Symbian S60 3rd) và được gọi bởi phương
thức ProcessCommandL() trong Framework. Nơi thường được sử dụng để cài đặt
phương thức này là trong lớp AppUi (kế thừa từ CEikAppUi hoặc CAknAppUi).
Còn nhiều phần quan trọng khác tôi không thể nói hết ở đây, bạn xem thêm cuốn
"Wiley S60 Programming - A Tutorial Guide (Apr - 2007).pdf" mà tôi giới thiệu
ngay ở bài viết đầu tiên.
Một số lỗi liên quan đến lập trình và cài đặt trên Symbian
Khi lập trình hay cài đặt phần mềm đôi khi bạn gặp những rắc rối, mà bạn không biết
tại sao. Sẽ mất thời gian để bạn phải tìm hiểu nguyên nhân, hay serch trên mạng để
tìm cách giải quyết. Khi lập trình và cài đặt các phần mềm trên Symbian tôi cũng gặp
nhiều rắc rối, xin đưa ra để nếu các bạn có gặp phải sẽ không phải mất nhiều thời gian
để giải quyết.
* Một số lỗi khi cài đặt phần mềm (đặc biệt trên Symbian S60
3rd)
- Certificate may not yet be valid , is expired or phone's date setting may be
incorrect: Lỗi này thường gặp khi bạn mới format máy khi gặp lỗi này các bạn chỉ
cần chỉnh lại ngày giờ cho chính xác với thời điểm hiện tại.
- not support: Lỗi nó là phần mềm bạn cài ko phải là của s60v3 mà của s60v2 hay là
phần mềm của của các loại symbian khác (uiq, s90..), nhưng cũng có trường hợp do
các files có tên quá dài chấm nhiều chấm quá nên máy ko hiểu và báo lỗi, nếu báo lỗi
này thì trước tiên các bạn thử đổi tên nó ngắn gọn lại nhé và cài thử lại xem có được
ko.
+ File Corupted: Là files đó đã bị lỗi bạn nên tải lại files khác.
+ error update: Symbian OS 9.1 ko cho bạn update vesion của soft đã CR, bạn nên
xóa bỏ soft trong máy và cài lại sẽ ok, muốn update phải là ứng dụng ko CR (đăng ký
= chìa khóa).
+ Certificate error contact the application supplier: Lỗi này báo là pm này chưa
được signsis (đăng ký).
+ Certificate exprired: Lỗi "chứng chỉ hết hạn" này xảy ra đối với 1 số dòng máy
mới & firmware mới, bạn có thể đổi ngày giở lui lại khoảng 1 vài tháng, nhưng cũng
có 1 số soft ko cài được -> chỉ có cách là đợi vesion mới của soft được CR mà cài.
Đối với dòng E (E65, E61 ....): thì bạn vào app manager -> software intall -> chọn
all la ok.