当前页面: 开发资料首页 → 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.