Nokia官方培训(Symbian 4300)笔记

Forum Nokia's Mission
    Forum Nokia Creates lucrative business opportunities for mobile developers globally.

The Nokia Vision
    Life Goes Mobile

Forum Nokia
    针对个人,有超过2百万的注册用户

Forum Nokia Pro
    针对企业,超过400家注册

Forum Nokia Champion
    针对在Forum Nokia注册的个人用户,而该人可能是注册为Forum Nokia Pro公司的员工

The Platform Approach
    S40:只支持Java应用

    S60:支持Java和C++应用,目前最火热的平台

    S80:企业级应用,支持的手机不多

Get It Right From the Start
    可以从网上获取所有开发资源,如SDK,工具,文档等

    技术支持:Keep Your Project Moving Forward

    技术咨询:Have Expert Guidance Every Step of the Way

    培训:Stay on the Cutting Edge

    程序测试:Get Your Applications Tested and SIgned

    讨论板:Learn From Experts

    测试机型: Develop and Test on the Latest Nokia Devices

 

读后感
可以了解一些基本常识,呵呵,Forum Nokia的广告篇~

Get It Right From the Start中很多都是要收费的,而且还很贵... 


Symbian Ownership

    Symbian是一个software licensing公司,由Nokia,索爱,松下,西门子,三星等公司控股,其中nokia股份最高,占到近50%。

Symbian Licensees

    Symbian本身不生产手机设备,它只是将license提供给其他手机设备生产厂商,除控股公司外,其他还有如摩托罗拉,联想,三菱,夏普,明基,富士通等等。

Symbian Devices:

   2003年的Nokia 6600;2004年的Nokia 6630;2005年的Nokia N70,2006年的Nokia 3250等都是经典手机。

Symbian OS (EPOC)

    Symbian是专为移动设备设计的操作系统,具有small memory footprint和low power consumption的特点。

Symbian OS Layers

    自上而下一共有6层,每层以及其特点如下所述:

  • UI Layer:用户界面
  • Application Engine Layer:提供对应用程序所需要的数据的访问
  • System Layer:提供系统所有核心功能
  • Kernel Layer:提供内核级服务,如进程和线程
  • Hardware Adaption Layer:硬件接口层,软件的最下层,与硬件相关。
  • Hardware:物理硬件

UI Design Platforms

  • S60:最通用的用户界面层,为市面上大多数手机所采用,为单手操作而设计。
  • UIQ:为UIQ Technology所拥有,采用此界面的有索爱,摩托罗拉,明基,Arima等。
  • S80:为商务用途所设计,Nokia 9系列手机所采用。
  • Nokia 7710:曾被称作S90,唯一一款。
  • FOMA:专为日本的NTT DoCoMo的3G FOMA网络设计,采用此界面的有富士通和三菱。
  • 下图为S60平台的界面示意图: 

 Symbian platform Evolution

Development Requirements

  • SDK:本教程的实验采用S60第三版的SDK
  • 开发环境:采用Carbide.C++ Express

上述两种工具都可以直接从forum.nokia网站上下载得到,需要先注册成为Forum.nokia的会员。

 S60 3rd Edition SDK

    关于该SDK的一些重要信息:

  • 文档:<EPOCROOT>/S60Doc
  • 示例程序:<EPOCROOT>/S60Ex
  • 模拟器:<EPOCROOT>/EPOC32/release/winscw/udeb/EPOC.exe
  •  API头文件:<EPOCROOT>/EPOC32/include
  • 手机二进制库文件:<EPOCROOT>/EPOC32/release/armv5
  • 模拟器二进制库文件: <EPOCROOT>/EPOC32/release/winscw/udeb

Symbian Projects 

    一个Symbian工程产生一个二进制文件:或者是exe,或者是dll。

    包含以下内容:

  • 头文件:这个不用解释吧。
  • 源代码文件:由编译器生成目标代码,然后通过链接器生成exe或者dll。
  • 资源文件:定义资源的文本文件,生成二进制资源文件。
  • 本地化文件:根据不同语言定义的字符串。
  • 图像文件:同时支持位图(bitmaps)和向量图(scalable vector)。
  • 配置文件:如MMP文件。

 

读后感 

常识性的东西,但是既然做了symbian开发,这些东西都不知道肯定会被人笑话的。

这里介绍的很系统,思路很清楚,我以前也是这儿知道一点,那儿知道一点。

现在应该算是都串起来了,呵呵 

Carbide.C++ IDE

    基于Eclipse的全功能IDE,与CodeWarrior保持一定的兼容性,分三个版本:

  • Express:入门级开发工具,可免费从Forum.nokia网站获得。
  • Developer:提供更多的工具,以及支持On-Target Debugging。
  • Proffesional:为开发Symbian设备以及高性能应用程序开发者提供。

Carbide.c++基本概念 

  • workbench:桌面开发环境,可以认为是Carbide.c++本身。
  • workspace:Symbian工程的存储目录,是文件系统中的一个文件夹,如c:/carbide。
  • Perspectives:在workbench中的view的位置及分布情况,有Debug,Symbian,C++等perspective切换。
  • views:UI组件,提供浏览信息的一种方式:如工程,调试信息,源代码等。
  • editors:编辑器,用于编辑文件。 

 Creating a New Workspace

    workspace是文件系统中的一个目录,保存了所有Carbide.C++创建的项目、文件夹、以及文件。

    File->Switch Workspace... , 名字中间不能有空格,而且必须和SDK在同一个盘,最好都在C盘。

    File -> New -> C++ Application For S60 Project ,然后跟着向导一步一步走就可以了。

Importing an S60 Application

   File -> Import... ,一般选择"Symbian MMP File",“Symbian Bld.inf“可以import多个项目。

Editing Project Properties

    Porject -> Properties , 或者在Build Configuration view里面选择右上角那个"i"按钮,即可弹出project properties dialog。

    Carbide.c++不需要MMP文件,它为你保存所有的设置信息,提供project properties dialog进行修改。

Adding a new link library

    这是Symbian开发过程中最常见的操作之一,可以在project properties dialog中选择WINSCW C/C++ Linker 的libraries进行添加。

Building a Project

    选中C/C++ Projects中的当前项目,然后Project -> Build All

Running On The Emulator

    选中C/C++ Projects中的当前项目,然后在Run菜单中选择Run As -> Run Symbian OS Application

 Debugging with Carbide

    当采用Debug而非Run的时候,Carbide会自动切换到调试perspective。

设置断点:双击代码边上的空白栏(marker bar)

在Variables中可以直接看到描述符中所包含的字符串,这个比较有用。

Building for Target

    最后是放到真机上测试。在build configurations view中选择GCCE,然后Project -> Build All就可以了。

    如果要对sis进行签名,可以在project properties dialog的Create Sis中加入cer和key文件的地址和默认密码,这样生成过程就会自动帮你把最后生成的sis签名了。

 

读后感

Carbide.c++的使用操作比我想象中的要简单得多。

开发环境配置也很方便,只要装一个SDK和一个Carbide,比较适合新手。

由于Carbide是Nokia本身提供的,所以个方面的支持应该都是最好的,比如可以直接看到描述符中的内容。

我是不是该放弃VS.NET 2003而改用Carbide.c++呢 ?做完里面所有的lab,如果感觉还这么好,就换,呵呵!

Basic Types

 在Symbian中,很多C++基本类型都被重新定义了,最好使用Symbian的,理由如下:

  • 所有Symbian API都是用的Symbianc重定义的
  • 将来Symbian OS由32位转为64位时,支持性更好
  • 这本身就是Symbian C++ Coding Standards所要求的
 Integers

    typedef signed int TInt;  C++中的signed int,32位,基本用法类似。

    typedef unsigned int TUint;  一般用于计数器(Counter)或者标记(Flags)。

其他Int类型:TInt64, TInt32, TInt16,TInt8; 同时有一份TUint的版本。

Text

 text类型在Symbian编程中基本不用,而一般采用描述符(descriptor)。TText默认是16位的。

Boolean 

    typedef int TBool; 有两个枚举值:ETrue和EFalse。TBool变量最好不要直接和ETure和EFalse比较。如下:

TBool flag = ETrue;
if (flag)  // if (!flag)

    flag = EFalse; 
}

 Floating Point

    对浮点数的支持视处理器而定,如果没有FPU,效率非常低,所以最好是不要用浮点数。 如果一定要用,尽量转化为整数操作。

typedef float TReal32;  typedef double TReal64; typedef double TReal;

TAny

    typedef void TAny;

TAny一般只用作指针,其他情况下用void比较好。

TAny* MyFunction();     void MyOtherFn();

TAny* 在很多Symbian API中都用到了,如:

static TUint8* Copy( TAny* aTrg, const TAny* aSrc, TInt aLength);

Enumerations

enum TState {EOff, Eon, EInit};

Enumeration类型应该以T开头,而枚举值应该以E开头。

TState  state = GetState();
if (state == EOn)
{
   //Do something here

Coding Conventions

    T类:只包含值,而不包含指针以及外部的资源,在栈上分配空间。

TVersion osVersion = User::Version();

    C类:所有需要分配内存的类都必须从CBase继承并且以C开头。

class CExample : public CBase
{
private:
   CDesCArrayFlat* iArray;
}    

CExample* example = new (ELeave) CExample;

    R类:包含指向某个资源的handler。

RTimer timer;
timer.CreateLocal();

    M类:定义一个接口,一般只包含纯虚函数,不包含成员数据,减少类之间的依赖,用来接受回调消息。

class MEikStatusPaneObserver
{
   public:
        virtual void HandleStatusPaneSizeChange() = 0;
}

任何实现MEikStatusPaneObserver接口的类都必须实现HandleStatusPaneSizeChange()函数。

Variable Naming Conventions

  •     成员变量以“i”开头
  •     参数以“a”开头
  •     动态变量随便,以小写字母开头
  •     常量以“K”开头
  •     尽量不要使用全局变量,不能使用全局静态变量。

Functions

  •     函数以大写字母开头,如AddFileNameL();
  •     以D结尾表示deletion of an object
  •     以L结尾表示函数可能leave
  •     以C结尾表示一个item被放到cleanup stack

Casting

    Casting用于在类(classes)和类型(types)之间作转化,Symbian中仍然可以使用C中语法。

    dynamic_cast:不支持,Symbian中没有RTTI。

    static_cast:把一个基类转化为一个继承类。 

TInt intValue = 0xff;
TUint8 byteValue = static_cast<TUint8>(intValue);

    reinterpret_cast:把一个指针类型转化为另外一个指针类型,如integer转化为point类型或者相反。

TUint32 fourBytes = 0;
TUint8* bytePtr = reinterpret_cast<TUint8*> (&fourBytes);
bytePtr++;
*bytePtr = 0xFF; 

    const_cast:移除一个类的const属性。

 

读后感

Symbian编程中基础的基础,属于每天都会碰到的东西。

最后部分的casting有点难懂,其他的看习惯了也就好了。

入乡随俗,既然选择了Symbian,就用Symbian的惯例去写程序吧。

Why Memory Management

    Symbian OS本身就是为内存和资源受限的设备开发的,应用程序运行过程中很可能碰到内存用光,或者硬件资源不可用的情况。而这种exceptions 是通过修改程序无法解决的,所以遵守以下几条:

  • 尽量不要使用不必要的RAM
  • 尽早释放资源,如文件server等
  • 当你每次申请内存时,都须准备处理out-of-memory错误
  • 当 out-of-memory错误发生时,返回到一个stable的状态,并释放所有期间申请到的资源

 Stack and Heap

    Stack:默认大小8kb,自动删除,如 TInt i = 0;

    Heap :至少0.5Mb,由程序员手动删除,如 CMyObj* obj = new (ELeave) CMyObj;

Leaves

    首先介绍Conventional C++ Memory Management,在Symbian看来,这是非常低效率的。

  • NULL Pointer Checking  if ((myObj = new CMyObj( ) ) == NULL) { //Error Handling }
  • ANSI C++ Exeption Handling   try { //throw an Exception } catch (int e) { //Error Handling }

    在Symbian中推荐采用Leave,如果内存或者资源不能分配到,这个代码就会Leave,沿着Call Stack,直到操作系统或者在某个函数中被Handle掉。

    所有可能Leave的函数最好以L结尾,保证该函数的用户知道这个函数可能Leave。

    Leave的例子:

  • 动态内存分配: return new (ELeave) TUint8[1000];
  • 产生一个Leave:User::Leave(KErrNotFound);
  • 内存不足时Leave:User::LeaveNoMemory();
  • NULL的时候Leave:User::LeaveIfNull(aNotify);
  • 当发生错误时Leave:RFs fs; TInt err = fs.Connect(); User::LeaveIfError(err);

    处理Leave:

    操作系统有默认的处理Leave的方式:

  • 在程序启动过程中:直接关闭应用程序。
  • 应用程序启动后:显示一个错误消息。 

    开发者可以通过trap装置来处理Leave。TRAP(_r, _s)和TRAPD(_r, _s) ,其中:

  • _r:是一个TInt类型的leave code,默认值为TErrNone。
  • _s:一系列可能Leave的C++ Statements。

        TRAPD(err, DoFunctionL());
        if (err != KErrNone)
        { //Error Handling }
        else
        { //Everything is well }

  The Cleanup Stack

    cleanup stack用于存储在leave发生后需要deallocating的局部变量(指针)。即:当一个函数leave了,所有在cleanup stack上的对象会被全部删除掉。

    Cleanup Stack的使用方法:

CleanupStack::PushL(ptr) :当发生leave时所有内存都会被释放
CleanupClosePushL(handle):当发生leave时这个句柄(handler)会被关闭

CleanupStack::Pop(pointer):第一个元素出栈
CleanupStack::PopAndDestroy(pointer):第一个元素出栈并释放内存

    如果一个函数可能leave,检查一下两种情况:

  • 如果leave了,是否所有在堆(heap)上的元素都在cleanup stack中了
  • 如果没有leave,你是否自己恰当地将他cleanup了

 CMyClass* CMyClass::NewL(TInt aBufSize)
{
   CMyClass* self = new (ELeave) CMyClass;
   CleanupStack::PushL(self);
   self->ConstructL(aBufSize);
   CleanupStack::Pop(self);
   return self;
}

    如果某个函数会在cleanup stack上留下一个对象,那么他必须以C结尾。

Two Phase Construction

    C++构造函数一定不能leave。所有内存和资源的分配应该在第二阶段构造函数ConstructL( ) 中完成。

编码指南,所有用户定义的C类必须:

  • 定义NewL和NewLC函数为public static
  • 定义ConstructL和C++ Constructor为private

Best Practise

     Construction的规则:

  • 默认的C++构造函数中不能含有可能leave的代码
  • 可能发生leave的函数必须在ConstructL中被调用
  • 如果基类也有ConstructL,必须首先调用,不要忘了explicit scoping

    Destruction的规则:

  • C类必须在析构函数中删除它自己所包含的对象
  • 在删除一个对象后,把它的指针设为NULL
  • 不要删除不是本类所拥有的对象
  • 在reallocation前首先删除对象,并且将其指针设为NULL

    Further Discussion:

  • Preserve Stack Memory:每个进程只有8K,以引用的方式传递参数,大的对象放在堆上
  • Preallocation vs last moment allocation:一般的原则是只在使用前分配资源并且在使用后马上释放。但是                     preallocation的好处是节约处理时间,并且在没有内存的情况下照常运行(资源已经分配到了)
  • where to put trap harness:最基本的情况是依靠GUI应用程序的框架。根据应用的不同,可以自定义粒度。
  • Error Code Returns vs. leaving functions:在执行某个处理前检测是否会出现问题,如下代码:

                                    User::LeaveIfError(fs.Connect()); 

Memory Leaks

    如果你的程序有内存泄露,在模拟器上关闭时会crash。尽早发现并解决你的内存泄露,因为你可以追查到你可能导致内存泄露的代码改动。如果实在找不到,可用下面方法:

    Heap Balance Checking:

  • _UHEAP_MARK
  • _UHEAP_MARKEND

    用上述这两个宏放在你要检查的代码的开头和结尾,如果发生panic,则说明这段代码中发生了内存泄露。可以嵌套使用。 

Panics 

    Panic是一个未经处理的exception,暗示着一个无法解决的错误。

一般程序有以下三类错误:

  • 程序错误:如引用一个超过数组范围的元素
  • 环境错误:内存、磁盘空间不够,或缺少其他资源等
  • 用户错误:输入错误数据
    可以使用trap和cleanup stack技术来解决环境和用户错误,但是对于第一类的程序错误,我们无法恢复,最好是使用User::Panic()函数,它带有两个参数,第一个是string,第二个是Tint。 

Introduction

    描述符(Descriptors)封装了字符串和二进制数据,用于替代C中的以NULL结尾的字符串。它的长度和数据都封装在了描述符中,Symbian API中用的都是描述符。如:

    TPtrC ptr (KHelloWorld);    CEikonEnv::Static()->InfoMsg(ptr);

 Main Types of Descriptors

    主要可以分为以下几类,其中带C的是不可修改的。 

  • Abstract:(TDes、TDesC),其他描述符的基类,不能实例化,一般用作函数的参数。
  • Literal:(TLitC,_LIT()),用于存储literal string,一般使用后者。
  • Buffer:(TBuf,TBufC),数据存储于栈上,大小在编译时确定。
  • Heap:(HBufC),数据存储于堆上,大小在运行时确定。
  • Pointer:(TPtr,TPtrC),引用存储于类之外的数据

Descriptor Modification

    描述符可以是可修改的和不可修改的,通常带C的都是不可修改的,不可修改的是可修改的基类。   

  • Moidfiable:提供了访问和修改数据的API,如TBuf
  • Non-Modifiable:数据只可以被访问,不可修改。但是通过Des()函数可返回一个可修改的指针

Descriptor Width

    在描述符类后加上8或者16影响了存储在描述符中的数据的宽度,默认是16位的,处理二进制或ASCII时采用8位。

  • 8位:(TDesC8),用于二进制数据或者ASCII字符串
  • 16位:(TDesC16),默认,Unicode

下面开始对上述5类描述符进行详细介绍,首先看一下类继承关系

 

 

                                            这里显示的是8位的,16位默认的类继承关系与此一致

Abstract Descriptors

    除Literal外的所有描述符的基类,提供了基本的接口和基础功能。他们本身不能实例化,一般用作函数参数。

TDesC:提供了比较、复制、搜索、提取部分字符串的函数。

TInt TDesCUtil::SumLengths(const TDesc& aDesC1, const TDesc& aDesC2)
{
   return aDesC1.Length() + aDesC2.Length();

TDes:继承自TDesC,添加了许多用于修改数据的函数。其最大长度是描述符被创建时确定的。

TInt TDesCUtil::AppendL(TDesc& aTarget, const TDesc& aDesC)
{
   TInt sumLen = aTarget.Length() + aDesC.Length();
   if (aTarget.MaxLength() < sumLen)
   {
      User::Leave(KErrOverflow);
   }
   aTarget.AppendL(aDesC);

 Literal Descriptors

    提供了一种将字符串放在只读存储空间中的机制(实际存放在程序的数据区,而不是真的在ROM中)。一般不采用TLitC而直接采用_LIT()宏。    _LIT(KHelloWorld, "Hello World!");

    通过()操作符可以得到 const TDesC&。 TInt length = KHelloWorld().Length();

    在函数参数为const TDesC&可以直接使用KHelloWorld。iLabel->SetTextL(KHelloWorld);

 Buffer Descriptors

    将数据作为本身的一部分存储在stack上,他们的最大长度是在编译时确定的。  

TBuf<16> helloWorld = KHelloWorld;
TInt len = KHelloWorld().Length();
helloWorld[len-1]='?';  

在内存中如下所示: 

TBufC的用法如下:

_LIT(KHelloWorld, "Hello World");
const TInt maxBuf = 32;
TBufC<maxBuf> buf;
TInt currentLen = buf.Length(); // == 0
buf = KHelloWorld;
currentLen = buf.Length(); // == 11
TText ch = buf[2]; // == 'l'

 TBuf的用法如下:

const TInt bufLen = 6;
TUInt8 objType = 1;
TUInt8 objId = 1;
TUInt8 xCoord = 128;
TUInt8 yCoord = 192;
....
TBuf8<bufLen> buf;
buf.Append(objType);
buf.Append(objId);
...
//we can now do something with the buffer such as writting it to a binary file or send via socket.

Pointer Descriptor

    用于引用存储在其他地方的数据,如:

const unsigned char KBuffer[ ] = {0x00, 0x33, 0x66, 0x99, 0xbb, 0xff};
TPtrC8 bufferPtr( KBuffer, sizeof(KBuffer));
iSocket.Write(bufferPtr, iStatus);

在内存中如下所示:

TPtr的用法:

 _LIT(KHelloWorld, "Hello World");
const TInt maxBuf = 32;
TBufC<maxBuf> buf;
buf = KHelloWorld;
TPtr ptr = buf.Des();
ptr[7] = 'a';  ptr[8] = 'l';  ptr[9] = 'e';  ptr[10] = 's';  
CEikonEnv::Static()->InfoMsg(ptr); // "Hello Wales"

 Heap Descriptors

     动态在堆(heap)上分配,通过HBufC的API,数据可以被set和reset,但是不能被修改。如:

HBufC* heapBuf = HBufC::NewL(KHelloWorld().Length());
*heapBuf = KHelloWorld();
delete heapBuf; 

     在内存中的情况如下图所示:

    HBufC通常在以下几种情况下使用: 

  •  在运行时从资源文件中加载字符串
  •  从用户界面中接收用户输入的字符串
  • 从应用程序引擎中接收字符串,如contacts database中的名字 

     对HBufC中的内容进行修改:

_LIT(KHello, "Hello!");
_LIT(KWorld, "World!"); 
HBufC* heapBuf = HBufC::NewL(KHello().Length());
*heapBuf = KHello;    //buf holds "Hello!"

heapBuf = heapBuf->ReAllocL(KHello().Length() + KWorld().Length());
CleanupStack::PushL(heapBuf);

TPtr ptr (heapBuf->Des());  //DON'T use TPtr ptr = heapBuf->Des(); this will set maxlen to 6 but not 12...
ptr[KHello().Length() - 1] = ' ';
ptr += KWorld;
iTopLabel -> SetTextL(ptr);
CleanupStack::PopAndDestroy();
DrawNow();

下面介绍Descriptors的具体用法:  

 Non-Modifying Methods

     Length(),Size(),Left(),Right(),Mid(),Compare(),Locate(),LocateReverse (),Find(),Match()等。以下代码示例描述了如何在一个descriptor中找到<>中的内容,如果不存在,返回整个字符串:

static const TUint KAddressStartChar = '<'; 
static const TUint KAddressEndChar = '>';

TPtrC ExtractAddressNumber( const TDesC& aAddressString)
{
    TInt addrStart = aAddressString.Locate(KAddressStartChar ) + 1;
    TInt addrEnd  = aAddressString.LocateReverse(KAddressEndChar ) ;
    if ((addrStart == KErrNotFound) || (addrEnd == KErrNotFound) || (addrStart >= addrEnd) )
    {
        addrStart = 0;
        addEnd    = aAddressString.Length();
    }
    return (aAddressString.Mid(addrStart, (addrEnd - addrStart) ) );

 Modifying Methods

     Zero(),Copy(),Num(),Format(),Insert(),Replace(),Delete(),Append(),Trim()等。代码示例:

_LIT(KText, "Hello World!");
_LIT(KNewText, "New Text");
_LIT(KReplaced, "Replaced");

TBuf<16> buf1(KText); 
buf1.Delete(6, 6); // length is now 6, leaving "Hello" in the buffer

TBuf<16> buf2(KNewText);
buf2.Copy(KReplaced); // buf2 now contains "Replaced"
buf2.Append(KNewText);  //buf2 now contains "Replaced New Text"

buf2.Delete(99, 1); //Will Cause a PANIC!!! 

 Descriptors in Method Declarations

  • 在函数参数中尽量使用基类
  • 使用中性的描述符,一般情况下使用TDesC而不是TDesC8或者TDesC16
  • 当描述符内容不应该改变时,使用const修饰符 
  • 经典用法:void SetText(const TDesC& aText);    TPtrC Text() const; 

  Character Conversions   

     CCnvCharacterSetConverter类提供了在Unicode和其他字符集编码之间转换的方法。

ASCII本来就是Unicode的一个子集,无须使用该类。和Unicode之间的转换方法如下所示:

TBuf16<64>  UnicodeBuf;
_LIT8(KAsciiStr, "Hello");
UnicodeBuf.Copy(KAsciiStr);

Unicode和拉丁语系之间的转化可使用如下的代码:

 TBuf8<64> Latin1Buf;
_LIT16(KUnicodeStr1, "hello");
_LIT16(KUnicodeStr2, "I have got 10/x20AC.");  ///x20AC is a euro
Latin1Buf.Copy(KUnicodeStr1); //OK
Latin1Buf.Copy(KUnicdoeStr2); //Not as you wanted.

待补充:Unicode和中文之间的转化 

 读后感

对于初学者来说,一开始这部分是最难接受的,因为都已经习惯了C中的字符串。

但是经过一段时间的适应后,会慢慢熟悉它,这种东西要看好多遍才能领会,这个培训教程确实很好,讲的非常清楚。

多看看Forum Nokia上的例子和如InternetRadio等开源代码,慢慢的,你就适应了。

Symbian,想说爱你并不容易!  呵呵~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值