站内搜索: 请输入搜索关键词

当前页面: 开发资料首页Netbeans 专题Defining and Using Preprocessor Directives in the Mobility Pack

Defining and Using Preprocessor Directives in the Mobility Pack

摘要: One of the most difficult aspects of developing applications for mobile devices is device fragmentation. Mobile devices often differ in a variety of attributes, such as screen size or color depth, and many support proprietary or optional APIs. These differences can require special code or project settings.

The NetBeans Mobility Pack solves this problem by letting you use project configurations and preprocessor directives within your code, so you can tailor your MIDlet for multiple devices within a single source file.

For example, if you are writing an application targeted for several different devices, you can create a project configuration for each device, then add preprocessor directives that optimize the code for each configuration. When you build and run your MIDlet, you can choose to build for one or all of the configurations, producing a distribution JAR for each configuration.

This article provides a guide to the syntax of the preprocessor used in the Mobility Pack's device fragmentation solution. This guide is divided into the following sections.

Using Preprocessor Directives

Preprocessor directives are specified by creating a commented line that starts with the //# character sequence immediately followed by the directive, for example //#ifdef.

Preprocessor blocks must be well-formed, which means when a block is started with one of the //#if directives, it must be closed by an //#endif directive. Blocks can be nested, which means that inside an if/elif/else/endif block can be arbitrary number of additional if/elif/else/endif blocks.

For example:

//#if mmedia
   //#if nokia
         //#if s60_ver=="1.0"
import com.nokia.mmapi.v1
//#elif s60_ver=="2.0"
import com.nokia.mmapi.v2
//#else
import com.nokia.mmapi.def
//#endif
//#else
import javax.microedition.mmapi
//#endif
//#endif

Directive Syntax and Functions

The following sections are a detailed description of the identifiers and expressions you can use in preprocessor directives, and their syntax.

Identifiers and Expressions

The following identifiers and expressions are available in the described syntax and function, as follows:

Variables

The preprocessor supports three types of variables: Strings, Integers and Booleans.

The variable names must start with characters which are the same as the start characters of valid Java identifiers but in addition, consequentive characters can also be '.' '\' and '/'. This is to make migration easier for developers with existing Antenna or J2ME Polish sources which happen to use these characters in some variable and symbol names. Most common comparisons <=, <, >=, > and == are supported between the variables. Comparisons should not be done on different variable types. If different variable types are compared, the IDE issues a warning (an annotation in the Editor and a warning in task output), but the expression gets evaluated according to the behavior described in the Comparison Operators section.

Strings can contain just about anything but must be enclosed in quotation marks (""). Checks for definition can be made on any variable and, unlike J2ME Polish, no additional keywords are needed. The preprocessor checks for the J2ME Polish :defined construct and removes it from the variable name before checking for the definition. For example, let string nokia_model be "N60", then //#ifdef nokia_model or //#if nokia_model will yield "True" because it is defined and is evaluated as such. On the other hand, //#if nokia_model=="7610" will yield "False" because nokia_model is not "7610".

Here is what it would look like in the source code in J2MEPolish:

//#define nokia_model="N60"
//#ifdef nokia_model:defined
System.out.println("Nokia");
//#else
System.out.println("Other");
//#endif

In NetBeans, it would like like this:

//#define nokia_model="N60"
//#ifdef nokia_model
System.out.println("Nokia");
//#else
System.out.println("Other");
//#endif

Integers are numbers and behave the same as strings when it comes to boolean operations. However, they are compared as true integers, and can be used for such tasks as preprocessing code where various screen resolutions require different images that are optimized for different resolutions. For example, //#if screen_width>100 && screen_height>120 can specify a code block which will import images only for devices with screens larger than 100x120.

If the variable is an empty variable (for example, a configuration name) then it is considered a define symbol. If the variable is not defined anywhere in preprocessor scope (as an ability or configuration) then it is considered an undefined symbol.

Operators

There are three types of operators with different priorities:

! or the "Not" Operator

! or the"Not" Operator has the highest priority and can be used on variables and expressions, such as !<identifier> !<expression>. For example, //#if !nokia will check whether nokia is not defined. //#if !(screen_width>100 && screen_height>120) will check if screen size is smaller than 100x120. The expression must be a valid one enclosed in parenthesis, as shown. Since the ! operator has the highest priority, expressions such as //#if screen_size=="100x200" is illegal and will yield syntax errors because a boolean result cannot be compared to a string.

The Comparison Operators

The comparison operators have the second highest priority and perform typical comparison operations. They can compare strings lexically and integers mathematically. Cross-type comparisons are supported and behave as defined in Variable Comparisons Table. They can be used only in expressions and should compare two variables, not symbols.

There is also a special comparison operator which performs a "subset" relationship operation. This operator is denoted by the @ token, and both left and right arguments should be strings which represent two sets of tokens delimited by specific delimiters. It first tokenizes both left and right string arguments into sets and then determines if the set from the left argument is a subset of the set from the right argument. The valid word delimiters are <whitespace>,',' and ';' and they can be mixed arbitrarily within each argument. The comparison operator behaves just as in the following examples:

"gif" @ "gif86, jpeg, gifaboo" = False

"gif" @ "gif gif86 jpeg" = True

"1 2 4;7,8" @ "0,1,2,3,4,5,6,7,8,9" = True

"3 5 7 11 13" @ "0,1,2,3,4,5,6,7,8,9" = False

Even though the variables should be of the same type when comparing, the preprocessor will not fail but will present a warning (an annotation in the Editor and a warning in task output) and evaluate the expression as follows. When the left and right sides of the comparison operation are of different types, both will be considered strings and compared lexically. The exception is the @ operation where the "subset" relationship operation will still be performed (as such, you can check if a certain integer is in a particular set). If one of the variables is not defined or is defined as a boolean symbol, it will be considered an empty String "". If one of the variables is an integer, it will also be converted to a string and a lexical comparison will take place. You should avoid comparing variables of different types, but doing so by accident will not break the build process.

Boolean Operators

Boolean operators have the lowest priority relative to the rest of the operators, but they do have different priorities amongst each other, just like in the Java language. They perform typical logical operations such as &&, || and ^ on booleans, expression results, or check for variable definitions, and then treat those as booleans as well. The && operator has the highest priority from all three boolean operators, while ^ and || have lower priorities, respectively.

For example, if we preprocess the following example with active configuration being "Series40"

//#ifdef Series60
     //#define tmpNokia3650
     //#define tmpNokia3660
     //#define tmpSiemensSX1
     //#define tmpScreenWidth=176
     //#define tmpScreenHeight=208
 //#elifdef Series40
      //#define tmpNokia3220
      //#define tmpNokia3210
      //#define tmpScreenWidth=80
      //#define tmpScreenHeight=100
//#elifdef Series20
     //#define tmpNokia8320
//#else
     //#define default
//#endif

//#if tmpScreenWidth==176 && tmpScreenHeight=208 || tmpNokia3650 && tmpNokia3660
System.out.println("Series 60 configuration active!");
//#elif tmpScreenWidth==176 || tmpScreenWidth=80 && tmpScreenHeight=100 || tmpNokia8320
System.out.println("One of the Nokia configurations active!");
//#else
System.out.println("Default configuration active!);
//#endif

the result will be

//#if tmpScreenWidth==176 && tmpScreenHeight=208 || tmpNokia3650 && tmpNokia3660
    //# System.out.println("Series 60 configuration active!");
//#elif tmpScreenWidth==176 || tmpScreenWidth=80 && tmpScreenHeight=100 || tmpNokia8320
     System.out.println("One of the Nokia configurations active!");
//#else
    //# System.out.println("Default configuration active!);
//#endif

Expressions

Expressions are evaluated according to the following Backus Naur Form (BNF) grammar specification:

<expression>::= <term> { <boolop> <term> }
<term>::= <factor> { <compop> <factor> }
<factor>::= notop <factor> | value | <expression>
<boolop>::= && | || | ^
<compop>::= > | >= | < | <= | == | != | @
<notop>::= !

In the table below, assuming that the following variables are in scope of the current configuration:

Variable Name Variable Type Value
nokia
Boolean null
screen_width
Integer 100
screen_height
Integer 160
symbVar
String v7.0
mmapi
Boolean null

 

Assuming the values listed above, here are some examples of legal and illegal expressions:

Expression Yields Value of
!nokia && mmapi False
nokia&&mmapi True
symbVer=="v7.0" || (screen_width>=100 && screen_width>=100) True
siemens && !screen_width!=100 Syntax Error
siemens || !nokia False
nokia && (screen_width>100 || screen_height>100 Syntax Error

You can use the following table to understand how the preprocessor evaluates variable comparisons. For example, if the variable on the left side of the comparison is an integer and the variable on the right side is a string, then using the == operator will be evaluated lexically. If the same variables are compared with the @ operator, the comparison will be evaluated mathematically.

Left Side
Right Side
==
!=
>
<
>=
<=
@
undefined undefined warn warn warn warn warn warn warn
undefined defined warn warn warn warn warn warn warn
undefined string warn warn warn warn warn warn warn
undefined integer warn warn warn warn warn warn warn
defined undefined warn warn warn warn warn warn warn
defined defined warn warn warn warn warn warn warn
defined string warn warn warn warn warn warn warn
defined integer warn warn warn warn warn warn warn
integer undefined warn warn warn warn warn warn warn
integer defined warn warn warn warn warn warn warn
integer string lex lex lex lex lex lex math
integer integer math math math math math math warn
string undefined warn warn warn warn warn warn warn
string defined warn warn warn warn warn warn warn
string string lex lex lex lex lex lex math
string integer lex lex lex lex lex lex warn

 

Compilation Based on Device Platform Versioning

On occasion device lines from certain manufacturers have various platforms and platform versions. In this example, we take Nokia and their s60 and s40 platforms, both with version 1 and 2. To accomplish this, we create four configurations and add abilities as follows:

Configurations Nokia.s60.v2 Nokia.s60.v1 Nokia.s40.v2 Nokia.s40.v1
Abilities manufacturer=Nokia
platform=s60
platform_version=2
manufacturer=Nokia
platform=s60
platform_version=1
manufacturer=Nokia
platform=s40
platform_version=2
manufacturer=Nokia
platform=s40
platform_version=1

When the symbols and variables are properly defined, the code that will compile for all four platforms based on their differences could look like:

//#if manufacturer=="Nokia"
.
compile all code common to nokias, e.g. using FullCanvas
.
//#if platform=="s40"
.
add s40 specific code
.
//#if platform_version=1
.
compile s40.v1 specific stuff
.
//#elif platform_version=2
.
compile s40.v2 specific stuff
.
//#else
.
compile s40 generic
//#endif
. .
//#elif platform=="s60"
.
add s60 specific code
.
//#if platform_version=1
.
compile s60.v1 specific stuff
.
//#elif platform_version=2
.
compile s60.v2 specific stuff
.
//#else
.
compile 640 generic
//#endif
.
.
//#else
.
add generic Nokia code
//#endif
//#endif

Under many circumstances, however, this approach might not be possible or will need a lot of redundant code writing, especially when actual features in the software depend on the platform specification. This approach will yield good results when handling various audio/video formats, for example. When functionality and features in the software are based on the platform specification, a more incremental approach is desirable.

.
.
code common to all devices
.
.
//#if manufacturer=="Nokia" && platform=="s60" && platform_version==1
.
implement specific feature
.
//#elif manufacturer=="Nokia" && platform=="s60" && platform_version==2
.
.
//#elif manufacturer=="Nokia" && platform=="s40" && platform_version==1
.
.
//#elif manufacturer=="Nokia" && platform=="s40" && platform_version==2
.
.
//#endif

Depending on the situation, both approaches can be mixed together to get the desired results. NetBeans provides some predefined abilities, such as CLDC and MIDP versions, as well as symbols for various JSRs which can be used and do not have to be defined by hand.


↑返回目录
前一篇: Customize Your NetBeans Java Template to Fit Your Need
后一篇: Deploy and Run Java ME Applications using Sun Java Application Server