Flier's Sky

天空,蓝色的天空,眼睛看不到的东西,眼睛看得到的东西
posts - 115, comments - 322, trackbacks - 42, articles - 0

2004年8月8日

原文:http://www.blogcn.com/User8/flier_lu/index.html?id=3236734


    为了在一个标志字段中保存多种类型的标志,C 语言中定常见模式之一,是先定义一个 XXX_MASK,再定义一个 XXX_SHIFT,然后通过移位操作定义这段位上的标志,如 WinCrypt.h 中定义证书存储位置标志时,将位置标志位放在高16位中:
// Includes flags and location
#define CERT_SYSTEM_STORE_MASK                  0xFFFF0000

// Location of the system store:
#define CERT_SYSTEM_STORE_LOCATION_MASK         0x00FF0000
#define CERT_SYSTEM_STORE_LOCATION_SHIFT        16

//  Registry: HKEY_CURRENT_USER or HKEY_LOCAL_MACHINE
#define CERT_SYSTEM_STORE_CURRENT_USER_ID       1
#define CERT_SYSTEM_STORE_LOCAL_MACHINE_ID      2

//

#define CERT_SYSTEM_STORE_CURRENT_USER          
    (CERT_SYSTEM_STORE_CURRENT_USER_ID 
<< CERT_SYSTEM_STORE_LOCATION_SHIFT)
#define CERT_SYSTEM_STORE_LOCAL_MACHINE         
    (CERT_SYSTEM_STORE_LOCAL_MACHINE_ID 
<< CERT_SYSTEM_STORE_LOCATION_SHIFT)
  
    而在 C# 中一般使用以 FlagsAttribute 标记后的 Enum 来模拟类似语义,如

[Flags]
public enum CertSystemStoreFlag : uint
{    
  
//  Registry: HKEY_CURRENT_USER or HKEY_LOCAL_MACHINE
  CERT_SYSTEM_STORE_CURRENT_USER_ID               = 1,
  CERT_SYSTEM_STORE_LOCAL_MACHINE_ID              
= 2,

  
// Includes flags and location
  CERT_SYSTEM_STORE_MASK                          = 0xFFFF0000,

  
// Set if pvPara points to a CERT_SYSTEM_STORE_RELOCATE_PARA structure
  CERT_SYSTEM_STORE_RELOCATE_FLAG                 = 0x80000000,
  
  CERT_SYSTEM_STORE_LOCATION_MASK                 
= 0x00FF0000,    
  CERT_SYSTEM_STORE_LOCATION_SHIFT                
= 16,
  
  CERT_SYSTEM_STORE_CURRENT_USER                  
= 
}

    
    FlagsAttribute 标记使得此 Enum 能够以位域(bit field)形式进行操作,既有 Flags 的类型安全特性,又有进行位操作的灵活性。同时还能定义此 Enum 的基本类型,如上面指定的 uint,以最大限度兼容现有 C 代码。
    
    不过这样组合使用 Enum 的多个特性时有一个小小的语法陷阱,不能将 C# 完全等同于 C++ 的语法,例如要这样照搬 C++ 定义语法:
[Flags]
public enum CertSystemStoreFlag : uint

  CERT_SYSTEM_STORE_CURRENT_USER_ID               
= 1,   
  
  CERT_SYSTEM_STORE_LOCATION_SHIFT                
= 16,
  
  CERT_SYSTEM_STORE_CURRENT_USER 
= CERT_SYSTEM_STORE_CURRENT_USER_ID << CERT_SYSTEM_STORE_LOCATION_SHIFT,
}

    
    编译时就会获得一个让人困惑的警告消息:
以下为引用:

CS0019: Operator '<<' cannot be applied to operands of type 'uint' and 'uint'

    
    以一个 C++ 背景的程序员角度来看,实在是无法想像为什么移位操作竟然不能对 uint 进行处理,不过在 C# 中这恰恰是语法所要求的。ECMA-334 C# Language Specification 的第 14.8 节 Shift operators 是这样定义 C# 的移位操作符的:
以下为引用:

The << and >> operators are used to perform bit shifting operations. 
  
    shift-expression : 
        additive-expression 
        shift-expression << additive-expression 
        shift-expression >> additive-expression 
        
...

The predefined shift operators are listed below. 

    2 Shift left: 

        int operator <<(int x, int count);  
        uint operator <<(uint x, int count);  
        long operator <<(long x, int count);  
        ulong operator <<(ulong x, int count);  

    3 The << operator shifts x left by a number of bits computed as described below. 4 The high-order bits outside the range of the result type of x are discarded, the remaining bits are shifted left, and the low-order empty bit positions are set to zero. 
    
    5 Shift right: 
    
        int operator >>(int x, int count);  
        uint operator >>(uint x, int count);  
        long operator >>(long x, int count);  
        ulong operator >>(ulong x, int count);  
    
    6 The >> operator shifts x right by a number of bits computed as described below. 7 When x is of type int or long, the low-order bits of x are discarded, the remaining bits are shifted right, and the high-order empty bit positions are set to zero if x is non-negative and set to one if x is negative. 8 When x is of type uint or ulong, the low-order bits of x are discarded, the remaining bits are shifted right, and the high-order empty bit positions are set to zero.        

    
    可以看到,双目移位操作符的两个参数实际上是不一样的,操作符左的参数可以是有/无符号的int和long,但操作符右边的则都是 int。因此上面那个错误的表达式中,移位操作符右边数字应该被显式转换为 int 来符合 C# 的语法要求。
[Flags]
public enum CertSystemStoreFlag : uint

  CERT_SYSTEM_STORE_CURRENT_USER_ID               
= 1,   
  
  CERT_SYSTEM_STORE_LOCATION_SHIFT                
= 16,
  
  CERT_SYSTEM_STORE_CURRENT_USER 
= CERT_SYSTEM_STORE_CURRENT_USER_ID << (int)CERT_SYSTEM_STORE_LOCATION_SHIFT,
}

        
    呵呵,虽然比较别扭,但没办法,谁让 C# 定义得这么严谨呢 :P

btw: 感谢 Junfeng Zhang 帮忙指出问题所在 

posted @ 2004-08-08 20:40 Flier Lu 阅读(2824) 评论(2) 编辑