当前页面: 开发资料首页 → Netbeans 专题 → 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.
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
The following identifiers and expressions are available in the described syntax and function, as follows:
#ifdef
identifier#ifdef
identifier
must be closed with #endif
. This commenting behavior is same
for all of the other directives.#ifndef
identifier#ifdef
but returns the "Not" boolean value
of that result. The #ifndef
identifier must be closed with
#endif
. #elifdef
identifier #ifdef
or #ifndef
.#elifndef
identifier #ifdef
or #ifndef
.#if
expression #endif
.#elif
expression #elif
expression preprocesses the code that follows based on the result of the
expression. #else
if/#ifdef/#ifndef
directive.#endif
#if/#ifdef/
#ifndef
.#condition
expression #debug
level #debug
level
is used for debugging purposes in conjunction with, for example, System.out.println
.
#debug
level can be nested.#mdebug
level #debug,
but will uncomment or comment a
whole block of lines following the line it is on until it reaches #enddebug
.
is used for debugging purposes in conjunction with, for example, System.out.println
.
#mdebug
level can be nested. If an #mdebug
block
partially intersects an if/ifdef/ifndef
block (e.g. enddebug
is outside a closed if block in which mdebug is called) the preprocessor
will generate errors.#enddebug
#mdebug
block.#define identifier or identifier=value or identifier
value
#undefine identifier
#undefine
can also be used to remove global variables defined in the configuration
properties from the preprocessor memory but will not remove them
from the list of project or configuration 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.
There are three types of operators with different priorities:
! 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 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 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 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 |
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.