www.9778.com 7

www.9778.com:宏定义中的##操作符和… and _ _VA_ARGS_ _

Rust
1.40.0 已经正式发布。该版本的亮点包括有 #[non_exhaustive] 和 macros!() and #[attribute]s 的改进。

在本章中,我们将更多讨论有关在Visual
LISP中使用ActiveX性能。首先,我们从ActiveX的技术环境开始,包括对象、对象模型、集合、属性、方法等方面。然后我们将深挖一些特定的ActiveX技术部分。了解ActiveX功能性是学习任何语言必不可少的工作。

attribute是GNU C特色之一,在iOS用的比较广泛.系统中有许多地方使用到.
attribute可以设置函数属性(Function Attribute )、变量属性(Variable
Attribute )和类型属性(Type Attribute)等.

1.Preprocessor Glue: The ## Operator

具体更新内容如下:

ActiveX是一种面向对象的工具,也就是说它表现为对象的使用以及对象间的关联这种方式。在这里我不想去解释面向对象的问题,这些东西最好留给那些更专业的教科书去讲。然而,我将尝试给一些基本的面向对象方面的描述,以便大家有一个基本的了解。

函数属性(Function Attribute)

  • noreturn
  • noinline
  • always_inline
  • pure
  • const
  • nothrow
  • sentinel
  • format
  • format_arg
  • no_instrument_function
  • section
  • constructor
  • destructor
  • used
  • unused
  • deprecated
  • weak
  • malloc
  • alias
  • warn_unused_result
  • nonnull

预处理连接符:##操作符

#[non_exhaustive] 结构,枚举和变体

类型属性(Type Attributes)

  • aligned
  • packed
  • transparent_union,
  • unused,
  • deprecated
  • may_alias

Like the # operator, the ## operator can be used in the replacement
section of a function-like macro.Additionally, it can be used in the
replacement section of an object-like macro. The ## operator combines
two tokens into a single token. 

当属性#[non_exhaustive]附加到struct或的变体时enum,它将防止定义它的板条箱外部的代码构造所述struct或变体。为了避免将来损坏,还防止其他包装箱在田地上进行彻底匹配。以下示例说明了beta取决于的错误alpha

在面向对象的环境中,任何事物都是以Classes(类)开始的。类是抽象的框架,用于描述对象应是什么样的,以及他们应该如何表现和相互联系的。在某种意义上说类定义了对象类型的种类。例如,一辆汽车是属于交通工具类。交通工具是父类,而汽车是子类。反之,你可取得更多特定的和额外定义的子类,例如旅行车、面包车和运动型轿车。

变量属性(Variable Attribute)

  • aligned
  • packed

##将两个符号连接成一个。

// alpha/lib.rs:

#[non_exhaustive]
struct Foo {
    pub a: bool,
}

enum Bar {
    #[non_exhaustive]
    Variant { b: u8 }
}

fn make_foo() -> Foo { ... }
fn make_bar() -> Bar { ... }

// beta/lib.rs:

let x = Foo { a: true }; //~ ERROR
let Foo { a } = make_foo(); //~ ERROR

// `beta` will still compile when more fields are added.
let Foo { a, .. } = make_foo(); //~ OK


let x = Bar::Variant { b: 42 }; //~ ERROR
let Bar::Variant { b } = make_bar(); //~ ERROR
let Bar::Variant { b, .. } = make_bar(); //~ OK
                   // -- `beta` will still compile...

类不处理特定的实例,他们更注重于实例方面的描述,而非使用方面。当你使用类时,你可以说调用类的一个实例。调用类的结果经常是创建了一个对象。对象可以是单独的图元或包含了更多对象的容器。

Clang特有的

  • availability
  • overloadable

For example, you could do this:

幕后发生的事情是,#[non_exhaustive] struct或的构造函数的可见性enum降低到pub(crate),从而阻止了在定义它的板条箱外部进行访问。

对象
一个对象就是类的一个实例。对象有其固定的属性,同时也可以有固定的方法和事件。属性是定义对象怎样表现和反应的特性。方法是用于访问或修改对象属性或某些行为的内置函数。事件是由对象发送的通知,当执行或激活它们时,它用于响应特定动作。

书写格式

书写格式:attribute后面会紧跟一对原括弧,括弧里面是相应的attribute参数

__attribute__(xxx)

#define XNAME(n) x ## n

更重要的方面是,#[non_exhaustive]也可以附加到enum自身上。从标准库中获取的示例是Ordering

就用上面的汽车类的例子来说,对象可以是特别的轿车。比如说,你的轿车,带有特别的配置(制造、型号、颜色、选配、以及序列号)。你可以说,你的轿车是汽车类的一个实例,或汽车类派生的一些类。

常见的系统用法

Then the macro

#[non_exhaustive]
pub enum Ordering { Relaxed, Release, Acquire, AcqRel, SeqCst }

图3-1 对象模型

format

官方例子:NSLog

 #define NS_FORMAT_FUNCTION(F,A) __attribute__((format(__NSString__, F, A)))

format属性可以给被声明的函数加上类似printf或者scanf的特征,它可以使编译器检查函数声明和函数实际调用参数之间的格式化字符串是否匹配。该功能十分有用,尤其是处理一些很难发现的bug。对于format参数的使用如下
format (archetype, string-index, first-to-check)
第一参数需要传递“archetype”指定是哪种风格,这里是
NSString;“string-index”指定传入函数的第几个参数是格式化字符串;“first-to-check”指定第一个可变参数所在的索引.

XNAME(4)

#[non_exhaustive]在此上下文中的目的是确保可以随时间添加更多变体。这是通过防止其他包装箱从详尽模式实现match-ing上Ordering。也就是说,编译器将拒绝:

对象模型
对象模型是一个随意的架构,或类之间的分层定义的排列关系,意思就是可以从更高级别的类来获得一个对象。对象模型与访问它的语言或工具无关,它有自己的逻辑框架。不管你是使用Visual
Basic、VBA、Visual
LISP、Delphi、Java、C/C++、C#.NET或带有ActiveX接口的其它语言,它都将以相同的模型存在。这并不代表对象模型的所有特性都支持所有语言。某些特性在某些语言中可以访问或比较容易被访问,但在其它语言中可能就不行。

noreturn

官方例子: abort() 和 exit()

该属性通知编译器函数从不返回值。当遇到类似函数还未运行到return语句就需要退出来的情况,该属性可以避免出现错误信息。

would expand to the following:

match ordering {
    // This is an error, since if a new variant is added,
    // this would suddenly break on an upgrade of the compiler.
    Relaxed | Release | Acquire | AcqRel | SeqCst => {
        /* logic */
    }
}

我们将对象模型比喻成一套房子,它由房间、门和窗组成。不同的人都可进入和使用房子,而他们都是面对同样的这套房子。在这种情况下,房子和房间就是对象模型,而人即是编程语言。这样,你应该懂吧。

availability

官方例子:

- (CGSize)sizeWithFont:(UIFont *)font NS_DEPRECATED_IOS(2_0, 7_0, "Use -sizeWithAttributes:") __TVOS_PROHIBITED;

//来看一下 后边的宏
 #define NS_DEPRECATED_IOS(_iosIntro, _iosDep, ...) CF_DEPRECATED_IOS(_iosIntro, _iosDep, __VA_ARGS__)

define CF_DEPRECATED_IOS(_iosIntro, _iosDep, ...) __attribute__((availability(ios,introduced=_iosIntro,deprecated=_iosDep,message="" __VA_ARGS__)))

//宏展开以后如下
__attribute__((availability(ios,introduced=2_0,deprecated=7_0,message=""__VA_ARGS__)));
//ios即是iOS平台
//introduced 从哪个版本开始使用
//deprecated 从哪个版本开始弃用
//message    警告的消息

availability属性是一个以逗号为分隔的参数列表,以平台的名称开始,包含一些放在附加信息里的一些里程碑式的声明。

  • introduced:第一次出现的版本。

  • deprecated:声明要废弃的版本,意味着用户要迁移为其他API

  • obsoleted: 声明移除的版本,意味着完全移除,再也不能使用它

  • unavailable:在这些平台不可用

  • message:一些关于废弃和移除的额外信息,clang发出警告的时候会提供这些信息,对用户使用替代的API非常有用。

  • 这个属性支持的平台:ios,macosx。

简单例子:

//如果经常用,建议定义成类似系统的宏
- (void)oldMethod:(NSString *)string __attribute__((availability(ios,introduced=2_0,deprecated=7_0,message="用 -newMethod: 这个方法替代 "))){
    NSLog(@"我是旧方法,不要调我");
}

- (void)newMethod:(NSString *)string{
    NSLog(@"我是新方法");
}

效果:

www.9778.com 1

Paste_Image.png

//如果调用了,会有警告

www.9778.com 2

Paste_Image.png

x4

取而代之的是,其他板条箱需要通过添加通配符来解决更多变体的可能性,例如_

类继承
对象模型通常都是从根或源对象开始。在AutoCAD中,源对象是AutoCAD
Application
(AutoCAD应用程序)对象,也被称之为AcadApplication对象。它提供了基本的属性、方法、事件和来自所有其它对象和集合构成的集合。例如,AcadApplication对象有一集合为Documents(即Documents集合),在其中对应有一个或多个Document对象。每一Document对象有它自己的对象、集合、属性和方法以及其它东西。

unavailable

告诉编译器该方法不可用,如果强行调用编译器会提示错误。比如某个类在构造的时候不想直接通过init来初始化,只能通过特定的初始化方法()比如单例,就可以将init方法标记为unavailable;

//系统的宏,可以直接拿来用
 #define UNAVAILABLE_ATTRIBUTE __attribute__((unavailable))

 #define NS_UNAVAILABLE UNAVAILABLE_ATTRIBUTE

@interface Person : NSObject

@property(nonatomic,copy) NSString *name;

@property(nonatomic,assign) NSUInteger age;

- (instancetype)init NS_UNAVAILABLE;

- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age;

@end

www.9778.com 3

Paste_Image.png

//实际上unavailable后面可以跟参数,显示一些信息,如:

//系统的
 #define NS_AUTOMATED_REFCOUNT_UNAVAILABLE __attribute__((unavailable("not available in automatic reference counting mode")))

Listing 1 uses this and another macro using ## to do a bit of token
gluing.

match ordering {
    Relaxed | Release | Acquire | AcqRel | SeqCst => { /* ... */ }
    // OK; if more variants are added, nothing will break.
    _ => { /* logic */ }
}

你可向下层浏览对象模型进入下一层次的对象和集合,也可以向上层浏览父对象和集合。模型非常强大,应用程序可直接访问和操作环境来执行几乎无限的任务。它同时保持每一事物都整齐有序,在开发软件解决方案时能提供有效的帮助。

objc_root_www.9778.com:宏定义中的##操作符和… and _ _VA_ARGS_ _。class

表示这个类是一个根类(基类),比如NSObject,NSProxy.

//摘自系统
//NSProxy
NS_ROOT_CLASS
@interface NSProxy <NSObject> {
    Class   isa;
}

//NSObject
__OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0)
OBJC_ROOT_CLASS
OBJC_EXPORT
@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

 

有关该#[non_exhaustive]属性的更多详细信息,可参见稳定性报告。

集合和词典
集合是在相同父窗口中一组相似的对象。该容器有一个独特的名字,在大多数情况下,将提供自己的方法来访问所包含的对象。词典是一种特殊类型的集合,它允许你扩展你自己的词典。Visual
LISP并没有过多提供用于创建或处理集合的方法。它允许你遍历它、修改项目、添加或删除条目。词典允许你添加自己的词典并写入数据,你可遍历它、添加、修改和删除其条目,同样,你也可以添加
、修改和删除词典本身。

NSObject

@property (nonatomic,strong) __attribute__((NSObject)) CFDictionaryRef myDictionary;

CFDictionaryRef属于CoreFoundation框架的,也就是非OC对象,加上attribute((NSObject))后,myDictionary的内存管理会被当做OC对象来对待.

// glue.c — use the ## operator

Macro and attribute 的改进

在AutoCAD中的一些公共的集合有Documents(文档)、Layers(图层)、Dimension
Styles(标注样式)、Linetypes(线型)、 Blocks(块)等等。

objc_designated_initializer

用来修饰类的designated
initializer初始化方法,如果修饰的方法里没有调用super类的 designated
initializer,编译器会发出警告。可以简写成NS_DESIGNATED_INITIALIZER

这篇文章讲的很好,建议参考这个.
https://yq.aliyun.com/articles/5847

#include <stdio.h>

  • 在 type
    contexts 中调用 procedural
    macros mac!() 。

在AutoCAD中的一些公共的词典有PageSetups (页面设置)、Layouts
(布局)(它同样也做为词典保存),还有Express Tools中的个别组件,如WipeOuts
(遮罩)。Xrecord对象也保存在词典内。

visibility

语法:

__attribute__((visibility("visibility_type")))

其中,visibility_type 是下列值之一:

  • default
    假定的符号可见性可通过其他选项进行更改。缺省可见性将覆盖此类更改。缺省可见性与外部链接对应。

  • hidden
    该符号不存放在动态符号表中,因此,其他可执行文件或共享库都无法直接引用它。使用函数指针可进行间接引用。

  • internal
    除非由 特定于处理器的应用二进制接口 (psABI)
    指定,否则,内部可见性意味着不允许从另一模块调用该函数。

  • protected
    该符号存放在动态符号表中,但定义模块内的引用将与局部符号绑定。也就是说,另一模块无法覆盖该符号。

  • 除指定 default
    可见性外,此属性都可与在这些情况下具有外部链接的声明结合使用。
    您可在 C 和 C++ 中使用此属性。在 C++
    中,还可将它应用于类型、成员函数和命名空间声明。

系统用法:

//  UIKIT_EXTERN     extern
 #ifdef __cplusplus
 #define UIKIT_EXTERN        extern "C" __attribute__((visibility ("default")))
 #else
 #define UIKIT_EXTERN            extern __attribute__((visibility ("default")))
 #endif

#define XNAME(n) x ## n

例如,用户可以编写以下类型:Foo = expand_to_type!(bar); 其中
expand_to_type 将是一个 procedural macro。

属性、方法和事件
属性只是描述关联于对象或集合的特性。例如它可包含名称、高度、宽度、旋转角度、比例缩放、颜色、图层、线型等等。属性根据不同的对象的类型而有所区别,但有些属性对所有对象和集合是通用的。集合和词典通常提供了Count和Name属性,还有Item和Add方法。只有词典提供了Delete方法,而你却不能通过Visual
LISP删除集合。

nonnull

编译器对函数参数进行NULL的检查,参数类型必须是指针类型(包括对象)
//使用

- (int)addNum1:(int *)num1 num2:(int *)num2  __attribute__((nonnull (1,2))){//1,2表示第一个和第二个参数不能为空
    return  *num1 + *num2;
}

- (NSString *)getHost:(NSURL *)url __attribute__((nonnull (1))){//第一个参数不能为空
    return url.host;
}

#define PRINT_XN(n) printf(“x” #n ” = %dn”, x ## n);

  • Macros
    in extern { ... } blocks.

方法是对象提供的用于访问或编辑专用特性或针对对象本身执行特别动作的内置函数。常见的方法有Rotate(旋转)、Erase(删除)、Copy(复制)、Scale(比例缩放)和Offset(偏移)。你可能注意到,这些看起来就像AutoCAD编辑命令。嗯,在本质上是这样的,但略有不同。

常见用法

int main(void)

包括有bang!() macros, 例如:

然而一般的AutoCAD编辑命令,必须在每步执行中验证对象。而方法是由主对象提供的,因此,只能由每一对象单独提供支持的方法。晕了吧?

aligned

__attribute((aligned
(n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐.例如:

不加修饰的情况

typedef struct
{
    char  member1;
    int   member2;
    short member3;
}Family;

//输出字节:
NSLog(@"Family size is %zd",sizeof(Family));
//输出结果为:
2016-07-25 10:28:45.380 Study[917:436064] Family size is 12

//修改字节对齐为1

typedef struct
{
    char  member1;
    int   member2;
    short member3;
}__attribute__ ((aligned (1))) Family;

//输出字节:
NSLog(@"Family size is %zd",sizeof(Family));
//输出结果为:
2016-07-25 10:28:05.315 Study[914:435764] Family size is 12

和上面的结果一致,因为 设定的字节对齐为1.而结构体中成员的最大字节数是int
4个字节,1 < 4,按照4字节对齐,和系统默认一致.

修改字节对齐为8

typedef struct
{
    char  member1;
    int   member2;
    short member3;
}__attribute__ ((aligned (8))) Family;

//输出字节:
NSLog(@"Family size is %zd",sizeof(Family));
//输出结果为:
2016-07-25 10:28:05.315 Study[914:435764] Family size is 16

这里 8 >
4,按照8字节对齐,结果为16,不知道字节对齐的可以看我的这篇文章http://www.jianshu.com/p/f69652c7df99

可是想了半天,也不知道这玩意有什么用,设定值小于系统默认的,和没设定一样,设定大了,又浪费空间,效率也没提高,感觉学习学习就好.

{

macro_rules! make_item { ($name:ident) => { fn $name(); } }

extern {
    make_item!(alpha);
    make_item!(beta);
}

另一种方式,OFFSET(偏移)命令可以在任何时间使用,但如果你试图偏移一个文本对象,
AutoCAD就会出现出错信息。然而,文本对象本身提供的各种方法,如Copy(复制)、Rotate(旋转)、Scale(比例缩放)和Move(移动),但没有Offset(偏移)。所以你可以从对象“调用”各种方法,却需要确定它对使用的对象是有效的。

packed

让指定的结构结构体按照一字节对齐,测试:

//不加packed修饰
typedef struct {
    char    version;
    int16_t sid;
    int32_t len;
    int64_t time;
} Header;

//计算长度
NSLog(@"size is %zd",sizeof(Header));
输出结果为:
2016-07-22 11:53:47.728 Study[14378:5523450] size is 16

可以看出,默认系统是按照4字节对齐

//加packed修饰
typedef struct {
    char    version;
    int16_t sid;
    int32_t len;
    int64_t time;
}__attribute__ ((packed)) Header;

//计算长度
NSLog(@"size is %zd",sizeof(Header));
输出结果为:
2016-07-22 11:57:46.970 Study[14382:5524502] size is 15

用packed修饰后,变为1字节对齐,这个常用于与协议有关的网络传输中.

int XNAME(1) = 14; // becomes int x1 = 14;

Procedural macro attributes on items
in extern { ... } blocks 现在也被支持:

事件是对象或集合由各种可以被检测到的或可以响应的活动所产生动作。当事件与这些事件的反应相结合使用时,就称为事件驱动编程。AutoCAD提供了一个被称之为反应器的强大的事件反应工具集,它使你可在绘图环境中发送触发器来响应各种事件。例如,当对象在活动的图形中被删除时,您可以创建一个反应器响应一个Erase事件。这只是一个事件和反应器的例子。

noinline & always_inline

内联函数:内联函数从源代码层看,有函数的结构,而在编译后,却不具备函数的性质。内联函数不是在调用时发生控制转移,而是在编译时将函数体嵌入在每一个调用处。编译时,类似宏替换,使用函数体替换调用处的函数名。一般在代码中用inline修饰,但是能否形成内联函数,需要看编译器对该函数定义的具体处理

  • noinline 不内联
  • always_inline 总是内联
  • 这两个都是用在函数上

内联的本质是用代码块直接替换掉函数调用处,好处是:快代码的执行,减少系统开销.适用场景:

  • 这个函数更小
  • 这个函数不被经常调用

使用例子:

//函数声明
void test(int a) __attribute__((always_inline));

int XNAME(2) = 20; // becomes int x2 = 20;

extern "C" {
    // Let's assume that this expands to `fn foo();`.
    #[my_identity_macro]
    fn foo();
}

属性关联
有一点很重要,就是你决不需要知道哪些属性适用于哪些对象和集合。有两个特别的函数对于保证您的代码能够在运行时妥善处理属性和方法的执行:(vlax-property-available-p)和(vlax-method-applicable-p)。这两个函数只是两个Visual
LISP判断函数,它提供布尔测试判断条件是真或假(LISP术语为non-nil或nil)。

warn_unused_result

当函数或者方法的返回值很重要时,要求调用者必须检查或者使用返回值,否则编译器会发出警告提示

- (BOOL)availiable __attribute__((warn_unused_result))
{
   return 10;
}

警告如下:

www.9778.com 4

Paste_Image.png

PRINT_XN(1);        // becomes printf(“x1 = %dn”, x1);

  • 在 procedural
    macros 中生成 macro_rules! items

这些函数的语法如下:
(vlax-property-available-p object property) 
(vlax-method-applicable-p object method) 

objc_subclassing_restricted

因为某些原因,我们不希望这个类被继承,也就是 “最终”的类,用法如下:

__attribute__((objc_subclassing_restricted))
@interface ViewController : UIViewController


@end

如果继承了这个类,编译器会报错

www.9778.com 5

Paste_Image.png

PRINT_XN(2);        // becomes printf(“x2 = %dn”, x2);

目前,函数式(mac!())和属性(#[mac])macros
都可以生成macro_rules!项目。

属性是跟与之关联的对象的类型相关。例如,一个Circle(圆)对象有一个Diameter(直径)属性,但Line
(线)对象就没有。根据不同的对象类型,属性也各不一样,下面的代码在拾取CIRCLE(圆)图元时会出错:

objc_requires_super

这个属性要求子类在重写父类的方法时,必须要重载父类方法,也就是调用super方法,否则警告.示例如下:

@interface ViewController : UIViewController

- (void)jump __attribute__((objc_requires_super));

@end

- (void)jump{
    NSLog(@"父类必须先执行");
}


@interface SGViewController : ViewController

@end

@implementation SGViewController
- (void)jump{
    NSLog(@"子类才能再执行");
}
@end

警告如下:

www.9778.com 6

Paste_Image.png

    return 0;

  • The $m:meta 匹配器支持 arbitrary
    token-stream
    values.

(if (setq ent (entsel
“n选择对象以获取其属性: “))
  (progn
    (setq obj (vlax-ename->vla-object (car ent)))
    (princ
      (strcat “n长度: ” (vla-get-Length obj))
    )
  )
)

objc_boxable

实现类似于NSNumber
的快速打包能力@(…),一般对于struct,union我们只能通过NSValue将其打包.
objc_boxable 可以帮助我们实现快速打包,示例如下:

//自定义结构体
typedef struct __attribute__((objc_boxable)){
    CGFloat x,y,width,height;
}SGRect;

 SGRect rect = {0,0,100,200};
 //这里直接打包成NSValue
 NSValue *value = @(rect);

 //这里我直接用系统的方法打印
 NSLog(@"%@",NSStringFromCGRect(value.CGRectValue));

 输出:
 2016-07-21 21:28:43.538 Study[14118:5408921] {{0, 0}, {100, 200}}

这样SGRect就具备快速打包功能了.

}

也就是说,以下内容现在有效:

但是,如果在取对象的属性前先检验该属性是否有效时,代码的执行就能正常,如下面的例子所示:

constructor / destructor

意思是:
构造器和析构器;constructor修饰的函数会在main函数之前执行,destructor修饰的函数会在程序exit前调用.
示例如下:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        NSLog(@"main");
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

__attribute__((constructor))
void  before(){
    NSLog(@"before main");
}

__attribute__((destructor))
void  after(){
    NSLog(@"after main");
}

//在viewController中调用exit
- (void)viewDidLoad {
    [super viewDidLoad];

    exit(0);
}
输出如下:

2016-07-21 21:49:17.446 Study[14162:5415982] before main
2016-07-21 21:49:17.447 Study[14162:5415982] main
2016-07-21 21:49:17.534 Study[14162:5415982] after main

注意点:

  • 程序退出的时候才会调用after函数,经测试,手动退出程序会执行
  • 上面两个函数不管写在哪个类里,哪个文件中效果都一样
  • 如果存在多个修饰的函数,那么都会执行,顺序不定

实际上如果存在多个修饰过的函数,可以它们的调整优先级
代码如下:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        NSLog(@"main");
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

__attribute__((constructor(101)))
void  before1(){
    NSLog(@"before main - 1");
}
__attribute__((constructor(102)))
void  before2(){
    NSLog(@"before main - 2");
}

__attribute__((destructor(201)))
void  after1(){
    NSLog(@"after main - 1");
}
__attribute__((destructor(202)))
void  after2(){
    NSLog(@"after main - 2");
}

输出结果如下:
2016-07-21 21:59:35.622 Study[14171:5418393] before main - 1
2016-07-21 21:59:35.624 Study[14171:5418393] before main - 2
2016-07-21 21:59:35.624 Study[14171:5418393] main
2016-07-21 21:59:35.704 Study[14171:5418393] after main - 2
2016-07-21 21:59:35.704 Study[14171:5418393] after main - 1

注意点:

  • 括号内的值表示优先级,[0,100]这个返回时系统保留的,自己千万别调用.
  • 根据输出结果可以看出,main函数之前的,数值越小,越先调用;main函数之后的数值越大,越先调用.

当函数声明和函数实现分开写时,格式如下:

static void before() __attribute__((constructor));

static void before() {
    printf("beforen");
}

讨论:+load,constructor,main的执行顺序,代码如下:

+ (void)load{
    NSLog(@"load");
}
__attribute__((constructor))
void  before(){
    NSLog(@"before main");
}

输出结果如下:
2016-07-21 22:13:58.591 Study[14185:5421811] load
2016-07-21 22:13:58.592 Study[14185:5421811] before main
2016-07-21 22:13:58.592 Study[14185:5421811] main

可以看出执行顺序为:
load->constructor->main
为什么呢?
因为 dyld(动态链接器,程序的最初起点)在加载 image(可以理解成 Mach-O
文件)时会先通知 objc runtime 去加载其中所有的类,每加载一个类时,它的
+load 随之调用,全部加载完成后,dyld 才会调用这个 image 中所有的
constructor 方法,然后才调用main函数.

Here’s the output:

macro_rules! accept_meta { ($m:meta) => {} }
accept_meta!( my::path );
accept_meta!( my::path = "lit" );
accept_meta!( my::path ( a b c ) );
accept_meta!( my::path [ a b c ] );
accept_meta!( my::path { a b c } );

(if (setq ent (entsel
“n选择对象以获取其属性: “))
  (progn
    (setq obj (vlax-ename->vla-object (car ent)))
    (if (vlax-property-available-p obj ‘Length)
      (princ
        (strcat “n长度: ” (vla-get-Length obj))
      )
      (princ “n对象没有 LENGTH 属性…”)
    )
  )
)

enable_if

用来检查参数是否合法,只能用来修饰函数:

void printAge(int age)
__attribute__((enable_if(age > 0  && age < 120, "你丫太监?")))
{
    NSLog(@"%d",age);
}

表示只能输入的参数只能是 0 ~ 120左右,否则编译报错
报错如下:

www.9778.com 7

Paste_Image.png

x1 = 14

标准库中增加的 const fn

不幸的是,没有直接的方法获取给定对象的所有属性的列表来用于遍历编程。不过,你还是可以获取列表信息的,这样对你的帮助也不算小。

cleanup

声明到一个变量上,当这个变量作用域结束时,调用指定的一个函数.如果不知道什么是作用域,请先学习一下.例子:

//这里传递的参数是变量的地址
void intCleanup(int *num){
    NSLog(@"cleanup------%d",*num);
}

- (void)test{
  int a __attribute__((cleanup(intCleanup))) = 10;
}

输出结果为:
2016-07-22 09:59:09.139 Study[14293:5495713] cleanup------10

注意点:

  • 指定的函数传递的参数是变量的地址
  • 作用域的结束包括:大括号结束、return、goto、break、exception等情况
  • 当作用域内有多个cleanup的变量时,遵守 先入后出 的栈式结构.

示例代码:

void intCleanup(int *num){
    NSLog(@"cleanup------%d",*num);
}

void stringCleanup(NSString **str){
    NSLog(@"cleanup------%@",*str);
}

void rectCleanup(CGRect *rect){
    CGRect temp = *rect;
    NSString *str = NSStringFromCGRect(temp);
    NSLog(@"cleanup------%@",str);
}


 int a __attribute__((cleanup(intCleanup))) = 10;
    {
        NSString *string __attribute__((cleanup(stringCleanup))) = @"string";
        CGRect rect __attribute__((cleanup(rectCleanup))) = {0,0,1,1};
    }


    输出结果为:
    2016-07-22 10:09:36.621 Study[14308:5498861] cleanup------{{0, 0}, {1, 1}}
2016-07-22 10:09:36.622 Study[14308:5498861] cleanup------string
2016-07-22 10:09:36.622 Study[14308:5498861] cleanup------10

讨论:如果修饰了某个对象,那么cleanup和dealloc,谁先执行?
测试代码如下:

void objectCleanup(NSObject **obj){
    NSLog(@"cleanup------%@",*obj);
}

- (void)viewDidLoad {
    [super viewDidLoad];
    ViewController *vc __attribute__((cleanup(objectCleanup))) = [[ViewController alloc] init];
}

- (void)dealloc{
    NSLog(@"dealloc");
}

输出结果如下:
2016-07-22 10:23:08.839 Study[14319:5502769] cleanup------<ViewController: 0x13fe881e0>
2016-07-22 10:23:08.840 Study[14319:5502769] dealloc

可以明显看出,cleanup先于对象的dealloc执行.

  • 在block中的用法:在block中使用,先看例子:

//指向block的指针,觉得不好理解可以用typeof
void blockCleanUp(void(^*block)()){
    (*block)();
}

 void (^block)(void) __attribute__((cleanup(blockCleanUp))) = ^{
        NSLog(@"finish block");
    };

这个好处就是,不用等到block最后才写某些代码,我们可以把它放在block的任意位置,防止忘记.

x2 = 20

此版本中,以下函数成为const fn

要查询在给定的对象上有什么属性和方法,可在该对象上使用(vlax-dump-object)函数。函数的语法是(vlax-dump-object object
show-methods), show-methods参数可以是nil或非nil。如果是非nil,就显示对象支持的方法,否则就不显示方法。

overloadable

用于c语言函数,可以定义若干个函数名相同,但参数不同的方法,调用时编译器会自动根据参数选择函数原型:

__attribute__((overloadable)) void print(NSString *string){
    NSLog(@"%@",string);
}

__attribute__((overloadable)) void print(int num){
    NSLog(@"%d",num);
}

//调用
print(10);
print(@"哈哈");

Note how the PRINT_XN() macro uses the # operator to combine strings
and the ## operator to combine tokens into a new identifier.

  • is_power_of_two用于无符号整数

图 3-2 – Documents集合的属性和方法 

objc_runtime_name

看到runtime是不是就感觉高大上,没错这个也跟运行时有关.作用是将将类或协议的名字在编译时指定成另一个.示例如下:

__attribute__((objc_runtime_name("NSObject")))
@interface SGObject :NSObject

@end

 //调用
 NSLog(@"%@",[SGObject class]);
 //输出
 2016-07-22 11:18:00.934 Study[14355:5516261] NSObject

可以用来做代码混淆.

更多请看官网:
https://gcc.gnu.org/onlinedocs/gcc-4.0.0/gcc/Function-Attributes.html

 

增加到标准库的函数

图3-2显示了Documents集合对象的属性和方法。你可看到输出的第一行显示了带有一个描述的内部对象参照(IAcadDocuments),并且列出了可用的属性和方法。

2.Variadic Macros: … and _ _VA_ARGS_ _

以下函数和宏已经稳定:

提示! 以下的命令定义对显示所选图元的属性和方法会有用处。它没有提供出错处理,但是它仍是个有用的小工具。

Some functions, such as printf(), accept a variable number of arguments.
The stdvar.h header file,provides tools for creating user-defined
functions with a variable number of arguments. And C99 does the same
thing for macros.Although not used in the standard, the word variadic
has come into currency to label this facility. (However, the process
that has added stringizing and variadic to the C vocabulary has not yet
led to labeling functions or macros with a fixed number of arguments as
fixadic functions and normadic macros.)

  • todo!()

  • slice::repeat

  • mem::take

  • BTreeMap::get_key_value 和 HashMap::get_key_value

  • Option::as_deref, Option::as_deref_mut

  • Option::flatten

  • UdpSocket::peer_addr

  • {f32,f64}::to_be_bytes{f32,f64}::to_le_bytes{f32,f64}::to_ne_bytes{f32,f64}::from_be_bytes{f32,f64}::from_le_bytes,和{f32,f64}::from_ne_bytes

(defun C:DUMP (/ ent obj)
  (while (setq ent (entsel “n选择图元以获取对象数据: “))
    (setq obj (vlax-ename->vla-object (car ent)))
    (vlax-dump-object obj T)
    (vlax-release-object obj)
  )
  (princ)
)

The idea is that the final argument in an argument list for a macro
definition can be ellipses (that is, three periods)(省略号). If so,
the predefined macro _ _VA_ARGS_ _ can be used in the substitution
part to indicate what will be substituted for the ellipses. For example,
consider this definition:

详情可查看更新说明:

提示! 属性后面的括号(RO)代表该属性为只读,在这里,所有的属性均为只读,方法后面括号内的数字表示了每一方法所需要的参数数目。

#define PR(…) printf(_ _VA_ARGS_ _)

要访问属性,可使用(vla-get-xxx)函数,或更通用的(vlax-get-Property)函数,两者都可以。第一个函数的语法是(vla-get-xxx
object),其中XXX是属性名。在使用(vlax-get-property)函数时,语法为(vlax-get-property object
propertyname),其中propertyname可以是双引号字符串或单引号字符串。

Suppose you later invoke the macro like this:

(文/开源中国)    

(vlax-get-property object property) 或 
(vla-get-property object) 或 
(vlax-get object property) 

PR(“Howdy”);

返回值是分配给该对象的指定名称属性的值,如果该对象的指定属性不存在,就会产生错误。举例来说,如果你对Line(直线)图元查询“Diameter”(直径)属性,就会产生错误。

PR(“weight = %d, shipping = $%.2fn”, wt, sp);

参数: 
Object – 一个 vla-对象 
Property – 与对象相关的有效属性。 

For the first invocation, _ _VA_ARGS_ _ expands to one argument:

示例: 
(vlax-get-property objLine “Length”) 
(vlax-get-property objLine ‘Length) 
(vla-get-Length objLine) 

“Howdy”

所有这些语句都将完成同样的事情。

For the second invocation, it expands to three arguments:

属性的名称并不要求区分大小写,但本书的例子都将首字母大写,以示清晰。总的来说,你会发现以上前两项都很好用,但也有情况是要求用到后两项。特别是涉及到象Microsoft  Excel
或 Word这样的外部应用程序。第四种方式 vlax-get 是R14版遗留下来的,只为了向后兼容而已。

“weight = %d, shipping = $%.2fn”, wt, sp

使用方法
通过图3-2的例子,你可看到Documents集合对象支持4种方法:Add、Close、Item和Open。Item方法需要使用一个参数(也就是在图3-2中显示在方法后面的那个(1)),它是由集合所引出的文档的索引和命名。

Thus, the resulting code is this:

通常Item方法有一个有趣的特性,那就是它可以接受字符串或整数值参数。当给的是整数参数时,它就简单地返回集合的第(nth)条目,0为第一条目。当给的是一个字符串值时,它尝试通过它的名称属性来取得条目。Item
(名称)方法不分大小写,这是相当有用的,它不需要先对字符串进行大小写转换就可以接受名称。

printf(“Howdy”);

提示! 如果你熟悉Visual
Basic或VBA并经常使用默认方法和默认属性,那你要注意,这种特性在Visual
LISP是不存在的。例如,在Visual Basic中,通过以下任意一种途径来访问Item方法都是可行的:
Object.Item(12) 或 Object(12) 或
Object(“Name”) 

printf(“weight = %d, shipping = $%.2fn”, wt, sp);

这是因为在VB或VBA中,Item方法是大多数对象的默认方法。Visual
LISP并不支持这种特性,它需要在每次使用时都阐明所有属性和方法。例如:

Listing 2 shows a slightly more ambitious example that uses string
concatenation and the # operator:

(vlax-invoke-method documents “Item”
12) 会有效果… 
(vla-item documents
“Drawing1.dwg”) 会有效果… 
(vlax-invoke-method documents
12) 不会有效果。 

 

使用图3-2的例子,Item方法可通过以下任何方法使用:

// variadic.c — variadic macros

(vla-Item documents 1) 
(vla-Item documents “Drawing1.dwg”) 
(vlax-invoke-method documents “Item” 1) 
(vlax-invoke-method documents ‘Item “Drawing1.dwg”) 

#include <stdio.h>

(vlax-invoke-method object method [arguments]…) 或 
(vla-method object arguments) 或 
(vlax-invoke object method [arguments] …) 

#include <math.h>

调用关联于对象的方法并为方法提供任何所需的参数。如果成功,将返回结果。如果对象没有提供对应的方法,将会产生ActiveX错误。例如,在Text(文本)图元中调用“Offset”(偏移)方法,将会产生ActiveX错误。

#define PR(X, …) printf(“Message” #X “: ” _ _VA_ARGS_ _)

参数: 
Object – 一个vla-对象 
Method – 由对象显露的方法 
Arguments – 支持该方法的任何所需的参数 

int main(void)

示例: 
(vlax-invoke-method objLine “Move” point1
point2) 
(vla-Move objLine point1 point2) 
(vlax-invoke objLine “Move” point1 point2) 

{

所有这些示例做的事情都一样。对于大部分AutoCAD对象来说都是可行的,但对于由TypeLib接口或外部应用程序或ActiveX组件输入创建的对象则不都可行。你可以将第一种方法用于外部应用程序对象,而可以将第二种方法用于内部对象。第三种方法是为了R14的向后兼容。

    double x = 48;

提示! 当你选择对属性和方法用Get或Put中的任意一种形式时,你会发现用较长的形式(如vlax-put-property)比用较短的形式(如vla-put-color)更灵活。原因是通过从函数中分离出属性名称,你就可以定义函数和循环语句,它们可以接受一个属性列表及相关的数值,例如…

    double y;

(defun MapPropertyList (object proplist)
  (foreach propset proplist
    (if (vlax-property-available-p object (car propset))
      (vlax-put-property object (car propset) (cadr propset))
    )
  )
)

    y = sqrt(x);

当试图在方法上使用这种方式时要特别小心,因为方法的参数列表会随着对象和方法的变化而变化。有一些方法不需要参数,而另一些则随长度变化。

    PR(1, “x = %gn”, x);

数据类型

    PR(2, “x = %.2f, y = %.4fn”, x, y);

数据类型是用于描述给定对象或属性可包含的数值的类型。数据类型的例子有整数、双精度、货币、日期、字符串等等。在AutoLISP已经享受“类型无关”多年后,Visual
LISP也同样可以,但不单只这样。在AutoCAD里,你可以尽可能地通过AutoLISP来保留这种类型无关,但当涉及其它应用程序时,例如Microsoft
Excel,你将不可避免而且明智地用到数据类型。

    return 0;

类型无关并非免费午餐,其代价是效率低下。当你提前声明了数据类型,你就通知编译器调动仅仅够用的资源来匹配预定数据。例如,存储整数类型的数据远比存储“长”数据值要低得多。当你未使用数据类型时,所有数据都会自动分配尽可能长的数据类型,以确保能匹配所有可能出现的数据。其结果导致应用程序消耗的资源远比它的需求大得多,无论是从初始加载的尺寸或者其运行时间资源的分配。

}

这就是为什么程序开发的语言中,象C++、Java,甚至是Visual
Basic通常都是比较快的(相比使用自由类型语言编程的同样功能)。他们确保能提前精简处理以保证运行时有更快的性能。AutoLISP并不这样做,因此是相对慢的处理语言。而Visual
LISP相对会快一些,但只有你利用其新的特性,让其最尽可能发挥。

In the first macro call, X has the value 1, so #X becomes “1”. That
makes the expansion look like this:

常量和枚举
常量是特殊的数据类型。正如其名,它的值是不可改变的。有时被称为静态。通常来说,常量是由程序语言或由托管应用程序本身提供,作为一种方便的手段。例如,acByLayer常量在属性值中可以被256代替。名称值比整数值更好明白也更好记。例如,以下两个语句在功能上说是一致的。

(#为参数加双引号。)

(vla-put-color object acByLayer) 
(vla-put-color object 256) 

print(“Message ” “1” “: ” “x = %gn”, x);

枚举是常量的逻辑组合,它用来确定常量值范围。例如你可以用颜色值1、2、3、4、5,但也可以将之常量化为acRed、acYellow、acGreen、acCyan、acBlue、acMagenta和acWhite,这样更清晰。这种类型的相关常量值范围即称为枚举。参看附录A为标准的AutoCAD枚举清单。

Then the four strings are concatenated, reducing the call to this:

提示!并不是所有的ActiveX枚举都在Visual
LISP中有提供。例如,标准的Decimal(十进制)或Short(短)数据类型并没有镜像为vlax-vbDecimal或vlax-vbShort。请参阅第六章学习更多关于数据类型的信息。

print(“Message 1: x = %gn”, x);

变体和安全数组
在上节的数据类型中,有提到对自由类型声明使用最大的可用配额,例如在AutoLISP中的(setq)语句。事实上,是分配了一个变体数据类型,变体包括所有的数据类型,它提供了足够的资源空间去包含任何其它数据类型,可以是数值、日期、字符串或其它。事实上,变体数据类型是ActiveX产生出来的。但这个概念更具有一般性,在ActiveX之前已经存在很长的时间。

Here’s the output:

事实上Visual
LISP包含了将所有ActiveX数据转换为带有指示包含哪类类型的区分符的变体。感觉有点乱,但事实很简单。该容器是一个包含有货币数据类型值的变体,当你分配给对象一个新值时,你必须提供区分符以使得这个数据能够正确储存。这是必须要做的,尤其是你在AutoCAD和其它应用程序(如Microsoft
Excel)之间传递数值时。

Message 1: x = 48

除了发送数值外,你还可以从变体值中查询嵌套的数据类型,也可将值正确的转换成为相关的LISP数据类型。例如,你可查询变体对象值是否包含了双精度值。然后你可以在LISP中将这个数值读取为实数(REAL)数据类型。Visual
LISP提供了大量的函数用于在LISP环境中创建、读取、修改变体量数据值。

Message 2: x = 48.00, y = 6.9282

安全数组相当于AutoLISP中的LIST对象。主要的区别在于它是静态的。意思就是说他不可能根据他们能存储的数字的多少来加长或改变项数。这将避免了在试图分配或取得超出数组长度的元素时产生的不必要的错误。这就是它们之所以被称为“安全”的原因。任何传递到ActiveX的LIST结构必须先转换成安全数组。任何从ActiveX对象获得的LIST面向对象的数据都必须通过象(car)、(nth)、(accoc)、(mapcar)、(member)等的LISP函数转换成LIST数据类型。Visual
LISP为创造操作、阅读安全数组数据值提供了大量的功能。

Don’t forget, the ellipses have to be the last macro argument:

更多关于变体和安全数组的内容,请参阅第六章。

#define WRONG(X, …, Y) #X #_ _VA_ARGS_ _
#y(这个是错误的例子。)

命名空间
命名空间是分配用于运行过程的虚拟空间,它与空间中的其它资源相互配合。但它有时也可以和其它命名空间中的其它过程相联系。可以把命名空间想象成卧室。你的应用程序就如在卧室工作的人,也即在特定命名空间的过程。其它的应用程序就如在邻近卧室(命名空间)工作的一样,这两个人可以相互独立和隔离的,但他们也可以互相传递数据以便沟通。这就是命名空间工作的原理。

 

使用命名空间的好处在于可以在特定的命名空间中的过程与另外命名空间相独立的,这样可以在它们之间不相互破坏(如在争夺保存资源时)。它们也可直接通过它们的命名空间加载和卸载过程。换句话说,这有点象拆掉房子里一间卧室,而房子就如模块化形式建的一样。移去一个房间不会影响到其它房间或过程的生存。

 

可能使用命名空间最大的短处是会导致在操作系统或其宿主应用程序上的一些开销。为了管理好给定的命名空间,它必须有自己的内存地址范围和指针分配。这需要消耗额外的资源去跟踪和控制命名空间,这反过来又在必要时提供了直接的方法去访问它、卸载它或暂停它。

 

AutoCAD提供了在Visual
LISP内部对命名空间的内部管理方法,同样在ObjectARX和VBA中也可以。这是Visual
LISP比AutoLISP功能强大改进的又一表现。实际上每个打开的文档都是其自己的命名空间(如果你不是在单文件模式下工作的话),当在一个图形中设置一个变量而又试图在另一个图形中读取它时,这个影响就会看到。是有办法可以实现在这些图形中传递变量的,我们第十章将会谈到。

 

接口和类型库
接口是用于连接其它ActiveX过程或构件的对象模型的手段。当你想利用其它应用程序的特定的属性、常量或方法时,你首先要定义一个接口去加载那个目标应用的对象模型。例如,有时你想通过Visual
LISP来利用Excel自己的工具直接用Microsoft
Excel来保存一些AutoCAD信息到电子表格文件。这就需要定义接口,然后就可使用类型库

建议你看看C语言相关的预处理命令部分
对于这 #define 是宏定义命令,分为无参数宏定义 和 有参数宏定义,你这个属于有参数的宏定义;
对于有参数的宏定义,参数部分 应该为 要替换为的部分的变量;
你这里 #define dbg_msg(...)   参数部分 是 ... 没有这种用法
例如:
#include<stdio.h>
#define dbg_msg(__FUNCTION__,__LINE__,__VA_ARGS__)  {printf("### [%s:%d] ", __FUNCTION__, __LINE__); printf(__VA_ARGS__);}

void main()
{
    char a[] = "123";
    int b = 5;
    char c[]="456"; 
    dbg_msg(a,b,c);  //宏定义要替换的部分,在编译前预处理器就会将这个地方替换为 目标字符串,之后才进行编译
}

要使用类型库,就必须将其加载到内存,并确定接口指针已经定义。Visual
LISP提供了一毓函数用于加载和配置类型库接口。

 

(vlax-import-type-library 
  :tlb-filename name string 
  :methods-prefix    string 
  :properties-prefix string 
  :constants-prefix  string 

 

输入类型库参照到当前命名空间中。

上面的宏是使用qDebug输出调试信息,在非Qt的程序中也可以改为printf,守护进程则可以改为syslog等等…
其中,决窍其实就是这几个宏 ##__VA_ARGS__, __FILE__,
__LINE__ 和__FUNCTION__,下面介绍一下这几个宏:
  1) __VA_ARGS__
是一个可变参数的宏,很少人知道这个宏,这个可变参数的宏是新的C99规范中新增的,目前似乎只有gcc支持(VC6.0的编译器不支持)。宏前面加上##的作用在于,当可变参数的个数为0时,这里的##起到把前面多余的”,”去掉的作用,否则会编译出错,
你可以试试。
  2) __FILE__ 宏在预编译时会替换成当前的源文件名
  3) __LINE__宏在预编译时会替换成当前的行号
  4) __FUNCTION__宏在预编译时会替换成当前的函数名称

参数: 
:tlb-filename string – (字符串) 为类型库文件的路径和文件名 
:methods-prefix string – (字符串) 为任意前缀字符串标识符 
:properties-prefix string – (字符串) 为任意前缀字符串标识符
:constants-prefix string – (字符串) 为任意前缀字符串标识符

 

示例: 
(vlax-import-type-library
  :tlb-filename             “c:\myfiles\typelibs\tlfile.tlb”
  :methods-prefix           “dsxm-“
  :properties-prefix        “dsxp-“
  :constants-prefix         “dsxc-“
)

 

该示例输入了由tlfile.tlb文件定义的外部应用程序或控件的类型库接口。其它的参数定义了类型库接口所暴露的方法、属性和常量的前缀。

#、##和__VA_ARGS__  

 

1.#
假如希望在字符串中包含宏参数,ANSI
C允许这样作,在类函数宏的替换部分,#符号用作一个预处理运算符,它可以把语言符号转化程字符串。例如,如果x是一个宏参量,那么#x可以把参数名转化成相应的字符串。该过程称为字符串化(stringizing).
#incldue <stdio.h>
#define PSQR(x) printf(“the square of” #x “is %d.n”,(x)*(x))
int main(void)
{
    int y =4;
    PSQR(y);
    PSQR(2+4);
    return 0;
}
输出结果:
the square of y is 16.
the square of 2+4 is 36.
第一次调用宏时使用“y”代替#x;第二次调用时用“2+4″代#x。
2.##
##运算符可以用于类函数宏的替换部分。另外,##还可以用于类对象宏的替换部分。这个运算符把两个语言符号组合成单个语言符号。例如:
#define XNAME(n) x##n
这样宏调用:
XNAME(4)
展开后:
x4
程序:
#include <stdio.h>
#define XNAME(n) x##n
#define PXN(n) printf(“x”#n” = %dn”,x##n)
int main(void)
{
    int XNAME(1)=12;//int x1=12;
    PXN(1);//printf(“x1 = %dn”, x1);
    return 0;
}
输出结果:
x1=12
3.可变参数宏 …和_ _VA_ARGS_ _
__VA_ARGS__
是一个可变参数的宏,很少人知道这个宏,这个可变参数的宏是新的C99规范中新增的,目前似乎只有gcc支持(VC6.0的编译器不支持)。
实现思想就是宏定义中参数列表的最后一个参数为省略号(也就是三个点)。这样预定义宏_
_VA_ARGS_
_就可以被用在替换部分中,替换省略号所代表的字符串。比如:
#define PR(…) printf(__VA_ARGS__)
int main()
{
    int wt=1,sp=2;
    PR(“hellon”);
    PR(“weight = %d, shipping = %d”,wt,sp);
    return 0;
}
输出结果:
hello
weight = 1, shipping = 2
省略号只能代替最后面的宏参数。
#define W(x,…,y)错误!

 

 

 

 

 

 

 

 

较大的项目都会用大量的宏定义来组织代码,你可以看看/usr/include下面的头文件中用
了多少个宏定义。看起来宏展开就是做个替换而已,其实里面有比较复杂的规则,C语言有很多复杂但不常用的语法规则本书并不涉及,但有关宏展开的语法规则本
节却力图做全面讲解,因为它很重要也很常用。 
2.1. 函数式宏定义 
以前我们用过的#define N 20或#define STR “hello,
world”这种宏定义可以称为变量式宏定义(Object-like
Macro),宏定义名可以像变量一样在代码中使用。另外一种宏定义可以像函数调用一样在代码中使用,称为函数式宏定义(Function-like
Macro)。例如编辑一个文件main.c: 

 

#define MAX(a, b) ((a)>(b)?(a):(b))
k = MAX(i&0x0f, j&0x0f)

我们想看第二行的表达式展开成什么样,可以用gcc的-E选项或cpp命令,尽管这个C程序不合语法,但没关系,我们只做预处理而不编译,不会检查程序是否符合C语法。 

 

www.9778.com 8

$ cpp main.c
# 1 "main.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "main.c"

k = ((i&0x0f)>(j&0x0f)?(i&0x0f):(j&0x0f))

www.9778.com 9

 

就像函数调用一样,把两个实参分别替换到宏定义中形参a和b的位置。注意这种函数式宏定义和真正的函数调用有什么不同: 
1、函数式宏定义的参数没有类型,预处理器只负责做形式上的替换,而不做参数类型检查,所以传参时要格外小心。 
2、调用真正函数的代码和调用函数式宏定义的代码编译生成的指令不同。如果MAX是个真正的函数,那么它的函数体return
a > b ? a :
b;要编译生成指令,代码中出现的每次调用也要编译生成传参指令和call指令。而如果MAX是个函数式宏定义,这个宏定义本身倒不必编译生成指令,但是
代码中出现的每次调用编译生成的指令都相当于一个函数体,而不是简单的几条传参指令和call指令。所以,使用函数式宏定义编译生成的目标文件会比较大。 
3、定义这种宏要格外小心,如果上面的定义写成#define MAX(a, b)
(a>b?a:b),省去内层括号,则宏展开就成了k =
(i&0x0f>j&0x0f?i&0x0f:j&0x0f),运算的优先级就错了。同样道理,这个宏定义的外层
括号也是不能省的,想一想为什么。 
4、调用函数时先求实参表达式的值再传给形参,如果实参表达式有Side
Effect,那么这些Side Effect只发生一次。例如MAX(++a,
++b),如果MAX是个真正的函数,a和b只增加一次。但如果MAX是上面那样的宏定义,则要展开成k
= ((++a)>(++b)?(++a):(++b)),a和b就不一定是增加一次还是两次了。 
5、即使实参没有Side
Effect,使用函数式宏定义也往往会导致较低的代码执行效率。下面举一个极端的例子,也是个很有意思的例子。 
例 21.1. 函数式宏定义 

 

www.9778.com 10

#define MAX(a, b) ((a)>(b)?(a):(b))

int a[] = { 9, 3, 5, 2, 1, 0, 8, 7, 6, 4 };

int max(int n)
{
    return n == 0 ? a[0] : MAX(a[n], max(n-1));
}

int main(void)
{
    max(9);
    return 0;
}

www.9778.com 11

 

这段代码从一个数组中找出最大的数,如果MAX是个真正的函数,这个算法就是从前到后遍历一遍数组,时间复杂度是Θ(n),而现在MAX是这样一个函数式宏定义,思考一下这个算法的时间复杂度是多少? 
尽管函数式宏定义和真正的函数相比有很多缺点,但只要小心使用还是会显著提高代码的执行效率,毕竟省去了分配和释放栈帧、传参、传返回值等一系列工作,因
此那些简短并且被频繁调用的函数经常用函数式宏定义来代替实现。例如C标准库的很多函数都提供两种实现,一种是真正的函数实现,一种是宏定义实现,这一点
以后还要详细解释。 
函数式宏定义经常写成这样的形式(取自内核代码include/linux/pm.h): 

 

#define device_init_wakeup(dev,val) 
        do { 
                device_can_wakeup(dev) = !!(val); 
                device_set_wakeup_enable(dev,val); 
        } while(0)

 

为什么要用do { … } while(0)括起来呢?不括起来会有什么问题呢? 

 

www.9778.com 12

#define device_init_wakeup(dev,val) 
                device_can_wakeup(dev) = !!(val); 
                device_set_wakeup_enable(dev,val);

if (n > 0)
    device_init_wakeup(d, v);

www.9778.com 13

 

这样宏展开之后,函数体的第二条语句不在if条件中。那么简单地用{ …
}括起来组成一个语句块不行吗? 

 

www.9778.com 14

#define device_init_wakeup(dev,val) 
                { device_can_wakeup(dev) = !!(val); 
                device_set_wakeup_enable(dev,val); }

if (n > 0)
    device_init_wakeup(d, v);
else
    continue;

www.9778.com 15

 

问题出在device_init_wakeup(d,
v);末尾的;号,如果不允许写这个;号,看起来不像个函数调用,可如果写了这个;号,宏展开之后就有语法错误,if语句被这个;号结束掉了,没法跟
else配对。因此,do { … } while(0)是一种比较好的解决办法。 
如果在一个程序文件中重复定义一个宏,C语言规定这些重复的宏定义必须一模一样。例如这样的重复定义是允许的: 

 

#define OBJ_LIKE (1 - 1)
#define OBJ_LIKE /* comment */ (1/* comment */-/* comment */  1)/* comment */

 

在定义的前后多些空白(这里的空白包括空格、Tab、注释,因为前一步预处理要把注释替换成空格)没有关系,在定义中间连续多个空白等价于一个空白,但在定义中间有空白和没有空白被认为是不同的,所以这样的重复定义是不允许的: 

 

#define OBJ_LIKE (1 - 1)
#define OBJ_LIKE (1-1)

 

如果需要重新定义一个宏,和原来的定义不同,可以先用#undef取消原来的定义,再重新定义,例如: 

 

www.9778.com 16

#define X 3
... /* X is 3 */
#undef X
... /* X has no definition */
#define X 2
... /* X is 2 */

www.9778.com 17

 

2.2. 内联函数  
C99引入一个新关键字inline,用于定义内联函数(inline
function)。这种用法在内核代码中很常见,例如include/linux/rwsem.h中: 

 

www.9778.com 18

static inline void down_read(struct rw_semaphore *sem)
{
        might_sleep();
        rwsemtrace(sem,"Entering down_read");
        __down_read(sem);
        rwsemtrace(sem,"Leaving down_read");
}

www.9778.com 19

 

inline关键字告诉编译器,这个函数的调用要尽可能快,可以当普通的函数调用实现,也可以用宏展开的办法实现。我们做个实验,把上一节的例子改一下: 
例 21.2. 内联函数 

 

www.9778.com 20

inline int MAX(int a, int b)
{
    return a > b ? a : b;
}

int a[] = { 9, 3, 5, 2, 1, 0, 8, 7, 6, 4 };

int max(int n)
{
    return n == 0 ? a[0] : MAX(a[n], max(n-1));
}

int main(void)
{
    max(9);
    return 0;
}

www.9778.com 21

 

按往常的步骤编译然后反汇编: 

 

www.9778.com 22

$ gcc main.c -g
$ objdump -dS a.out
...
int max(int n)
{
 8048369:       55                      push   %ebp
 804836a:       89 e5                   mov    %esp,%ebp
 804836c:       83 ec 0c                sub    $0xc,%esp
        return n == 0 ? a[0] : MAX(a[n], max(n-1));
 804836f:       83 7d 08 00             cmpl   $0x0,0x8(%ebp)
 8048373:       75 0a                   jne    804837f <max+0x16>
 8048375:       a1 c0 95 04 08          mov    0x80495c0,%eax
 804837a:       89 45 fc                mov    %eax,-0x4(%ebp)
 804837d:       eb 29                   jmp    80483a8 <max+0x3f>
 804837f:       8b 45 08                mov    0x8(%ebp),%eax
 8048382:       83 e8 01                sub    $0x1,%eax
 8048385:       89 04 24                mov    %eax,(%esp)
 8048388:       e8 dc ff ff ff          call   8048369 <max>
 804838d:       89 c2                   mov    %eax,%edx
 804838f:       8b 45 08                mov    0x8(%ebp),%eax
 8048392:       8b 04 85 c0 95 04 08    mov    0x80495c0(,%eax,4),%eax
 8048399:       89 54 24 04             mov    %edx,0x4(%esp)
 804839d:       89 04 24                mov    %eax,(%esp)
 80483a0:       e8 9f ff ff ff          call   8048344 <MAX>
 80483a5:       89 45 fc                mov    %eax,-0x4(%ebp)
 80483a8:       8b 45 fc                mov    -0x4(%ebp),%eax
}
...

www.9778.com 23

 

可以看到MAX是作为普通函数调用的。如果指定优化选项编译,然后反汇编: 

www.9778.com 24

$ gcc main.c -g -O
$ objdump -dS a.out
...
int max(int n)
{
 8048355:       55                      push   %ebp
 8048356:       89 e5                   mov    %esp,%ebp
 8048358:       53                      push   %ebx
 8048359:       83 ec 04                sub    $0x4,%esp
 804835c:       8b 5d 08                mov    0x8(%ebp),%ebx
        return n == 0 ? a[0] : MAX(a[n], max(n-1));
 804835f:       85 db                   test   %ebx,%ebx
 8048361:       75 07                   jne    804836a <max+0x15>
 8048363:       a1 a0 95 04 08          mov    0x80495a0,%eax
 8048368:       eb 18                   jmp    8048382 <max+0x2d>
 804836a:       8d 43 ff                lea    -0x1(%ebx),%eax
 804836d:       89 04 24                mov    %eax,(%esp)
 8048370:       e8 e0 ff ff ff          call   8048355 <max>
inline int MAX(int a, int b)
{
        return a > b ? a : b;
 8048375:       8b 14 9d a0 95 04 08    mov    0x80495a0(,%ebx,4),%edx
 804837c:       39 d0                   cmp    %edx,%eax
 804837e:       7d 02                   jge    8048382 <max+0x2d>
 8048380:       89 d0                   mov    %edx,%eax
int a[] = { 9, 3, 5, 2, 1, 0, 8, 7, 6, 4 };

int max(int n)
{
        return n == 0 ? a[0] : MAX(a[n], max(n-1));
}
 8048382:       83 c4 04                add    $0x4,%esp
 8048385:       5b                      pop    %ebx
 8048386:       5d                      pop    %ebp
 8048387:       c3                      ret    
...

www.9778.com 25

可以看到,并没有call指令调用MAX函数,MAX函数的指令是内联在max函数中的,由于源代码和指令的次序无法对应,max和MAX函数的源代码也交错在一起显示。 
2.3. #、##运算符和可变参数  
在函数式宏定义中,#运算符用于创建字符串,#运算符后面应该跟一个形参(中间可以有空格或Tab),例如: 

 

#define STR(s) # s
STR(hello     world)

 

用cpp命令预处理之后是”hello?world”,自动用”号把实参括起来成为一个字符串,并且实参中的连续多个空白字符被替换成一个空格。 
再比如: 

 

#define STR(s) #s
fputs(STR(strncmp("ab"cd", "abc", '4"')
    == 0) STR(: @n), s);

预处理之后是fputs(“strncmp(“ab”cd”, “abc”, ‘4″‘) == 0″ “: @n”,
s);,注意如果实参中包含字符常量或字符串,则宏展开之后字符串的界定符”要替换成”,字符常量或字符串中的和”字符要替换成和”。 
在宏定义中可以用##运算符把前后两个预处理Token连接成一个预处理Token,和#运算符不同,##运算符不仅限于函数式宏定义,变量式宏定义也可以用。例如: 

 

#define CONCAT(a, b) a##b
CONCAT(con, cat)

 

预处理之后是concat。再比如,要定义一个宏展开成两个#号,可以这样定义: 

 

#define HASH_HASH # ## #

 

中间的##是运算符,宏展开时前后两个#号被这个运算符连接在一起。注意中间的两个空格是不可少的,如果写成####,会被划分成##和##两个Token,而根据定义##运算符用于连接前后两个预处理Token,不能出现在宏定义的开头或末尾,所以会报错。 
我们知道printf函数带有可变参数,函数式宏定义也可以带可变参数,同样是在参数列表中用…表示可变参数。例如: 

 

#define showlist(...) printf(#__VA_ARGS__)
#define report(test, ...) ((test)?printf(#test):
    printf(__VA_ARGS__))
showlist(The first, second, and third items.);
report(x>y, "x is %d but y is %d", x, y);

预处理之后变成:

printf("The first, second, and third items.");
((x>y)?printf("x>y"): printf("x is %d but y is %d", x, y));

在宏定义中,可变参数的部分用__VA_ARGS__表示,实参中对应…的几个参数可以看成一个参数替换到宏定义中__VA_ARGS__所在的地方。 
调用函数式宏定义允许传空参数,这一点和函数调用不同,通过下面几个例子理解空参数的用法。 

#define FOO() foo
FOO()

预处理之后变成foo。FOO在定义时不带参数,在调用时也不允许传参数给它。 

 

#define FOO(a) foo##a
FOO(bar)
FOO()

 

预处理之后变成: 

 

foobar
foo

 

FOO在定义时带一个参数,在调用时必须传一个参数给它,如果不传参数则表示传了一个空参数。 

 

#define FOO(a, b, c) a##b##c
FOO(1,2,3)
FOO(1,2,)
FOO(1,,3)
FOO(,,3)

 

预处理之后变成: 

 

123
12
13
3

 

FOO在定义时带三个参数,在调用时也必须传三个参数给它,空参数的位置可以空着,但必须给够三个参数,FOO(1,2)这样的调用是错误的。 

#define FOO(a, ...) a##__VA_ARGS__
FOO(1)
FOO(1,2,3,)

 预处理之后变成: 

 

1
12,3,

FOO(1)这个调用相当于可变参数部分传了一个空参数,FOO(1,2,3,)这个调用相当于可变参数部分传了三个参数,第三个是空参数。 
gcc有一种扩展语法,如果##运算符用在__VA_ARGS__前面,除了起连接作用之外还有特殊的含义,例如内核代码net/netfilter/nf_conntrack_proto_sctp.c中的: 

 

#define DEBUGP(format, ...) printk(format, ## __VA_ARGS__)

 

printk这个内核函数相当于printf,也带有格式化字符串和可变参数,由于内核不能调用libc的函数,所以另外实现了一个打印函数。这个
函数式宏定义可以这样调用:DEBUGP(“info no. %d”,
1)。也可以这样调用:DEBUGP(“info”)。后者相当于可变参数部分传了一个空参数,但展开后并不是printk(“info”,),而是
printk(“info”),当__VA_ARGS是空参数时,##运算符把它前面的,号“吃”掉了。 
2.4. 宏展开的步骤  
以上举的宏展开的例子都是最简单的,有些宏展开的过程要做多次替换,例如: 

 

#define sh(x) printf("n" #x "=%d, or %dn",n##x,alt[x])
#define sub_z  26
sh(sub_z)

 

sh(sub_z)要用sh(x)这个宏定义来展开,形参x对应的实参是sub_z,替换过程如下: 

  1. #x要替换成”sub_z”。 
  2. n##x要替换成nsub_z。 
    3.
    除了带#和##运算符的参数之外,其它参数在替换之前要对实参本身做充分的展开,所以应该先把sub_z展开成26再替换到alt[x]中x的位置。 
  3. 现在展开成了printf(“n” “sub_z” “=%d, or
    %dn”,nsub_z,alt[26]),所有参数都替换完了,这时编译器会再扫描一遍,再找出可以展开的宏定义来展开,假设nsub_z或alt是变量式宏定义,这时会进一步展开。 
    再举一个例子: 

 

www.9778.com 26

#define x 3
#define f(a) f(x * (a))
#undef x
#define x 2
#define g f
#define t(a) a

t(t(g)(0) + t)(1);

www.9778.com 27

 

展开的步骤是: 

  1. 先把g展开成f再替换到#define t(a) a中,得到t(f(0) + t)(1);。 
  2. 根据#define f(a) f(x * (a)),得到t(f(x * (0)) + t)(1);。 
  3. 把x替换成2,得到t(f(2 * (0)) +
    t)(1);。注意,一开始定义x为3,但是后来用#undef
    x取消了x的定义,又重新定义x为2。当处理到t(t(g)(0) +
    t)(1);这一行代码时x已经定义成2了,所以用2来替换。还要注意一点,现在得到的t(f(2
    * (0)) + t)(1);中仍然有f,但不能再次根据#define f(a) f(x *
    (a))展开了,f(2 *
    (0))就是由展开f(0)得到的,这里面再遇到f就不展开了,这样规定可以避免无穷展开(类似于无穷递归),因此我们可以放心地使用递归定义,例
    如#define a a[0],#define a a.member等。 
  4. 根据#define t(a) a,最终展开成f(2 * (0)) +
    t(1);。这时不能再展开t(1)了,因为这里的t就是由展开t(f(2 * (0)) +
    t)得到的,所以不能再展开了。

 

 

 

 

可变参数宏

 

在 GNU C 中,宏可以接受可变数目的参数,就象函数一样,例如:
#define pr_debug(fmt,arg…)
printk(KERN_DEBUG fmt,##arg)

用可变参数宏(variadic macros)传递可变参数表
你可能很熟悉在函数中使用可变参数表,如:

void printf(const char* format, …);

直到最近,可变参数表还是只能应用在真正的函数中,不能使用在宏中。

C99编译器标准终于改变了这种局面,它允许你可以定义可变参数宏(variadic
macros),这样你就可以使用拥有可以变化的参数表的宏。可变参数宏就像下面这个样子:

#define debug(…) printf(__VA_ARGS__)

缺省号代表一个可以变化的参数表。使用保留名 __VA_ARGS__
把参数传递给宏。当宏的调用展开时,实际的参数就传递给 printf()了。例如:

Debug(“Y = %dn”, y);

而处理器会把宏的调用替换成:

printf(“Y = %dn”, y);

因为debug()是一个可变参数宏,你能在每一次调用中传递不同数目的参数:

debug(“test”); //一个参数

可变参数宏不被ANSI/ISO C++
所正式支持。因此,你应当检查你的编译器,看它是否支持这项技术。

 

如果该类型库提供了一个名为AddNumbers的方法,在我们的Visual
LISP代码中它将被识别为dsxm-AddNumbers。有趣的是,一旦你已经输入了类型库并且程序运行成功后,Visual
LISP将识别这些外部应用程序中的所有已定义的属性、方法、常量,并将其做为内置的LISP函数把它们的代码变蓝。这也是Visual
LISP IDE可以帮你编程及提高你及时发现错误的能力的另一原因。

用GCC和C99的可变参数宏, 更方便地打印调试信息

gcc的预处理提供的可变参数宏定义真是好用: 

#ifdef DEBUG
 #define dbgprint(format,args...) 
   fprintf(stderr, format, ##args)
#else
    #define dbgprint(format,args...)
#endif

如此定义之后,代码中就可以用dbgprint了,例如dbgprint(“aaa %s”, __FILE__);。感觉这个功能比较Cool  :em11: 

下面是C99的方法: 

#define dgbmsg(fmt,...) 
             printf(fmt,__VA_ARGS__)

新的C99规范支持了可变参数的宏

具体使用如下:

 以下内容为程序代码:

 #include <stdarg.h> #include <stdio.h>

 #define LOGSTRINGS(fm, …) printf(fm,__VA_ARGS__)

 int main() {      LOGSTRINGS(“hello, %d “, 10);      return 0; } 

 但现在似乎只有gcc才支持。

图3-3 类型库接口

可变参数的宏里的‘##’操作说明

带有可变参数的宏(Macros with a Variable Number of
Arguments)

在1999年版本的ISO
C 标准中,宏可以象函数一样,定义时可以带有可变参数。宏的语法和函数的语法类似。下面有个例子:

#define debug(format, …) fprintf (stderr, format, __VA_ARGS__)

这里,‘…’指可变参数。这类宏在被调用时,它(这里指‘…’)被表示成零个或多个符号,包括里面的逗号,一直到到右括弧结束为止。当被调用时,在宏体(macro
body)中,那些符号序列集合将代替里面的__VA_ARGS__标识符。更多的信息可以参考CPP手册。

GCC始终支持复杂的宏,它使用一种不同的语法从而可以使你可以给可变参数一个名字,如同其它参数一样。例如下面的例子:

#define debug(format, args…) fprintf (stderr, format, args)

这和上面举的那个ISO
C定义的宏例子是完全一样的,但是这么写可读性更强并且更容易进行描述。

GNU CPP还有两种更复杂的宏扩展,支持上面两种格式的定义格式。

在标准C里,你不能省略可变参数,但是你却可以给它传递一个空的参数。例如,下面的宏调用在ISO
C里是非法的,因为字符串后面没有逗号:

debug (“A message”)

GNU
CPP在这种情况下可以让你完全的忽略可变参数。在上面的例子中,编译器仍然会有问题(complain),因为宏展开后,里面的字符串后面会有个多余的逗号。

为了解决这个问题,CPP使用一个特殊的‘##’操作。书写格式为:

#define debug(format, …) fprintf (stderr, format, ##
__VA_ARGS__)

这里,如果可变参数被忽略或为空,‘##’操作将使预处理器(preprocessor)去除掉它前面的那个逗号。如果你在宏调用时,确实提供了一些可变参数,GNU
CPP也会工作正常,它会把这些可变参数放到逗号的后面。象其它的pasted
macro参数一样,这些参数不是宏的扩展。

类型库仅仅是一个接口,它暴露了提供者的所有对象模型成员供给其它应用程序查询用。当加载类型库时,它立刻定义并识别相关应用程序提供者的所有公开暴露的属性、常量和方法给应用程序使用者使用。

怎样写参数个数可变的宏

一种流行的技巧是用一个单独的用括弧括起来的的 “参数” 定义和调用宏,
参数在
宏扩展的时候成为类似 printf() 那样的函数的整个参数列表。

    #define DEBUG(args) (printf("DEBUG: "), printf args)

    if(n != 0) DEBUG(("n is %dn", n));

明显的缺陷是调用者必须记住使用一对额外的括弧。

gcc 有一个扩展可以让函数式的宏接受可变个数的参数。 但这不是标准。另一种
可能的解决方案是根据参数个数使用多个宏 (DEBUG1, DEBUG2, 等等), 或者用
逗号玩个这样的花招:

    #define DEBUG(args) (printf("DEBUG: "), printf(args))
    #define _ ,

    DEBUG("i = %d" _ i);

C99 引入了对参数个数可变的函数式宏的正式支持。在宏 “原型” 的末尾加上符号 …
(就像在参数可变的函数定义中),
宏定义中的伪宏 __VA_ARGS__ 就会在调用是 替换成可变参数。

最后,
你总是可以使用真实的函数, 接受明确定义的可变参数

如果你需要替换宏, 使用一个 函数和一个非函数式宏, 如 #define printf
myprintf。

 

其实如果不怕出现no effect statement warning的话,定义成

#ifdef _DEBUG
#define _DEBUGOUT   printf
#else
#define _DEBUGOUT
#endif

如果支持可变参数的话,定义成

#ifdef _DEBUG
#define _DEBUGOUT   printf
#else
#define _DEBUGOUT(x, …)
#endif

 

 

 

关于##在C宏定义中的作用

 

最近因为工作问题,一直要看Linux的源代码。对于源码中宏定义的#一直有点疑惑,发现一个哥们总结的不错,所以就Ctrl

  • C and Ctrl + V进来:

内核中有很多的宏定义,在宏定义define中经常看到两个字符串##和#,这里把它的用法做一下说明:
    ##是一个连接符号,用于把参数连在一起
        例如:
            > #define FOO(arg)   my##arg
        则
            > FOO(abc)
        相当于   myabc
    
   
#是“字符串化”的意思。出现在宏定义中的#是把跟在后面的参数转换成一个字符串
        例如:
            > #define STRCPY(dst, src)   strcpy(dst, #src)
        则
            > STRCPY(buff, abc)
        相当于   strcpy(buff, “abc”)

    另外,如果##后的参数本身也是一个宏的话,##会阻止这个宏的展开 。

    #define STRCPY(a, b)    strcpy(a ## _p, #b)
    int main()
    {
        char var1_p[20];
        char var2_p[30];
        strcpy(var1_p, “aaaa”);
        strcpy(var2_p, “bbbb”);
        STRCPY(var1, var2);
        STRCPY(var2, var1);
        printf(“var1 = %sn”, var1_p);
        printf(“var2 = %sn”, var2_p);
        return 0;

        /* 注意这里 */
        STRCPY(STRCPY(var1,var2),var2);
        /* 这里是否会展开为:
strcpy(strcpy(var1_p,”var2″)_p,”var2“)?
         * 答案是否定的:
         * 展开结果将是: strcpy(STRCPY(var1,var2)_p,”var2″)
         * ## 阻止了参数的宏展开!
         * 如果宏定义里没有用到 # 和 ##, 宏将会完全展开
         */
    }

/////////////////////////////////////////////////////////////////////////

tell you about ## in common text
关于记号粘贴操作符(token paste operator): ##

1.
简单的说,“##”是一种分隔连接方式,它的作用是先分隔,然后进行强制连接。

  
其中,分隔的作用类似于空格。我们知道在普通的宏定义中,预处理器一般把空格
  
解释成分段标志,对于每一段和前面比较,相同的就被替换。但是这样做的结果是,
  
被替换段之间存在一些空格。如果我们不希望出现这些空格,就可以通过添加一些
   ##来替代空格。

   另外一些分隔标志是,包括操作符,比如 +, -, *, /, [,],
…,所以尽管下面的
   宏定义没有空格,但是依然表达有意义的定义: define add(a, b) a+b

  
而其强制连接的作用是,去掉和前面的字符串之间的空格,而把两者连接起来。

  1. 举列 — 试比较下述几个宏定义的区别

   #define A1(name, type) type name_##type##_type 或
   #define A2(name, type) type name##_##type##_type

   A1(a1, int); /* 等价于: int name_int_type; */
   A2(a1, int); /* 等价于: int a1_int_type;   */

   解释:
        1)
在第一个宏定义中,”name”和第一个”_”之间,以及第2个”_”和第二个
  
“type”之间没有被分隔,所以预处理器会把name_##type##_type解释成3段:
   “name_”、“type”、以及“_type”,这中间只有“type”是在宏前面出现过
    的,所以它可以被宏替换。

        2) 而在第二个宏定义中,“name”和第一个“_”之间也被分隔了,所以
  
预处理器会把name##_##type##_type解释成4段:“name”、“_”、“type”
   以及“_type”,这其间,就有两个可以被宏替换了。

        3) A1和A2的定义也可以如下:
           #define A1(name, type) type name_ ##type ##_type 
                                      <##前面随意加上一些空格>
           #define A2(name, type) type name ##_ ##type ##_type

    结果是## 会把前面的空格去掉完成强连接,得到和上面结果相同的宏定义

  1. 其他相关 — 单独的一个 #

   至于单独一个#,则表示 对这个变量替换后,再加双引号引起来。比如

      #define __stringify_1(x)   #x
那么
      __stringify_1(linux)   <==> “linux”

所以,对于MODULE_DEVICE_TABLE

     1) #define
MODULE_DEVICE_TABLE(type,name)                        
             MODULE_GENERIC_TABLE(type##_device,name)
     2) #define
MODULE_GENERIC_TABLE(gtype,name)                      
             extern const struct gtype##_id
__mod_##gtype##_table     
             __attribute__ ((unused, alias(__stringify(name))))

得到 
      MODULE_DEVICE_TABLE(usb, products) 
                             /*notes: struct usb_device_id products;
*/
<==> MODULE_GENERIC_TABLE(usb_device,products)
<==> extern const struct usb_device_id
__mod_usb_device_table     
             __attribute__ ((unused, alias(“products”)))  

注意到alias
attribute需要一个双引号,所以在这里使用了__stringify(name)来
给name加上双引号。另外,还注意到一个外部变量”__mod_usb_device_table”被alias
到了本驱动专用的由用户自定义的变量products<usb_device_id类型>。这个外部变量
是如何使用的,更多的信息请参看《probe()过程分析》。

  1. 分析方法和验证方式 — 编写一个简单的C程序

  
用宏定义一个变量,同时用直接方式定义一个相同的变量,编译报告重复定义;
  
用宏定义一个变量,直接使用该宏定义的变量名称,编译通过且运行结果正确;
   使用printf打印字符串数据。printf(“token macro is %s”,
__stringify_1(a1));

 

  我看《APUE》的时候信号那一章有这样的宏定义:我想知道(void (*)())-1
 这是 
> 什么意思,-1和前面的(void (*)())什么关系,谢谢 

> #define SIG_ERR (void (*)())-1 
> #define SIG_DFL (void (*)())0 

> #define SIG_IGN (void (*)())1 

这个就是一个函数指针类型声明,将后面的整数-1、0和1强制转换成一个无返回值,可以带任意参数的函数指针。 

这个纯粹是C语言问题。 

写成这样可能会好理解一点: 
typedef void (*sig_handler_prototype)(); 

#define SIG_ERR (sig_handler_prototype)-1 
#define SIG_DFL (sig_handler_prototype)0 
#define SIG_IGN (sig_handler_prototype)-1 

但是这个我认为其实树上这样写是不严格的,因为信号处理的函数原型严格说应该是这样的: 
typedef void (*sig_handler_prototype)(int);

 

本文来自CSDN博客,转载请标明出处:

#    define PDEBUG(fmt, args…) printk( KERN_DEBUG “DEMO: ” fmt, ##
args)

#else//usr space

#    define PDEBUG(fmt, args…) fprintf(stderr, fmt, ## args)

 ##args表示如果args为空则消除前面的逗号

 

define小结 
ajumail 发表于 2006-11-10 

  1. 定义简单的常数:定义常量,便于修改(切不可在后面加上分号!)
     #define N 1000
     等效于 const int N = 1000;
    但略有不同,define只是简单替换,而不是作为一个量来使用.

  2. 定义简单的函数:注意多使用括号
     #define MAX(x, y) ((x) > (y)) ? (x) : (y)

  3. 定义单行宏:主要有以下三种用法.
       1)
    前加##或后加##,将标记作为一个合法的标识符的一部分.注意,不是字符串.多用于多行的宏定义中.例如:
    #define A(x)  T_##x
    则 int A(1) = 10; //等效于int T_1 = 10;
    #define A(x)  Tx##__
    则 int A(1) = 10; //等效于int T1__ = 10;
       2)
    前加#@,将标记转换为相应的字符,注意:仅对单一标记转换有效(理解有误?)
     #define B(x) #@x
     则B(a)即’a’,B(1)即’1’.但B(abc)却不甚有效.
       3) 前加#,将标记转换为字符串.
     #define C(x) #x
     则C(1+1) 即 ”1+1”.

  4. 定义多行宏:注意斜杠的使用,最后一行不能用斜杠.
     #define DECLARE_RTTI(thisClass, superClass)
      virtual const char* GetClassName() const 
      {return #thisClass;}
      static int isTypeOf(const char* type)
      {
       if(!strcmp(#thisClass, type)
        return 1;
       return superClass::isTypeOf(type);
       return 0;
      }
      virtual int isA(const char* type)
      {
       return thisClass::isTypeOf(type);
      }
      static thisClass* SafeDownCast(DitkObject* o)
      {
    www.9778.com,   if(o&&o->isA(#thisClass))
        return static_cast<thisClass*>(o);
       return NULL;
      }

  5. 用于条件编译:(常用形式)
     #ifndef _AAA_H
     #define _AAA_H
     //c/c++代码
     #endif

  6. 一些注意事项:
      1) 不能重复定义.除非定义完全相同.#define A(x) … 和#define
    A 是重复定义.
      2) 可以只定义符号,不定义值.如#define AAA

在图3-3中,Excel类型库被加载以做便Visual
LISP可以连接到Excel对象模型并使用其暴露的工具。这样可以节省你很多时间和麻烦,因为它在Excel中建立了直接的访问工具,这样就可以做你想需要的而不必尝试在Visual
LISP中重复另起炉灶。下面的例子显示了它是如何使用的。

例如,当通过从Visual
LISP中调用,将一个定值做为参数提供给Excel函数,你可以用常量枚举名来代替实际所代表的数值以使得你的编码清晰易懂。这也省去了你需要浏览Excel中的所有枚举并把它们翻译成Visual
LISP的时间。如果Excel提供了一个如put-cellcolor的常量时,那就可以在Visual
LISP中直接使用它。

Visual
LISP需要类型库信息来确定对象的方法、属性和常量是否存在。有些对象不含有任何类型库信息,例如AcadDocument对象。

(vlax-typeinfo-available-p object) 

如果对象的类型库信息存在,返回T。如果不存在,则返回nil。

参数:
Object – 一个vla-对象。 

(defun Excel-Get-Cell (rng row column)
  (vlax-variant-value
    (msxl-get-item
      (msxl-get-cells rng)
      (vlax-make-variant row)
      (vlax-make-variant column)
    )
  )
)
(defun Excel-Put-CellColor (row col intcol / rng)
  (setq rng (Excel-Get-Cell (msxl-get-ActiveSheet xlapp) row col))
  (msxl-put-colorindex (msxl-get-interior rng) intcol)
)

上例中定义的第二个函数提供了一个在Visual
LISP中对一个Excel工作表中的给定单元填色的方法。这可以通过使用Excel中已暴露接口的方法,这是通过首先加载Excel类型库实现的。这个以上显示的类型库项带有一个 msxl-前缀。

当你调用了一个类型库接口,其中涉及到的函数随后会被VLISP编辑器语法引擎识别到。当你正确地输入了它们后,它们就会改变颜色以显示它们确实被从外面的类型库接口中被视为一个有效的函数而显示出来。这是一个很有用的编码练习的基础:语法感知。

提示!类型库有很多种形式,他们通常是.TLB文件。但也可以是.OLB、.DLL,甚至是.EXE。值得一提的是Microsoft
Office 97 和 2000版通常用.TLB文件,而OFFICE
XP自己使用.EXE文件来对类型库提供对其它应用的接口定义。请翻阅与你准备使用外部应用程序或服务相关的参考资料以了解它是如何暴露共ActiveX类型库信息的内容。

尽管变体和安全数组数据类型这个话题在本书的前几节已经讲过,但在Visual
LISP的ActiveX领域中它们具有相当重要的位置,值得我们用一整章的篇幅来专门讲解。我们将首先简要了解他们是什么,然后开始探索如何通过Visual
LISP函数来使用它们。

正如我们前面提到的,变体是一种数据类型,它被设计成能包含所有任何其它数据的通用容器。他们消耗最多的内存并可处理所有的数据类型,因为他们会根据资源需求取最大内存。

象C/C++、Visual
Basic以及Delphi这类语言会提供声明语句去提前通知编译器确定每个变量将包含什么数据类型。这不仅保证了精简的资源需求,同时也允许编辑期间进行错误检查,以防止运行时的问题。

Visual LISP变体函数
(vlax-make-variant [value] [type]) 
使用给定的值或符号赋值来创建一个变体对象

参数:
Value – 要指定给变体的值。如果省略该参数,变体将创建为 vlax-vbEmpty
类型(未初始化)。
Type –
变体的数据类型,如果省略该参数,变体将根据最接近的ActiveX数据类型来确定LISP数据类型(看下表)。

示例:
(vlax-make-variant) 或 (vlax-make-variant
nil) 
创建一个未初始化(vlax-vbEmpty)变体。

(vlax-make-variant 10
:vlax-vbInteger) 
创建一个整型(vlax-vbInteger) 变体,其值为10。

(vlax-make-variant “vlisp
example”) 
创建一个字符串类型(vlax-vbString)变体,其值为“vlisp example”。

(setq dblarray (vlax-make-safearray
vlax-vbDouble ‘(0 . 3))) 
(vlax-make-variant dblarray
:vlax-vbArray) 
创建一个包含双精度值安全数组的变体。

提示!ActiveX数据类型中的十进制和短型在Visual
LISP中并不支持,但当从外部资源读取值时,你可以用(vlax-variant-type)来指定其类型。将这些类型的数据发送到外部资源时,你需要使用数字表示法来替代(vlax-vbDecimal)和(vlax-vbShort),因为在Visual
LISP中它们并未作为枚举提供。例如,十进制数据类型的枚举值为14。

变体数据类型
如果你不为变体构造器指定数据类型又会怎样?Visual
LISP将会使用默认映射尝试将它转换到一个适当的数据类型。表6-1显示了从LISP到变体的数据类型的默认映射。

  LISP 数据类型      分配的变体默认数据类型   
  nil      vlax-vbEmpty   
  :vlax-null      vlax-vbNull   
  INT (integer)      vlax-vbLong   
  REAL (float)      vlax-vbDouble   
  STR (string)      vlax-vbString   
  VLA-OBJECT      vlax-vbObject   
  :vlax-true or :vlax-false      vlax-vbBoolean   
  VARIANT      与初始值的类型相同   
  SafeArray      vlax-vbArray   
  N/A      vlax-vbShort   
  N/A      vlax-vbDecimal   
  N/A      vlax-vbDate   

表 6-1 – Visual LISP默认LISP->变体数据类型映射 

(vlax-variant-type variant) 
返回变体的数据类型,如果variant不是变体,则返回错误信息。返回值是数据类型的枚举值(查看附录A的数据类型枚举值)。

参数:
Variant – 变体。

示例:
(setq vartest (vlax-make-variant 6
vlax-vbInteger)) 
(vlax-variant-type vartest) 
返回 2 (整数类型) 

(setq vartest (vlax-make-variant “dog”
vlax-vbString)) 
(vlax-variant-type vartest) 
返回 8 (字符串类型) 

(vlax-variant-value symbol) 
返回包含于变体符号中值。如果symbol不包含变体数据类型。则返回错误信息。

参数:
Symbol – 包含变体值的符号。

示例:
(setq vartest (vlax-make-variant
“testvalue” vlax-vbString)) 
(vlax-variant-value vartest) 
将“testvalue”值作为字符串结果返回。

(setq sa (vlax-make-safearray vlax-vbDouble
‘(0 . 2))) 
(setq vartest (vlax-make-variant sa
vlax-vbDouble)) 
(vlax-variant-value vartest) 
返回#<safearray…>值,它是vla-object类型。

(vlax-safearray->list
(vlax-variant-value vartest)) 
返回结果是(0.0 0.0 0.0)列表值。

(vlax-variant-change-type symbol type) 
更改变体的数据类型。

参数:
Symbol – 变体值
Type – 要转换到的数据类型数字或枚举

示例:
(setq vartest (vlax-make-variant 5
vlax-vbInteger)) 
(setq vartest (vlax-variant-change-type
vartest vlax-vbString)) 
转换vartest为字符串类型(vlax-vbString)变体,它将致使通过(vlax-variant-value)返回的值为“5”。

(vlax-make-safearray type dim1 [dim2]
…) 
创建一个安全数组,其数据类型为type,维度范围dim1,以此类推,可以指定额外的维度。不管什么原因,如果操作失败,语句将返回nil。 

参数:
Type – 数据类型(整数或枚举)
Dim1 – 第一维数组(一维数组)
Dim2 – (可选)第二维维数(二维数组)等。

示例:
(setq sa (vlax-make-safearray vlax-vbDouble
‘(0 . 2))) 
创建一个双精度的单维数组,它可容纳三个不同的元素(0,1,2)。

(setq sa (vlax-make-safearray vlax-vbString
‘(0 . 1) ‘(1 . 3))) 
创建一个字符型二维数组,第一维包含两个元素,由索引0开始。第二给包含三个元素,由索引1开始。

提示!要填充安全数组,可使用(vlax-safearray-fill)还是(vlax-safearray-put-element)来填充,至于使用哪个填充,可根据用户是否需要一次只指定一个元素或一次性指定所有元素来决定。

(vlax-safearray->list symbol) 
如果symbol包含安全数组,元素将转换为LISP
LIST数据类型被返回。如果symbol不包含安全数组,会产生错误。你需要将对该函数的调用封装到错误捕获内以使这个错误能得到适当处理。

参数:
Symbol – 包含安全数组的符号

(vlax-safearray-type symbol) 
如果symbol包含安全数组,元素的数据类型将以枚举结果返回(整数值)。这将可匹配整数或枚举结果(查看附录X有关数据类型枚举)。如果symbol不包含安全数组,将产生错误。

参数:
Symbol – 包含安全数组的符号

示例:
(setq sa (vlax-make-safearray vlax-vbdouble
‘(0 . 3))) 
(vlax-safearray-type sa) 
返回5(双精度),相当于vlax-vbDouble

(vlax-safearray-fill safearray
‘element-values) 
指定值给安全数组内的多个元素。如果提供的参数不是一个数组,将返回ActiveX错误。你需要将对该函数的调用封装到错误捕获内以使这个错误能得到适当处理。

参数:
Safearray        安全数组类型的对象
Element-values    
   一个存储于数组中的值列表,你可以指定与数组中元素一样多的值。如果你指定的数值少于元素个数,剩余的元素保留他们当前值或为空。对于多维数组,element-values必须是列表的列表,每一列表对应数组的一维。

示例:
创建一个双精度值的单维数组:
_$ (setq myarray (vlax-make-safearray
vlax-vbdouble ‘(0 . 2))) 
#<safearray…> 
使用vlax-safearray-fill来填充数组元素值:
_$ (vlax-safearray-fill myarray ‘(1 2
3)) 
#<safearray…> 
列出数组包含的值以校验元素值:
_$ (vlax-safearray->list
myarray) 
(1.0 2.0 3.0) 

(vlax-safearray-get-element safearray
element [element…]) 
返回安全数组中指定元素的值,element的值是整数,表示在该数组中要取得的索引位置。如果safearray参数非安全数组对象,将产生ActiveX错误。你需要将对该函数的调用封装到错误捕获内以使这个错误能得到适当处理。

参数:
Safearray –安全数组类型的对象
Element – 整数,指要取得的索引位置

示例:
_$ (setq sa (vlax-make-safearray
vlax-vbString ‘(1 . 2) ‘(1 . 2) ))
#<safearray…> 
使用vlax-safearray-put-element来填充数组:
_$ (vlax-safearray-put-element sa 1 1
“A”) 
“a” 
_$ (vlax-safearray-put-element sa 1 2
“B”) 
“b” 
_$ (vlax-safearray-put-element sa 2 1
“C”) 
“c” 
_$ (vlax-safearray-put-element sa 2 2
“D”) 
“d” 
使用vlax-safearray-get-element来检索数组第一维的第二元素:
_$ (vlax-safearray-get-element sa 1
1) 
”A” 
_$ (vlax-safearray-get-element a 2
2) 
”D” 

(vlax-safearray-put-element safearray
element [element…] value) 
在安全数组中指定新值给单个元素。如果safearray参数不是安全数组对象,将产生ActiveX错误。如果提供的元素值不能与数组中的数据类型相匹配,将返回ActiveX错误。你需要将对该函数的调用封装到错误捕获内以使这个错误能得到适当处理。

参数:
Safearray         安全数组类型的对象
Element       
 指向你将指定值的元素所在位置的系列索引值。对于一维数组,指定一个索引值,对于二维数组,指定两个索引值,以此类推。
Value       
 指定给每个元素的值。在数组中要指定不同的值给数组中个别的元素,要分开调用独立的值来对应不同的元素位置。

示例:
_$ (setq sa (vlax-make-safearray
vlax-vbString ‘(1 . 2) ‘(1 . 2) )) 
#<safearray…> 
使用vlax-safearray-put-element来填充数组:
_$ (vlax-safearray-put-element sa 1 1
“A”) 
“A” 
_$ (vlax-safearray-put-element sa 1 2
“B”) 
“B” 
_$ (vlax-safearray-put-element sa 2 1
“C”) 
“C” 
_$ (vlax-safearray-put-element sa 2 2
“D”) 
“D” 
你也可以用vlax-safearray-fill函数来填充数组值,以下的函数调用能和三个vlax-safearray-put-element调用一样完成同样的任务:
(vlax-safearray-fill sa ‘((“A” “B”) (“C”
“D”))) 

(vlax-safearray-get-dim
safearray) 
返回给定安全数组中的维数(数组维数的数字)。如果给定的参数不是数组,将返回ActiveX错误。你需要将对该函数的调用封装到错误捕获内以使这个错误能得到适当处理。

参数:
Safearray 安全数组类型的对象

示例:
_$ (setq myarray (vlax-make-safearray
vlax-vbinteger ‘(2 . 5))) 
#<safearray…> 
_$ (vlax-safearray-get-dim
myarray) 

(vlax-safearray-get-l-bound safearray
dim) 
返回指定数组的维数下界(整数值)。如果给定的参数不是数组,将返回ActiveX错误。你需要将对该函数的调用封装到错误捕获内以使这个错误能得到适当处理。

参数:
Safearray – 安全数组类型的对象
Dim – 整数,数组的第几维,第一维为1

示例:
以下示例是求以下所定义的安全数组的下界值:
(vlax-make-safearray vlax-vbString ‘(1 . 2)
‘(0 . 1) )) 
取得数组第一维的起始索引值(下界):
_$ (vlax-safearray-get-l-bound tmatrix 1)

(vlax-safearray-get-u-bound safearray
dim) 
返回指定数组维度的上界(整数值)。如果指定的参数不是数组,将返回ActiveX错误。你需要将对该函数的调用封装到错误捕获内以使这个错误能得到适当处理。

参数:
Safearray – 安全数组类型的对象
Dim – 整数,数组的第几维,第一维为1。

(setq sa (vlax-make-safearray vlax-vbString
‘(1 . 2) ‘(0 . 1) )) 
_$ (vlax-safearray-get-u-bound sa
1) 

第一维的上界的索引值为2。

取得数组第二维的上界索引值,其值为1:
_$ (vlax-safearray-get-u-bound sa 2)

Visual
LISP提供了一系列用于创建、操控和关闭ActiveX对象的函数。这通常是对外部应用程序会话对象而言的,但它同时也可以适用于任何外部进程对象,如DLL或OCX接口。

 

(vlax-get-object program-id)

尝试连接到存在的对象(进程),与Visual Basic/VBA函数GetObject (program-id)相同。

 

参数:

Program-ID –
字符串,应用对象类标识符的名称。例如“Word.Application”或“Excel.Application”。

 

示例:

(setq xlapp (vlax-get-object
“Excel.Application”))

成功时返回外部Excel应用程序进程的vla-对象,否则返回nil。

 

(vlax-create-object program-id)

尝试创建一个新的对象会话(进程)。与Visual Basic函数CreateObject (program-id)相同。

 

参数:

Program-ID –
字符串,应用程序对象类标识符名称。例如“Word.Application”或“Excel.Application”。

 

示例:

(setq xlapp (vlax-create-object
“Excel.Application”))

成功时返回新的外部Excel应用程序进程的vla-对象,否则返回nil。

 

(vlax-get-or-create-object program-id)

尝试先连接到存在的对象会话,然后,如果找不到,就尝试创建一个新的对象会话。这个函数在Visual
Basic中没有对应的函数,它是Visual LISP中特有的。

 

参数:

Program-ID –
字符串,指应用程序对象类标识符名称。例如“Word.Application”或“Excel.Application”。

 

示例:

(setq xlapp (vlax-get-or-create-object
“Excel.Application”))

成功时返回外部Excel应用程序对象的vla-对象,否则返回nil。

 

(vlax-write-enabled-p object)

如果object可以修改则返回T,否则返回nil。

 

备注:请小心该函数。在对象实际上是开放可修改状态下,有时它会返回False。

 

参数:

Object – 任何Vla-对象

 

(vlax-object-erased-p object)

如果object在图中已经被删除则返回T,否则返回nil。

 

参数:

Object – 任何代表图元对象类型的vla-对象。

 

(vlax-release-object object)

从内存中释放对象。它不是重新分配内存。当释放一个指向外部应用程序会话的对象时,这里强烈建议使用(gc),它能从操作系统资源中强行释放外部进程。

 

参数:

Object – 任何vla-对象。

 

警告!尽管对象符号是局部的,函数用完后不一定要释放对象的资源。但还是建议当已经不需要这个对象时可使用该函数以确保释放对象。然而,需要提醒的是,既使是释放了由外部应用程序驱动的对象,它也可能没有从内存或操作系统进程堆栈中被全部释放。最好是在你完成代码后释放所有不再使用的对象,然后再调用(gc)函数去强制回收内存堆栈。

 

Visual
LISP提供的最有用的函数是文件和目录函数。这些函数集可用于访问和修改文件属性,以及列出指定文件夹中的文件及文件夹。使用这些函数的一个例子,是将其用于对话框的列表框的环境中。

可能你喜欢在列表框中显示图纸文件清单,而不显示其扩展名(这样的名称会短一些)。就可结合目录列表框和vl_filename-base函数来实现,如下所示:
(mapcar ‘vl-filename-base (vl-directory-files
pathname “*.dwg”)) 

这样将返回名称的列表,如(“图形1”“图形2”…)。注意在这个例子中并没有提供错误检查。如果(vl-directory-files)函数返回nil,其余的语句就会因出错而崩溃。该例只是用来证明这些函数是如何相结合并用于使文件和目录的信息更容易使用。

(vl-file-size filename) 
以整数形式返回filename的字节数。如果没找到文件名,就返回nil。

参数:
Filename 字符串,指要查询文件的名称。

示例:
(vl-file-size “c:\myfile1.txt”); 返回
125523 (大约 124 Kb) 

(vl-file-copy source-filename target-filename [append]) 
从源位置(source-filename)复制文件到目标位置(target-filename)。如果append为非nil,同时目标文件存在,源文件就会被添加到已存在的目标文件。如果目标文件存在而append为nil,该文件将不被复制同时返回值为nil。如果成功,将返回整数值。

参数:
Source-filename 被复制文件的名称,如果文件不在默认的搜索路径中,则该文件名必须包含完整的路径位置。
Target-filename 源文件要复制到的目标名称。如果目标路径未指定,则使用默认的工作目录位置。
Append (可选)如果是非nil,代表如果目标文件存在,源文件将会被附加到目标文件上去。

示例:
(vl-file-copy “c:\myfile1.txt”
“c:\mycopy.txt”) 
(vl-file-copy “c:\myfile2.txt” “c:\mycopy.txt” T);
添加目录文件 

(vl-file-delete filename) 
删除文件名。如果成功返回T,否则返回nil。

参数:
Filename 字符串,指要删除的文件的名称。

(vl-file-rename old-name new-name) 
重命名现有的文件,由old-name重命名为new-name。如果成功返回T,否则返回nil。

参数:
Old-Name 字符串,指现有文件的名称。
New-Name 字符串,指重命名后的文件名称。

(vl-file-directory-p filename) 
如果filename是目录文件夹名称则返回T,如果filename实际上是一个文件或根本不存在,则返回nil。

(vl-file-systime filename) 
返回文件最后修改的日期和时间值列表。返回的列表为(年 月 星期 日  时 分
秒)的格式。

(vl-filename-base filename) 
返回不带路径和扩展名返回基本文件名。

参数:
Filename 字符串,指文件名,可带或不带路径和扩展名。

示例:
(vl-filename-base “c:\myfiles\drawing1.dwg”) 
返回“drawing1”
(vl-filename-base “drawing1.dwg”) 
返回“drawing1”

(vl-filename-directory filename) 
从指定的filename字符串返回目录或路径前缀值。

参数:
Filename 字符串,指包含路径的文件名。

示例:
(vl-filename-directory “c:\dwgfiles\working\drawing1.dwg”) 
返回: “c:\dwgfiles\working” 

(vl-filename-extension filename) 
返回给定文件名字符串的扩展名。

参数:
Filename 字符串,指文件名称。

示例:
(vl-filename-extension “c:\myfiles\drawing1.dwg”) 
返回 “dwg” 

(vl-filename-mktemp [pattern directory extension]) 
创建一个用于临时文件的独特文件名。返回代表文件名称的字符串,格式为:directorybase<XXX><.extension>。其中base多达5个字符,由pattern中取得,XXX是一个3字符的独特组合。

所有在VLISP会话中由vl-filename-mktemp生成的文件名都会在你退出VLISP会话时被删除。

参数:
Pattern 字符串,包含了文件名样式;如果nil或缺省,vl-filename-mktemp就用“$VL~~”。
Directory 字符串,临时文件目录的名称;如果nil或缺省,vl-filename-mktemp就在以下顺序中选择一个目录:
■如果在pattern已指定目录,就用它。
■在TMP环境变量中指定的目录。
■在TEMP环境变量中指定的目录。
■当前目录。
Extension 字符串,指定给文件的扩展名;如果nil或缺省,vl-filename-mktemp使用pattern中的扩展名部分(有可能是空字符串)。

示例:
(vl-filename-mktemp) 
“C:\TMP\$VL~~004” 
(vl-filename-mktemp “myapp.del”) 
“C:\TMP\MYAPP005.DEL” 
(vl-filename-mktemp “c:\acad2002\myapp.del”) 
“C:\ACAD2002\MYAPP006.DEL” 
(vl-filename-mktemp “c:\acad2002\myapp.del”)
“C:\ACAD2002\MYAPP007.DEL” 
(vl-filename-mktemp “myapp” “c:\acad2002”) 
“C:\ACAD2002\MYAPP008” 
(vl-filename-mktemp “myapp” “c:\acad2002” “.del”) 
“C:\ACAD2002\MYAPP00A.DEL” 

(vl-directory-files path pattern [mode]) 
按照不同的模式,返回文件或子文件夹的列表。

参数:
Path 字符串,指要查询的路径名。
Pattern 字符串,指要查询文件。可以包含通配符。如果不指定或nil,则使用“*.*”
Mode (可选)整数。以下其中一个…
-1 = 只列出目录名
0 =  列出文件及目录(未指定时的默认值)
1 =  只列出文件

示例:
命令:
(vl-directory-files “c:\dwgfiles\Working” “*.dwg”) 
(“drawing1.dwg” “drawing2.dwg” . . .) 
命令: (vl-directory-files “c:\dwgfiles” nil -1) 
(“.” “..” “Finished” “Working”) 
命令: (vl-directory-files “c:\dwgfiles” nil 1) 
nil 

AutoLISP提供了大量强大的映射和循环函数,如(while)、(foreach)、(mapcar)和(apply)。而Visual
LISP也增加了一些更适合用于与ActiveX集合对象的函数。这些函数包括有(vlax-for)、(vl-every)和(vlax-map-collection)等等。

(vlax-map-collection object function) 
应用function到集合对象的成员(对象),如果对象不是集合,则生成错误。

参数:
Object 代表集合的vla对象。
Function 用于对象的符号或lambda表达式。

示例:
(setq docs (vla-get-documents
(vlax-get-acad-object))) 
(vlax-map-collection docs
‘vlax-dump-object) 
这将重复每个已打开文件并列表所有特性…
; IAcadDocument:
An AutoCAD drawing 
; 特性值: 
; Active (RO) = -1 
; ActiveDimStyle = #<VLA-OBJECT IAcadDimStyle 046bb644> 
; ActiveLayer = #<VLA-OBJECT IAcadLayer 046bbd84> 
; ActiveLayout = #<VLA-OBJECT IAcadLayout 046b8a64> 
; ActiveLinetype = #<VLA-OBJECT IAcadLineType 046b89b4> 
… 还有其它… 

(vlax-for symbol collection [expression1 [expression2]]…) 
循环集合的成员对象并对每一成员对象执行语句。如果第二个参数不是集合对象,将生成错误。引用的symbol是局部的和临时的,就如(foreach)一样。

参数:
Symbol 指定给集合中每个vla对象的符号。
Collection 代表集合的vla对象
Expressions 求值的一个或多个语句(可选项)

示例:
(setq acad (vlax-get-acad-object)) 
(setq layers (vla-get-layers (vla-get-activedocument acad))) 
(vlax-for eachLayer layers
  (princ (vla-get-name eachLayer))
  (terpri) 

这将在命令提示处列出活动图形中所有图层的名称。

(vl-position item list) 
找到时返回在list中item的第n个位置。如果list中未找到item,则返回nil。第一元素的位置索引为零(0)。

参数:
Item  任何符号或值。
List  值或符号列表。

示例:
(setq mylist ‘(“A” “B” “C”)) 
(vl-position “B” mylist) 返回 1 
(vl-position “b” mylist) 返回 nil

(vl-every predicate-function list…)

(vl-every 
  (function
    (lambda (filename) 
      (> (vl-file-size filename) 1024)
    )
  )
  (vl-directory-files nil nil 1) 

(vl-every ‘= ‘(1 2) ‘(1 3)) 
返回 nil 
(vl-every ‘= ‘(1 2) ‘(1 2 3)) 
返回 T

vl-every

vl-every

_$ (setq list1 (list 1 2 3 4)) 
(1 2 3 4) 
_$ (setq list2 nil) 
nil 
_$ (vl-every ‘= list2 list1) 

vl-every

vl-every

(vl-load-all filename) 

AutoCAD

(vl-propagate ‘symbol)

(vl-bb-set ‘symbol)

(vl-bb-ref ‘symbol)

(vl-list-exported-functions) 

(vlax-add-cmd “globalname” ‘function [“localname” | flags])

主要标志选项:
ACRX_CMD_MODAL

ACRX_CMD_TRANSPARENT

二级标志选项:
ACRX_CMD_USEPICKSET

ACRX_CMD_REDRAW

(vl-load-com) 
(vl-doc-export ‘example1) 
(defun example1 () 
   (princ “这是一个透明函数的示例。”)
   (princ) 
)   
(vlax-add-cmd “example1” ‘example1 “example1” ACRX_CMD_TRANSPARENT) 
(princ) 
命令: LINE 
指定第一点: ‘EXAMPLE1 
这是一个透明函数的示例。 
重新回到LINE命令。 
指定第一点:

(vlax-remove-cmd “globalname”) 

(vlax-remove-cmd “example1”) 

(vlax-remove-cmd “example2”) 
nil

(vl-acad-defun ‘function)

(vl-acad-defun ‘example1)

(vl-acad-undefun ‘function)

(vl-acad-undefun ‘example1”) 
T

Visual
LISP提供了专门的函数用于访问和修改Windows注册表。你可以用这些函数在本地注册表的HKEY_LOCAL_MACHINE和HKEY_CURRENT_USER单元中查询和修改注册表项。你不能使用Visual
LISP注册表函数来访问远程注册表,也不能用Visual
LISP来访问HKEY_USERS和HKEY_CLASSES_ROOT以及HKEY_CURRENT_CONFIG注册表单元。

注意,即使在Visual
LISP可以访问的注册表单元,你仍会受限于进程所有者的安全环境以致限制了你的访问。换句话说,如果Visual
LISP应用程序是被一个对这台机器权限有限的用户使用时,有些注册表项可能无法访问或不能被Visual
LISP修改。这个问题在网络环境中需要重点考虑,因为它的组策略会修改注册表的访问许可。

(vl-registry-read regkey [value-name]) 
如果注册表中有定义注册表项或注册表值名(符号),则返回值指定给一个明确的注册表项或注册表值名(符号)的值。如果未找到该注册表项或值名,则结果就为nil。

参数:
RegKey 在HKEY_LOCAL_MACHINE或HKEY_CURRENT_USER单元中的注册表项名称。
Value-Name (可选项)在指定注册表项下方的从属值名(符号)的名称。

示例:
(vl-registry-write
“HKEY_CURRENT_USER\Example1” “FOO” “123”) 
“123”
(vl-registry-read “HKEY_CURRENT_USER\Example1” “FOO”) 
“123” 
(vl-registry-read “HKEY_CURRENT_USER\Example1”) 
nil 
(vl-registry-write “HKEY_CURRENT_USER\Example2” “” “ABCDEF”) 
“ABCDEF” 
(vl-registry-read “HKEY_CURRENT_USER\Example2”) 
“ABCDEF” 

(vl-registry-write regkey [value-name] value) 
将值写入注册表项或注册表项的值名中去,如果成功返回值。如果不成功则返回nil。

参数:
RegKey 注册表项的名称。
Value-Name (可选项)在指定注册表项下方的从属值名(符号)的名称。
Value 写入到指定的注册表项或值名的值。

示例:
(vl-registry-write
“HKEY_CURRENT_USER\Example1” “TEST1” “123”) 
“123” 
(vl-registry-write “HKEY_CURRENT_USER\Example1” “” “456”) 
“456” 

(vl-registry-delete regkey [value-name]) 
删除注册表中指定位置的注册表项及其相关的值,成功时返回T,如果失败则返回nil。如果提供了value-name且不为nil,指定的值将被从注册表中清除。如果没有value-name或为nil,该函数将删除指定注册表项及它的所有值。如果注册表中下面有子注册表项存在,该注册表项将不能被删除。要删除含有子注册表项的注册表项,你必须先用(vl-registry-descendents)来收集子注册表项并先将它们删除。

参数:
RegKey 注册表项的名称。
Value-Name (可选项)在指定注册表项下方的从属值名(符号)的名称。

示例:
(vl-registry-write
“HKEY_CURRENT_USER\Example1” “TEST1” “123”) 
“123” 
(vl-registry-delete “HKEY_CURRENT_USER\Example1”) 

(vl-registry-descendents regkey [value-names]) 
返回指定注册表项下属的子项或值名的表。如果提供了value-names且不为nil,将从注册表中列出指定的值名。如果没有value-names或为nil,该函数则显示注册表项的所有子项。还要注意到,返回的值经常是按倒序排列。

参数:
RegKey 注册表项名称。
Value-Names 包含regkey入口的数值的字符串。

示例:
(vl-registry-descendents
“HKEY_LOCAL_MACHINE\SOFTWARE”) 
(“WexTech Systems” “Voice” “Synaptics”
“Symantec” “Secure” “Program Groups” “Policies” “ODBC” “Nico Mak
Computing” “MicroVision” “Microsoft” “MetaStream” “McNeel” “McAfee”
“JavaSoft” “Intel Corporation” “INTEL” “InstalledOptions” “Helios”
“DOSLib” “Dell Computers” “Dell Computer Corporation” “Dell Computer”
“DameWare Development” “Clients” “Classes” “BVRP Software” “BigFix”
“Autodesk” “ATI Technologies” “Apple Computer, Inc.” “America Online”
“Adobe” “Adaptec” “3Com”) 

你可打开AutoCAD安装程序的Visual
LISP样板目录下的RegDump.LSP文件,可看到注册表函数的更多例子。在这个文件中,你可以找到一个名为(registry-tree-dump)的有用函数,它对指定注册表项执行递归来搜索到其下级的所有子项和值名。

提示:你可以创建一对Get和Set函数来保存和恢复注册表值,用于控制标准位置和错误捕获。你应该会发现以下两个函数是很有用的:

(setq G$REGROOT
“HKEY_CURRENT_USER\Software\MyApplication\”) 
(defun RegGet (key default / val)
  (if (= nil (setq val (vl-registry-read (strcat G$REGROOT key))))
    (progn
      (regset key default) 
(setq val (vl-registry-read (strcat G$REGROOT key)))
    )
  )
  (if val val default) 

(defun RegSet (key val)
  (vl-registry-write (strcat G$REGROOT key) “” val)

转载: