Protobuf 语法指南

======注:本文只是转载,作为个人备查,原文请访问如下地址:=======

http://colobu.com/2015/01/07/Protobuf-language-guide/

 

目录 [−]

  1. 定义一个消息类型
    1. 指定字段类型
    2. 分配标识号
    3. 指定字段规则
    4. 添加更多消息类型
    5. 添加注释
    6. 从.proto文件生成了什么?
    7. 标量数值类型
    8. Optional的字段和默认值
    9. 枚举
  2. 使用其他消息类型
    1. 导入定义
    2. 嵌套类型
    3. 更新一个消息类型
    4. 扩展
    5. 嵌套的扩展
    6. 选择可扩展的标量符号
  3. Oneof
    1. 使用Oneof
    2. 向后兼容性问题
  4. 包(Package)
    1. 包及名称的解析
  5. 定义服务(Service)
  6. 选项(Options)
    1. 自定义选项
  7. 生成访问类

本指南描述了怎样使用protocol buffer 语法来构造你的protocol buffer数据,包括.proto文件语法以及怎样生成.proto文件的数据访问类。

本文是一个参考指南——如果要查看如何使用本文中描述的多个特性的循序渐进的例子,请在http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/tutorials.html中查找需要的语言的教程。

 

定义一个消息类型

先来看一个非常简单的例子。假设你想定义一个“搜索请求”的消息格式,每一个请求含有一个查询字符串、你感兴趣的查询结果所在的页数,以及每一页多少条查询结果。可以采用如下的方式来定义消息类型的.proto文件了:

1
2
3
4
5
message SearchRequest {
required string query = 1;
optional int32 page_number = 2;
optional int32 result_per_page = 3;
}

SearchRequest消息格式有3个字段,在消息中承载的数据分别对应于每一个字段。其中每个字段都有一个名字和一种类型。

指定字段类型

在上面的例子中,所有字段都是标量类型:两个整型(page_number和result_per_page),一个string类型(query)。当然,你也可以为字段指定其他的合成类型,包括枚举(enumerations)或其他消息类型。

分配标识号

正如上述文件格式,在消息定义中,每个字段都有唯一的一个数字标识符。这些标识符是用来在消息的二进制格式中识别各个字段的,一旦开始使用就不能够再改变。注:[1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用2个字节。所以应该为那些频繁出现的消息元素保留 [1,15]之内的标识号。切记:要为将来有可能添加的、频繁出现的标识号预留一些标识号。

最小的标识号可以从1开始,最大到2^29 – 1, or 536,870,911。不可以使用其中的[19000-19999]的标识号, Protobuf协议实现中对这些进行了预留。如果非要在.proto文件中使用这些预留标识号,编译时就会报警。

指定字段规则

所指定的消息字段修饰符必须是如下之一:

  • required:一个格式良好的消息一定要含有1个这种字段。表示该值是必须要设置的;
  • optional:消息格式中该字段可以有0个或1个值(不超过1个)。
  • repeated:在一个格式良好的消息中,这种字段可以重复任意多次(包括0次)。重复的值的顺序会被保留。表示该值可以重复,相当于java中的List。

由于一些历史原因,基本数值类型的repeated的字段并没有被尽可能地高效编码。在新的代码中,用户应该使用特殊选项[packed=true]来保证更高效的编码。如:

1
repeated int32 samples = 4 [packed=true];

required是永久性的:在将一个字段标识为required的时候,应该特别小心。如果在某些情况下不想写入或者发送一个required的字段,将原始该字段修饰符更改为optional可能会遇到问题——旧版本的使用者会认为不含该字段的消息是不完整的,从而可能会无目的的拒绝解析。在这种情况下,你应该考虑编写特别针对于应用程序的、自定义的消息校验函数。Google的一些工程师得出了一个结论:使用required弊多于利;他们更 愿意使用optional和repeated而不是required。当然,这个观点并不具有普遍性。

添加更多消息类型

在一个.proto文件中可以定义多个消息类型。在定义多个相关的消息的时候,这一点特别有用——例如,如果想定义与SearchResponse消息类型对应的回复消息格式的话,你可以将它添加到相同的.proto文件中,如:

1
2
3
4
5
6
7
8
9
message SearchRequest {
required string query = 1;
optional int32 page_number = 2;
optional int32 result_per_page = 3;
}
message SearchResponse {
}

添加注释

向.proto文件添加注释,可以使用C/C++/java风格的双斜杠(//) 语法格式,如:

1
2
3
4
5
message SearchRequest {
required string query = 1;
optional int32 page_number = 2;// Which page number do we want?
optional int32 result_per_page = 3;// Number of results to return per page.
}

从.proto文件生成了什么?

当用protocolbuffer编译器来运行.proto文件时,编译器将生成所选择语言的代码,这些代码可以操作在.proto文件中定义的消息类型,包括获取、设置字段值,将消息序列化到一个输出流中,以及从一个输入流中解析消息。

  • 对C++来说,编译器会为每个.proto文件生成一个.h文件和一个.cc文件,.proto文件中的每一个消息有一个对应的类。
  • 对Java来说,编译器为每一个消息类型生成了一个.java文件,以及一个特殊的Builder类(该类是用来创建消息类接口的)。
  • 对Python来说,有点不太一样——Python编译器为.proto文件中的每个消息类型生成一个含有静态描述符的模块,,该模块与一个元类(metaclass)在运行时(runtime)被用来创建所需的Python数据访问类。

你可以从如下的文档链接中获取每种语言更多API。http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/reference/overview.html

标量数值类型

一个标量消息字段可以含有一个如下的类型——该表格展示了定义于.proto文件中的类型,以及与之对应的、在自动生成的访问类中定义的类型:

.proto类型 Java 类型 C++类型 备注
double double double
float float float
int32 int int32 使用可变长编码方式。编码负数时不够高效——如果你的字段可能含有负数,那么请使用sint32。
int64 long int64 使用可变长编码方式。编码负数时不够高效——如果你的字段可能含有负数,那么请使用sint64。
uint32 int[1] uint32 Uses variable-length encoding.
uint64 long[1] uint64 Uses variable-length encoding.
sint32 int int32 使用可变长编码方式。有符号的整型值。编码时比通常的int32高效。
sint64 long int64 使用可变长编码方式。有符号的整型值。编码时比通常的int64高效。
fixed32 int[1] uint32 总是4个字节。如果数值总是比总是比228大的话,这个类型会比uint32高效。
fixed64 long[1] uint64 总是8个字节。如果数值总是比总是比256大的话,这个类型会比uint64高效。
sfixed32 int int32 总是4个字节。
sfixed64 long int64 总是8个字节。
bool boolean bool
string String string 一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本。
bytes ByteString string 可能包含任意顺序的字节数据。

你可以在文章http://code.google.com/apis/protocolbuffers/docs/encoding.html 中,找到更多“序列化消息时各种类型如何编码”的信息。

Optional的字段和默认值

如上所述,消息描述中的一个元素可以被标记为“可选的”(optional)。一个格式良好的消息可以包含0个或一个optional的元素。当解 析消息时,如果它不包含optional的元素值,那么解析出来的对象中的对应字段就被置为默认值。默认值可以在消息描述文件中指定。例如,要为 SearchRequest消息的result_per_page字段指定默认值10,在定义消息格式时如下所示:

1
optional int32 result_per_page = 3 [default = 10];

如果没有为optional的元素指定默认值,就会使用与特定类型相关的默认值:对string来说,默认值是空字符串。对bool来说,默认值是false。对数值类型来说,默认值是0。对枚举来说,默认值是枚举类型定义中的第一个值。

枚举

当需要定义一个消息类型的时候,可能想为一个字段指定某“预定义值序列”中的一个值。例如,假设要为每一个SearchRequest消息添加一个 corpus字段,而corpus的值可能是UNIVERSAL,WEB,IMAGES,LOCAL,NEWS,PRODUCTS或VIDEO中的一个。 其实可以很容易地实现这一点:通过向消息定义中添加一个枚举(enum)就可以了。一个enum类型的字段只能用指定的常量集中的一个值作为其值(如果尝 试指定不同的值,解析器就会把它当作一个未知的字段来对待)。在下面的例子中,在消息格式中添加了一个叫做Corpus的枚举类型——它含有所有可能的值 ——以及一个类型为Corpus的字段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
message SearchRequest {
required string query = 1;
optional int32 page_number = 2;
optional int32 result_per_page = 3 [default = 10];
enum Corpus {
UNIVERSAL = 0;
WEB = 1;
IMAGES = 2;
LOCAL = 3;
NEWS = 4;
PRODUCTS = 5;
VIDEO = 6;
}
optional Corpus corpus = 4 [default = UNIVERSAL];
}

你可以为枚举常量定义别名。 需要设置allow_alias option 为 true, 否则 protocol编译器会产生错误信息。

1
2
3
4
5
6
7
8
9
10
11
enum EnumAllowingAlias {
option allow_alias = true;
UNKNOWN = 0;
STARTED = 1;
RUNNING = 1;
}
enum EnumNotAllowingAlias {
UNKNOWN = 0;
STARTED = 1;
// RUNNING = 1; // Uncommenting this line will cause a compile error inside Google and a warning message outside.
}

枚举常量必须在32位整型值的范围内。因为enum值是使用可变编码方式的,对负数不够高效,因此不推荐在enum中使用负数。如上例所示,可以在 一个消息定义的内部或外部定义枚举——这些枚举可以在.proto文件中的任何消息定义里重用。当然也可以在一个消息中声明一个枚举类型,而在另一个不同 的消息中使用它——采用MessageType.EnumType的语法格式。

当对一个使用了枚举的.proto文件运行protocol buffer编译器的时候,生成的代码中将有一个对应的enum(对Java或C++来说),或者一个特殊的EnumDescriptor类(对 Python来说),它被用来在运行时生成的类中创建一系列的整型值符号常量(symbolic constants)。

关于如何在你的应用程序的消息中使用枚举的更多信息,请查看所选择的语言http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/reference/overview.html。

使用其他消息类型

你可以将其他消息类型用作字段类型。例如,假设在每一个SearchResponse消息中包含Result消息,此时可以在相同的.proto文件中定义一个Result消息类型,然后在SearchResponse消息中指定一个Result类型的字段,如:

1
2
3
4
5
6
7
8
9
message SearchResponse {
repeated Result result = 1;
}
message Result {
required string url = 1;
optional string title = 2;
repeated string snippets = 3;
}

导入定义

在上面的例子中,Result消息类型与SearchResponse是定义在同一文件中的。如果想要使用的消息类型已经在其他.proto文件中已经定义过了呢?
你可以通过导入(importing)其他.proto文件中的定义来使用它们。要导入其他.proto文件的定义,你需要在你的文件中添加一个导入声明,如:

1
import “myproject/other_protos.proto”;

默认情况下你只能使用直接导入的.proto文件中的定义. 然而, 有时候你需要移动一个.proto文件到一个新的位置, 可以不直接移动.proto文件, 只需放入一个dummy .proto 文件在老的位置, 然后使用import转向新的位置:

1
2
// new.proto
// All definitions are moved here
1
2
3
4
// old.proto
// This is the proto that all clients are importing.
import publicnew.proto”;
import “other.proto”;

// client.proto

1
2
import “old.proto”;
// You use definitions from old.proto and new.proto, but not other.proto

protocol编译器就会在一系列目录中查找需要被导入的文件,这些目录通过protocol编译器的命令行参数-I/–import_path指定。如果不提供参数,编译器就在其调用目录下查找。

嵌套类型

你可以在其他消息类型中定义、使用消息类型,在下面的例子中,Result消息就定义在SearchResponse消息内,如:

1
2
3
4
5
6
7
8
message SearchResponse {
message Result {
required string url = 1;
optional string title = 2;
repeated string snippets = 3;
}
repeated Result result = 1;
}

如果你想在它的父消息类型的外部重用这个消息类型,你需要以Parent.Type的形式使用它,如:

1
2
3
message SomeOtherMessage {
optional SearchResponse.Result result = 1;
}

当然,你也可以将消息嵌套任意多层,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
message Outer { // Level 0
message MiddleAA { // Level 1
message Inner { // Level 2
required int64 ival = 1;
optional bool booly = 2;
}
}
message MiddleBB { // Level 1
message Inner { // Level 2
required int32 ival = 1;
optional bool booly = 2;
}
}
}

注:该特性已被弃用,在创建新的消息类型的时候,不应该再使用它——可以使用嵌套消息类型来代替它。

“组”是指在消息定义中嵌套信息的另一种方法。比如,在SearchResponse中包含若干Result的另一种方法是 :

1
2
3
4
5
6
7
message SearchResponse {
repeated group Result = 1 {
required string url = 2;
optional string title = 3;
repeated string snippets = 4;
}
}

一个“组”只是简单地将一个嵌套消息类型和一个字段捆绑到一个单独的声明中。在代码中,可以把它看成是含有一个Result类型、名叫result的字段的消息(后面的名字被转换成了小写,所以它不会与前面的冲突)。

因此,除了数据传输格式不同之外,这个例子与上面的SearchResponse例子是完全等价的。

更新一个消息类型

如果一个已有的消息格式已无法满足新的需求——如,要在消息中添加一个额外的字段——但是同时旧版本写的代码仍然可用。不用担心!更新消息而不破坏已有代码是非常简单的。在更新时只要记住以下的规则即可。

  • 不要更改任何已有的字段的数值标识。
    *所添加的任何字段都必须是optional或repeated的。这就意味着任何使用“旧”的消息格式的代码序列化的消息可以被新的代码所解析,因为它们 不会丢掉任何required的元素。应该为这些元素设置合理的默认值,这样新的代码就能够正确地与老代码生成的消息交互了。类似地,新的代码创建的消息 也能被老的代码解析:老的二进制程序在解析的时候只是简单地将新字段忽略。然而,未知的字段是没有被抛弃的。此后,如果消息被序列化,未知的字段会随之一 起被序列化——所以,如果消息传到了新代码那里,则新的字段仍然可用。注意:对Python来说,对未知字段的保留策略是无效的。
  • 非required的字段可以移除——只要它们的标识号在新的消息类型中不再使用(更好的做法可能是重命名那个字段,例如在字段前添加“OBSOLETE_”前缀,那样的话,使用的.proto文件的用户将来就不会无意中重新使用了那些不该使用的标识号)。
  • 一个非required的字段可以转换为一个扩展,反之亦然——只要它的类型和标识号保持不变。
  • int32, uint32, int64, uint64,和bool是全部兼容的,这意味着可以将这些类型中的一个转换为另外一个,而不会破坏向前、 向后的兼容性。如果解析出来的数字与对应的类型不相符,那么结果就像在C++中对它进行了强制类型转换一样(例如,如果把一个64位数字当作int32来 读取,那么它就会被截断为32位的数字)。
  • sint32和sint64是互相兼容的,但是它们与其他整数类型不兼容。
  • string和bytes是兼容的——只要bytes是有效的UTF-8编码。
  • 嵌套消息与bytes是兼容的——只要bytes包含该消息的一个编码过的版本。
  • fixed32与sfixed32是兼容的,fixed64与sfixed64是兼容的。

扩展

通过扩展,可以将一个范围内的字段标识号声明为可被第三方扩展所用。然后,其他人就可以在他们自己的.proto文件中为该消息类型声明新的字段,而不必去编辑原始文件了。看个具体例子:

1
2
3
4
message Foo {
//
extensions 100 to 199;
}

这个例子表明:在消息Foo中,范围[100,199]之内的字段标识号被保留为扩展用。现在,其他人就可以在他们自己的.proto文件中添加新字段到Foo里了,但是添加的字段标识号要在指定的范围内——例如:

1
2
3
extend Foo {
optional int32 bar = 126;
}

这个例子表明:消息Foo现在有一个名为bar的optional int32字段。

当用户的Foo消息被编码的时候,数据的传输格式与用户在Foo里定义新字段的效果是完全一样的。
然而,要在程序代码中访问扩展字段的方法与访问普通的字段稍有不同——生成的数据访问代码为扩展准备了特殊的访问函数来访问它。例如,下面是如何在C++中设置bar的值:

1
2
Foo foo;
foo.SetExtension(bar, 15);

类似地,Foo类也定义了模板函数 HasExtension(),ClearExtension(),GetExtension(),MutableExtension(),以及 AddExtension()。这些函数的语义都与对应的普通字段的访问函数相符。要查看更多使用扩展的信息,请参考相应语言的代码生成指南。注:扩展可 以是任何字段类型,包括消息类型。

嵌套的扩展

可以在另一个类型的范围内声明扩展,如:

1
2
3
4
5
6
message Baz {
extend Foo {
optional int32 bar = 126;
}
}

在此例中,访问此扩展的C++代码如下:

1
2
Foo foo;
foo.SetExtension(Baz::bar, 15);

In other words, the only effect is that bar is defined within the scope of Baz.

This is a common source of confusion: Declaring an extend block nested inside a message type does not imply any relationship between the outer type and the extended type. In particular, the above example does not mean that Baz is any sort of subclass of Foo. All it means is that the symbol bar is declared inside the scope of Baz; it’s simply a static member.

一个通常的设计模式就是:在扩展的字段类型的范围内定义该扩展——例如,下面是一个Foo的扩展(该扩展是Baz类型的),其中,扩展被定义为了Baz的一部分:

1
2
3
4
5
6
message Baz {
extend Foo {
optional Baz foo_ext = 127;
}
}

然而,并没有强制要求一个消息类型的扩展一定要定义在那个消息中。也可以这样做:

1
2
3
4
5
6
7
8
message Baz {
}
// This can even be in a different file.
extend Foo {
optional Baz foo_baz_ext = 127;
}

事实上,这种语法格式更能防止引起混淆。正如上面所提到的,嵌套的语法通常被错误地认为有子类化的关系——尤其是对那些还不熟悉扩展的用户来说。

选择可扩展的标量符号

在同一个消息类型中一定要确保两个用户不会扩展新增相同的标识号,否则可能会导致数据的不一致。可以通过为新项目定义一个可扩展标识号规则来防止该情况的发生。

如果标识号需要很大的数量时,可以将该可扩展标符号的范围扩大至max,其中max是229 – 1, 或536,870,911。如下所示:

1
2
3
4
5
message Foo {
extensions 1000 to max;
}

max 是 2^29 – 1, 或者 536,870,911.

通常情况下在选择标符号时,标识号产生的规则中应该避开[19000-19999]之间的数字,因为这些已经被Protocol Buffers实现中预留了。

Oneof

如果你的消息中有很多可选字段, 并且同时至多一个字段会被设置, 你可以加强这个行为,使用oneof特性节省内存.

Oneof字段就像可选字段, 除了它们会共享内存, 至多一个字段会被设置。 设置其中一个字段会清除其它oneof字段。 你可以使用case()或者WhichOneof() 方法检查哪个oneof字段被设置, 看你使用什么语言了.

使用Oneof

为了在.proto定义Oneof字段, 你需要在名字前面加上oneof关键字, 比如下面例子的test_oneof:

1
2
3
4
5
6
message SampleMessage {
oneof test_oneof {
string name = 4;
SubMessage sub_message = 9;
}
}

然后你可以增加oneof字段到 oneof 定义中. 你可以增加任意类型的字段, 但是不能使用 required, optional, repeated 关键字.

在产生的代码中, oneof字段拥有同样的 getters 和setters, 就像正常的可选字段一样. 也有一个特殊的方法来检查到底那个字段被设置. 你可以在相应的语言API中找到oneof API介绍.

Oneof 特性:

  • 设置oneof会自动清楚其它oneof字段的值. 所以设置多次后,只有最后一次设置的字段有值.
1
2
3
4
5
SampleMessage message;
message.set_name(“name”);
CHECK(message.has_name());
message.mutable_sub_message(); // Will clear name field.
CHECK(!message.has_name());
  • If the parser encounters multiple members of the same oneof on the wire, only the last member seen is used in the parsed message.
  • oneof不支持扩展.
  • oneof不能 repeated.
  • 反射API对oneof 字段有效.
  • 如果使用C++,需确保代码不会导致内存泄漏. 下面的代码会崩溃, 因为sub_message 已经通过set_name()删除了.
1
2
3
4
SampleMessage message;
SubMessage* sub_message = message.mutable_sub_message();
message.set_name(“name”); // Will delete sub_message
sub_message.set_… // Crashes here
  • Again in C++, if you Swap() two messages with oneofs, each message will end up with the other’s oneof case: in the example below, msg1 will have a sub_message and msg2 will have a name.
1
2
3
4
5
6
7
SampleMessage msg1;
msg1.set_name(“name”);
SampleMessage msg2;
msg2.mutable_sub_message();
msg1.swap(&msg2);
CHECK(msg1.has_sub_message());
CHECK(msg2.has_name());

向后兼容性问题

当增加或者删除oneof字段时一定要小心. 如果检查oneof的值返回None/NOT_SET, 它意味着oneof字段没有被赋值或者在一个不同的版本中赋值了。 你不会知道是哪种情况。

Tag 重用问题

  • Move optional fields into or out of a oneof: You may lose some of your information (some fields will be cleared) after the message is serialized and parsed.
  • Delete a oneof field and add it back: This may clear your currently set oneof field after the message is serialized and parsed.
  • Split or merge oneof: This has similar issues to moving regular optional fields.

包(Package)

当然可以为.proto文件新增一个可选的package声明符,用来防止不同的消息类型有命名冲突。如:

1
2
package foo.bar;
message Open { }

在其他的消息格式定义中可以使用包名+消息名的方式来定义域的类型,如:

1
2
3
4
5
message Foo {
required foo.bar.Open open = 1;
}

包的声明符会根据使用语言的不同影响生成的代码。

  • 对于C++,产生的类会被包装在C++的命名空间中,如上例中的Open会被封装在 foo::bar空间中;
  • 对于Java,包声明符会变为java的一个包,除非在.proto文件中提供了一个明确有java_package;
  • 对于 Python,这个包声明符是被忽略的,因为Python模块是按照其在文件系统中的位置进行组织的。

包及名称的解析

Protocol buffer语言中类型名称的解析与C++是一致的:首先从最内部开始查找,依次向外进行,每个包会被看作是其父类包的内部类。当然对于 (foo.bar.Baz)这样以“.”分隔的意味着是从最外围开始的。ProtocolBuffer编译器会解析.proto文件中定义的所有类型名。 对于不同语言的代码生成器会知道如何来指向每个具体的类型,即使它们使用了不同的规则。

定义服务(Service)

如果想要将消息类型用在RPC(远程方法调用)系统中,可以在.proto文件中定义一个RPC服务接口,protocol buffer编译器将会根据所选择的不同语言生成服务接口代码及存根。如,想要定义一个RPC服务并具有一个方法,该方法能够接收 SearchRequest并返回一个SearchResponse,此时可以在.proto文件中进行如下定义:

1
2
3
service SearchService {
rpc Search (SearchRequest) returns (SearchResponse);
}

protocol编译器将产生一个抽象接口SearchService以及一个相应的存根实现。存根将所有的调用指向RpcChannel,它是一 个抽象接口,必须在RPC系统中对该接口进行实现。如,可以实现RpcChannel以完成序列化消息并通过HTTP方式来发送到一个服务器。换句话说, 产生的存根提供了一个类型安全的接口用来完成基于protocolbuffer的RPC调用,而不是将你限定在一个特定的RPC的实现中。C++中的代码 如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using google::protobuf;
protobuf::RpcChannel* channel;
protobuf::RpcController* controller;
SearchService* service;
SearchRequest request;
SearchResponse response;
void DoSearch() {
// You provide classes MyRpcChannel and MyRpcController, which implement
// the abstract interfaces protobuf::RpcChannel and protobuf::RpcController.
channel = new MyRpcChannel(“somehost.example.com:1234”);
controller = new MyRpcController;
// The protocol compiler generates the SearchService class based on the
// definition given above.
service = new SearchService::Stub(channel);
// Set up the request.
request.set_query(“protocol buffers”);
// Execute the RPC.
service->Search(controller, request, response, protobuf::NewCallback(&Done));
}
void Done() {
delete service;
delete channel;
delete controller;
}

所有service类都必须实现Service接口,它提供了一种用来调用具体方法的方式,即在编译期不需要知道方法名及它的输入、输出类型。在服务器端,通过服务注册它可以被用来实现一个RPC Server。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using google::protobuf;
class ExampleSearchService : public SearchService {
public:
void Search(protobuf::RpcController* controller,
const SearchRequest* request,
SearchResponse* response,
protobuf::Closure* done) {
if (request->query() == “google”) {
response->add_result()->set_url(“http://www.google.com”);
} else if (request->query() == “protocol buffers”) {
response->add_result()->set_url(“http://protobuf.googlecode.com”);
}
done->Run();
}
};
int main() {
// You provide class MyRpcServer. It does not have to implement any
// particular interface; this is just an example.
MyRpcServer server;
protobuf::Service* service = new ExampleSearchService;
server.ExportOnPort(1234, service);
server.Run();
delete service;
return 0;
}

There are a number of ongoing third-party projects to develop RPC implementations for Protocol Buffers. For a list of links to projects we know about, see the third-party add-ons wiki page.

选项(Options)

在定义.proto文件时能够标注一系列的options。Options并不改变整个文件声明的含义,但却能够影响特定环境下处理方式。完整的可用选项可以在google/protobuf/descriptor.proto找到。

一些选项是文件级别的,意味着它可以作用于最外范围,不包含在任何消息内部、enum或服务定义中。一些选项是消息级别的,意味着它可以用在消息定 义的内部。当然有些选项可以作用在域、enum类型、enum值、服务类型及服务方法中。到目前为止,并没有一种有效的选项能作用于所有的类型。

如下就是一些常用的选择:

  • java_package (file option): 这个选项表明生成java类所在的包。如果在.proto文件中没有明确的声明java_package,就采用默认的包名。当然了,默认方式产生的 java包名并不是最好的方式,按照应用名称倒序方式进行排序的。如果不需要产生java代码,则该选项将不起任何作用。如:
1
option java_package = “com.example.foo”;
  • java_outer_classname (file option): 该选项表明想要生成Java类的名称。如果在.proto文件中没有明确的java_outer_classname定义,生成的class名称将会根据.proto文件的名称采用驼峰式的命名方式进行生成。如(foo_bar.proto生成的java类名为FooBar.java),如果不生成java代码,则该选项不起任何作用。如:
1
option java_outer_classname = “Ponycopter”;
  • optimize_for (fileoption): 可以被设置为 SPEED, CODE_SIZE,or LITE_RUNTIME。这些值将通过如下的方式影响C++及java代码的生成:
    • SPEED (default): protocol buffer编译器将通过在消息类型上执行序列化、语法分析及其他通用的操作。这种代码是最优的。
    • CODE_SIZE: protocol buffer编译器将会产生最少量的类,通过共享或基于反射的代码来实现序列化、语法分析及各种其它操作。采用该方式产生的代码将比SPEED要少得多, 但是操作要相对慢些。当然实现的类及其对外的API与SPEED模式都是一样的。这种方式经常用在一些包含大量的.proto文件而且并不盲目追求速度的 应用中。
    • LITE_RUNTIME: protocol buffer编译器依赖于运行时核心类库来生成代码(即采用libprotobuf-lite 替代libprotobuf)。这种核心类库由于忽略了一 些描述符及反射,要比全类库小得多。这种模式经常在移动手机平台应用多一些。编译器采用该模式产生的方法实现与SPEED模式不相上下,产生的类通过实现 MessageLite接口,但它仅仅是Messager接口的一个子集。
1
option optimize_for = CODE_SIZE;
  • cc_generic_services, java_generic_services, py_generic_services (file options): 在C++、java、python中protocol buffer编译器是否应该基于服务定义产生抽象服务代码。由于历史遗留问题,该值默认是true。但是自2.3.0版本以来,它被认为通过提供代码生成 器插件来对RPC实现更可取,而不是依赖于“抽象”服务。
1
2
3
4
5
6
7
// This file relies on plugins to generate service code.
option cc_generic_services = false;
option java_generic_services = false;
option py_generic_services = false;
  • message_set_wire_format (message option): 如果该值被设置为true,该消息将使用一种不同的二进制格式来与Google内部的MessageSet的老格式相兼容。对于Google外部的用户来说,该选项将不会被用到。如下所示:
1
2
3
4
5
6
7
message Foo {
option message_set_wire_format = true;
extensions 4 to max;
}
  • packed (field option): 如果该选项在一个整型基本类型上被设置为真,则采用更紧凑的编码方式。当然使用该值并不会对数值造成任何损失。在2.3.0版本之前,解析器将会忽略那些 非期望的包装值。因此,它不可能在不破坏现有框架的兼容性上而改变压缩格式。在2.3.0之后,这种改变将是安全的,解析器能够接受上述两种格式,但是在 处理protobuf老版本程序时,还是要多留意一下。
1
repeated int32 samples = 4 [packed=true];
  • deprecated (field option): 如果该选项被设置为true,表明该字段已经被弃用了,在新代码中不建议使用。在多数语言中,这并没有实际的含义。在java中,它将会变成一个 @Deprecated注释。也许在将来,其它基于语言声明的代码在生成时也会如此使用,当使用该字段时,编译器将自动报警。如:
1
optional int32 old_field = 6 [deprecated=true];

自定义选项

ProtocolBuffers允许自定义并使用选项。该功能应该属于一个高级特性,对于大部分人是用不到的。由于options是定在 google/protobuf/descriptor.proto中的,因此你可以在该文件中进行扩展,定义自己的选项。如:

1
2
3
4
5
6
7
8
9
import “google/protobuf/descriptor.proto”;
extend google.protobuf.MessageOptions {
optional string my_option = 51234;
}
message MyMessage {
option (my_option) = “Hello world!”;
}

在上述代码中,通过对MessageOptions进行扩展定义了一个新的消息级别的选项。当使用该选项时,选项的名称需要使用()包裹起来,以表明它是一个扩展。在C++代码中可以看出my_option是以如下方式被读取的。

1
string value = MyMessage::descriptor()->options().GetExtension(my_option);

在Java代码中的读取方式如下:

1
2
String value = MyProtoFile.MyMessage.getDescriptor().getOptions()
.getExtension(MyProtoFile.myOption);

在Python中:

1
2
value = my_proto_file_pb2.MyMessage.DESCRIPTOR.GetOptions()
.Extensions[my_proto_file_pb2.my_option]

正如上面的读取方式,定制选项对于Python并不支持。定制选项在protocol buffer语言中可用于任何结构。下面就是一些具体的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import “google/protobuf/descriptor.proto”;
extend google.protobuf.FileOptions {
optional string my_file_option = 50000;
}
extend google.protobuf.MessageOptions {
optional int32 my_message_option = 50001;
}
extend google.protobuf.FieldOptions {
optional float my_field_option = 50002;
}
extend google.protobuf.EnumOptions {
optional bool my_enum_option = 50003;
}
extend google.protobuf.EnumValueOptions {
optional uint32 my_enum_value_option = 50004;
}
extend google.protobuf.ServiceOptions {
optional MyEnum my_service_option = 50005;
}
extend google.protobuf.MethodOptions {
optional MyMessage my_method_option = 50006;
}
option (my_file_option) = “Hello world!”;
message MyMessage {
option (my_message_option) = 1234;
optional int32 foo = 1 [(my_field_option) = 4.5];
optional string bar = 2;
}
enum MyEnum {
option (my_enum_option) = true;
FOO = 1 [(my_enum_value_option) = 321];
BAR = 2;
}
message RequestType {}
message ResponseType {}
service MyService {
option (my_service_option) = FOO;
rpc MyMethod(RequestType) returns(ResponseType) {
// Note: my_method_option has type MyMessage. We can set each field
// within it using a separate “option” line.
option (my_method_option).foo = 567;
option (my_method_option).bar = “Some string”;
}
}

注:如果要在该选项定义之外使用一个自定义的选项,必须要由包名 + 选项名来定义该选项。如:

1
2
3
4
5
6
// foo.proto
import “google/protobuf/descriptor.proto”;
package foo;
extend google.protobuf.MessageOptions {
optional string my_option = 51234;
}
1
2
3
4
5
6
// bar.proto
import “foo.proto”;
package bar;
message MyMessage {
option (foo.my_option) = “Hello world!”;
}

最后一件事情需要注意:因为自定义选项是可扩展的,它必须象其它的域或扩展一样来定义标识号。正如上述示例,[50000-99999]已经被占 用,该范围内的值已经被内部所使用,当然了你可以在内部应用中随意使用。如果你想在一些公共应用中进行自定义选项,你必须确保它是全局唯一的。可以通过protobuf-global-extension-registry@google.com来获取全局唯一标识号。 只需提供你的项目名和项目网站. 通常你只需要一个扩展号。 你可以使用一个扩展号声明多个选项:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
message FooOptions {
optional int32 opt1 = 1;
optional string opt2 = 2;
}
extend google.protobuf.FieldOptions {
optional FooOptions foo_options = 1234;
}
// usage:
message Bar {
optional int32 a = 1 [(foo_options).opt1 = 123, (foo_options).opt2 = “baz”];
// alternative aggregate syntax (uses TextFormat):
optional int32 b = 2 [(foo_options) = { opt1: 123 opt2: “baz” }];
}

生成访问类

可以通过定义好的.proto文件来生成Java、Python、C++代码,需要基于.proto文件运行protocol buffer编译器protoc。运行的命令如下所示:

1
protoc –proto_path=IMPORT_PATH –cpp_out=DST_DIR –java_out=DST_DIR –python_out=DST_DIR path/to/file.proto
  • IMPORT_PATH声明了一个.proto文件所在的具体目录。如果忽略该值,则使用当前目录。如果有多个目录则可以 对–proto_path 写多次,它们将会顺序的被访问并执行导入。-I=IMPORT_PATH是它的简化形式。
  • 当然也可以提供一个或多个输出路径:

你必须提供一个或多个.proto文件作为输入。多个.proto文件能够一次全部声明。虽然这些文件是相对于当前目录来命名的,每个文件必须在一个IMPORT_PATH中,只有如此编译器才可以决定它的标准名称。


中文翻译出处: http://www.open-open.com/home/space.php?uid=37924&do=blog&id=5873
原文: https://developers.google.com/protocol-buffers/docs/proto#generating

转载时加入了新增加的内容

交换机数据包转发方法

2011-06-13 10:48:27

 在过去,交换机使用下面的两种转发方法之一来进行网络端口间的数据交换:存储转发交换或直通交换。按下“交换机转发方法”按钮可显示这两种方法。不过,存储转发是当前型号的 Cisco Catalyst 交换机中唯一使用的转发方法。

存储转发交换
在存储转发交换中,当交换机收到帧时,它将数据存储在缓冲区中,直到收下完整的帧。存储过程期间,交换机分析帧以获得有关其目的地的信息。在此过程中,交换机还将使用以太网帧的循环冗余校验 (CRC) 帧尾部分来执行错误检查。
CRC 根据帧中的位数(即 1 位的数量),使用数学公式来确定收到的帧是否有错。在确认帧的完整性之后,帧将从对应的端口转发出去,并发往其目的地。当在帧中检测到错误时,交换机放弃该帧。放弃有错的帧可减少损坏的数据所耗用的带宽量。存储转发交换对于融合网络中的服务质量 (QoS) 分析是必需的,在融合网络中,必须对帧进行分类以划分流量优先级。例如,IP 语音数据流的优先级需要高于 Web 浏览流量。
直通交换
在直通交换中,交换机在收到数据时立即处理数据,即使传输尚未完成。交换机只缓冲帧的一部分,缓冲的量仅足以读取目的 MAC 地址,以便确定转发数据时应使用的端口。目的 MAC 地址位于帧中前导码后面的前 6 个字节。交换机在其交换表中查找目的 MAC 地址,确定外发接口端口,然后通过指定的交换机端口将帧转发到其目的地。交换机对该帧不执行任何错误检查。由于交换机不必等待完全缓冲整个帧,且不执行任何错误检查,因此直通交换比存储转发交换更快。但是,因为交换机不执行任何错误检查,因此它会在网络中转发损坏的帧。转发损坏的帧时,这些帧会耗用带宽。目的网卡最终将放弃损坏的帧。
直通交换有两种变体:
快速转发交换:快速转发交换提供最低程度的延时。快速转发交换在读取目的地址之后立即转发数据包。由于快速转发交换在收到整个数据包之前就开始转发,因此有时候中继数据包时会出错。这种情况并不经常发生,而且目的网络适配器在收到含错数据包时会将其丢弃。在快速转发模式下,延时是指从收到第一个位到传出第一个位之间的时间差。快速转发交换是典型的直通交换方法。
免分片 (fragment) 交换:在免分片交换中,交换机在转发之前存储帧的前 64 个字节。可以将免分片交换视为存储转发交换和直通交换之间的折衷。免分片交换只存储帧的前 64 个字节的原因是,大部分网络错误和冲突都发生在前 64 个字节。免分片交换在转发帧之前对帧的前 64 个字节执行小错误检查以确保没有发生过冲突,并且尝试通过这种方法来增强直通交换功能。免分片交换是存储转发交换的高延时和高完整性与直通交换的低延时和弱完整性之间的折衷。
某些交换机可配置为按端口执行直通交换,当达到用户定义的错误阈值时,这些端口自动切换为存储转发。当错误率低于该阈值时,端口自动恢复到直通切换。

本文转自 “07net01” 博客,请务必保留此出处http://07net01.blog.51cto.com/1192774/586950

Android内核编译过程全解

之前编译过锤子的坚果手机内核,摸索了很长时间,遗憾的是没有把一些填坑的细节记录下来,免不了有些细节还得摸索一遍。这次要编译三星的Galaxy Note 5内核,加上已经有了一次成功经历,所以想把它系统化的记录下来,供自己或其他需要的人参考。

我这里主要讲内核的编译,至于关联到的一些其他工具的安装或配置,这里就不展开了,碰到这样的问题请移步问问Google。

1.  获取CPU信息

要为某手机编译内核,首先要了解手机所用的CPU,不同厂商生产的CPU,对应的linux内核是不一样的。

查看CPU信息的一种方法是利用adb,“adb shell cat /roc/cpuinfo”可以得到cpu架构和生产厂商。另外,利用“adb shell cat /proc/version”还可以得到手机中正在使用的内核信息。以下是我的Galaxy note 5的信息:将手机与PC通过usb线相连,首先查看adb是否已经可以访问、然后获取cpu信息、最后获取linux内核信息:

C:\Users\yuanhui>adb devices
List of devices attached
0715e7e408981f38 device

C:\Users\yuanhui>adb shell cat /proc/cpuinfo
Processor : AArch64 Processor rev 2 (aarch64)
processor : 0
processor : 1
processor : 2
processor : 3
processor : 4
processor : 5
processor : 6
processor : 7
Features : fp asimd aes pmull sha1 sha2 crc32
CPU implementer : 0x41
CPU architecture: AArch64
CPU variant : 0x0
CPU part : 0xd03
CPU revision : 2

Hardware : SAMSUNG Exynos7420

C:\Users\yuanhui>adb shell cat /proc/version
Linux version 3.10.61-6137732 (dpi@SWDC3312) (gcc version 4.9 20140514 (prerelea
se) (GCC) ) #1 SMP PREEMPT Fri Feb 5 13:33:23 KST 2016
另外,通过官网也可以获得比较详细的硬件信息及参数:

QQ截图20160401115714

2. 下载源代码

得到CPU信息后,就可以到Android网站去下载linux内核源码了,下载地址说明:

http://source.android.com/source/building-kernels.html#figuring-out-which-kernel-to-build

这里需要特别注意,三星的源码树有两个,一个是kernel/exynos,一个是kernel/samsung,Galaxy note 5 用的是exynos芯片,所以一定要下载exynos的源码。

QQ截图20160401115941

所以,git命令为:

$ git clone https://android.googlesource.com/kernel/exynos

这里稍微注意一下,由于国内googlesource.com被墙了,只能通过VPN才能下载。

我为了方便,所有下载及编译都是在Ubuntu里完成的:

hyh@ubuntu:~$ git clone https://android.googlesource.com/kernel/exynos
Cloning into ‘exynos’…
remote: Sending approximately 733.06 MiB …
remote: Counting objects: 9, done
remote: Finding sources: 100% (9/9)
Receiving objects: 100% (3159494/3159494), 733.07 MiB | 631.00 KiB/s, done.
remote: Total 3159494 (delta 2631328), reused 3159494 (delta 2631328)
Resolving deltas: 100% (2631328/2631328), done.
Checking connectivity… done.
hyh@ubuntu:~$

这样,在我的home下就生成了一个exynos目录,源码就在这个文件夹里。

为了后面使用方便,把目录改成了linux-kernel-exynos。

到里面看看都有些什么分支:

hyh@ubuntu:~$ cd linux-kernel-exynos/
hyh@ubuntu:~/linux-kernel-exynos$ git branch -r
origin/HEAD -> origin/master
origin/android-exynos-3.4
origin/android-exynos-koi-3.10-marshmallow-mr1-wear-release
origin/android-exynos-manta-3.4-adf
origin/android-exynos-manta-3.4-jb-mr1
origin/android-exynos-manta-3.4-jb-mr1-fr
origin/android-exynos-manta-3.4-jb-mr1.1
origin/android-exynos-manta-3.4-jb-mr2
origin/android-exynos-manta-3.4-kitkat-mr0
origin/android-exynos-manta-3.4-kitkat-mr1
origin/android-exynos-manta-3.4-kitkat-mr2
origin/android-exynos-manta-3.4-lollipop-mr1
origin/android-exynos-manta-3.4-lollipop-release
origin/master
hyh@ubuntu:~/linux-kernel-exynos$

之前看到手机用的是3.10的内核,那就把3.10的分支checkout出来:

hyh@ubuntu:~/linux-kernel-exynos$ git checkout origin/android-exynos-koi-3.10-marshmallow-mr1-wear-release
Checking out files: 100% (45351/45351), done.
Note: checking out ‘origin/android-exynos-koi-3.10-marshmallow-mr1-wear-release’.

You are in ‘detached HEAD’ state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

git checkout -b <new-branch-name>

HEAD is now at 0f9bded… Merge “Fix that wrong eint information is displayed” into android-exynos-koi-3.10
hyh@ubuntu:~/linux-kernel-exynos$

这时候才能看到目录下有了源代码(之前目录里面实际有个800多兆的.git文件夹,隐藏了,看不到而已)。

3. 配置/定制内核

通常情况下,各个厂商都会针对自己的手机做大量定制,如果编译内核时选择生成相应的配置文件,则我们可以从手机中直接得到。能拿到这个内核配置文件编译内核就会顺利很多。

C:\Users\yuanhui>adb pull /proc/config.gz

大部分情况都能拿到这个config文件,然后解压出其中的.config文件放到linux-kernel-exynos目录下,直接调用make ARCH=arm menuconfig即可定制内核。

但是很不幸,三星的这款手机没有此文件:

C:\Users\yuanhui>adb pull /proc/config.gz
remote object ‘/proc/config.gz’ does not exist

好吧,没有也没关系,直接make ARCH=arm menuconfig,通常情况也是能顺利编译的……

我这里为了把CP210x的驱动编译进去,从Silicon官网下载了Android内核编译CP210x驱动的文档,照着把CP210x驱动编译选项勾选好,保存退出,在linux-kernel-exynos目录下生成了一个.config的文件:

hyh@ubuntu:~/linux-kernel-exynos$ make ARCH=arm menuconfig
HOSTCC scripts/basic/fixdep
HOSTCC scripts/kconfig/conf.o
HOSTCC scripts/kconfig/lxdialog/checklist.o
HOSTCC scripts/kconfig/lxdialog/inputbox.o
HOSTCC scripts/kconfig/lxdialog/menubox.o
HOSTCC scripts/kconfig/lxdialog/textbox.o
HOSTCC scripts/kconfig/lxdialog/util.o
HOSTCC scripts/kconfig/lxdialog/yesno.o
HOSTCC scripts/kconfig/mconf.o
SHIPPED scripts/kconfig/zconf.tab.c
SHIPPED scripts/kconfig/zconf.lex.c
SHIPPED scripts/kconfig/zconf.hash.c
HOSTCC scripts/kconfig/zconf.tab.o
In file included from scripts/kconfig/zconf.tab.c:2503:0:
scripts/kconfig/menu.c: In function ‘get_symbol_str’:
scripts/kconfig/menu.c:567:18: warning: ‘jump’ may be used uninitialized in this function [-Wmaybe-uninitialized]
jump->offset = r->len – 1;
^
scripts/kconfig/menu.c:528:19: note: ‘jump’ was declared here
struct jump_key *jump;
^
HOSTLD scripts/kconfig/mconf
scripts/kconfig/mconf Kconfig
#
# using defaults found in /boot/config-4.2.0-23-generic
#
/boot/config-4.2.0-23-generic:928:warning: symbol value ‘m’ invalid for BRIDGE_NETFILTER
/boot/config-4.2.0-23-generic:2668:warning: symbol value ‘m’ invalid for STMMAC_PLATFORM
/boot/config-4.2.0-23-generic:3834:warning: symbol value ‘m’ invalid for GPIO_UCB1400
/boot/config-4.2.0-23-generic:4336:warning: symbol value ‘m’ invalid for MFD_WM8994
/boot/config-4.2.0-23-generic:4343:warning: symbol value ‘m’ invalid for REGULATOR_88PM8607
/boot/config-4.2.0-23-generic:4365:warning: symbol value ‘m’ invalid for REGULATOR_LP872X
/boot/config-4.2.0-23-generic:4367:warning: symbol value ‘m’ invalid for REGULATOR_LP8788
/boot/config-4.2.0-23-generic:4410:warning: symbol value ‘m’ invalid for REGULATOR_TWL4030
/boot/config-4.2.0-23-generic:5453:warning: symbol value ‘m’ invalid for SND_HDA_CODEC_REALTEK
/boot/config-4.2.0-23-generic:5454:warning: symbol value ‘m’ invalid for SND_HDA_CODEC_ANALOG
/boot/config-4.2.0-23-generic:5455:warning: symbol value ‘m’ invalid for SND_HDA_CODEC_SIGMATEL
/boot/config-4.2.0-23-generic:5456:warning: symbol value ‘m’ invalid for SND_HDA_CODEC_VIA
/boot/config-4.2.0-23-generic:5457:warning: symbol value ‘m’ invalid for SND_HDA_CODEC_HDMI
/boot/config-4.2.0-23-generic:5458:warning: symbol value ‘m’ invalid for SND_HDA_CODEC_CIRRUS
/boot/config-4.2.0-23-generic:5459:warning: symbol value ‘m’ invalid for SND_HDA_CODEC_CONEXANT
/boot/config-4.2.0-23-generic:5460:warning: symbol value ‘m’ invalid for SND_HDA_CODEC_CA0110
/boot/config-4.2.0-23-generic:5461:warning: symbol value ‘m’ invalid for SND_HDA_CODEC_CA0132
/boot/config-4.2.0-23-generic:5463:warning: symbol value ‘m’ invalid for SND_HDA_CODEC_CMEDIA
/boot/config-4.2.0-23-generic:5464:warning: symbol value ‘m’ invalid for SND_HDA_CODEC_SI3054
/boot/config-4.2.0-23-generic:5465:warning: symbol value ‘m’ invalid for SND_HDA_GENERIC
/boot/config-4.2.0-23-generic:6539:warning: symbol value ‘m’ invalid for COMEDI_PCI_DRIVERS
/boot/config-4.2.0-23-generic:6594:warning: symbol value ‘m’ invalid for COMEDI_PCMCIA_DRIVERS
/boot/config-4.2.0-23-generic:6602:warning: symbol value ‘m’ invalid for COMEDI_USB_DRIVERS
/boot/config-4.2.0-23-generic:7036:warning: symbol value ‘m’ invalid for LP8788_ADC
/boot/config-4.2.0-23-generic:8176:warning: symbol value ‘m’ invalid for KVM
configuration written to .config

*** End of the configuration.
*** Execute ‘make’ to start the build or try ‘make help’.

hyh@ubuntu:~/linux-kernel-exynos$

 

4. 编译内核

 

不同的CPU架构,就得选择不同架构的工具链。我们在刚开始的时候通过adb shell cat /proc/cpuinfo已经得到了CPU的家规信息,为aarch64,所以我们要选择的aarch64工具链来编译,Google Source 网站上同时提供了各个版本的编译工具:

https://android.googlesource.com/platform/prebuilts/

我们选择aarch64 gcc 4.9的版本来编译:

hyh@ubuntu:~$ git clone https://android.googlesource.com/platform/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9
Cloning into ‘aarch64-linux-android-4.9’…
remote: Sending approximately 180.15 MiB …
remote: Counting objects: 108, done
remote: Finding sources: 100% (108/108)
remote: Total 1641 (delta 990), reused 1641 (delta 990)
Receiving objects: 100% (1641/1641), 180.18 MiB | 620.00 KiB/s, done.
Resolving deltas: 100% (990/990), done.
Checking connectivity… done.
hyh@ubuntu:~$

把这个路径加入到$PATH中,以便编译时省去冗长的路径:

hyh@ubuntu:~/linux-kernel-exynos$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
hyh@ubuntu:~/linux-kernel-exynos$ export PATH=$PATH:~/aarch64-linux-android-4.9/bin
hyh@ubuntu:~/linux-kernel-exynos$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/home/hyh/aarch64-linux-android-4.9/bin

可以看到,用于编译内核的编译器路径已经添加到了$PATH中。

下面开始编译:
hyh@ubuntu:~/linux-kernel-exynos$ make ARCH=arm CROSS_COMPILE=arm-linux-androideabi- zImage
CHK include/generated/uapi/linux/version.h
CHK include/generated/utsrelease.h
make[1]: ‘include/generated/mach-types.h’ is up to date.
CC kernel/bounds.s
GEN include/generated/bounds.h
CC arch/arm/kernel/asm-offsets.s
In file included from include/linux/scatterlist.h:10:0,
from include/linux/dma-mapping.h:9,
from arch/arm/kernel/asm-offsets.c:15:
/home/hyh/linux-kernel-exynos/arch/arm/include/asm/io.h:30:28: fatal error: mach/exynos-ss.h: No such file or directory
#include <mach/exynos-ss.h>
^
compilation terminated.
/home/hyh/linux-kernel-exynos/./Kbuild:81: recipe for target ‘arch/arm/kernel/asm-offsets.s’ failed
make[1]: *** [arch/arm/kernel/asm-offsets.s] Error 1
Makefile:836: recipe for target ‘prepare0’ failed
make: *** [prepare0] Error 2
hyh@ubuntu:~/linux-kernel-exynos$

出错了,改个编译工具,网上下载了一个arm-eabi-4.8,放到hyh目录下,在$PATH中加入目录/home/hyh/arm-eabi-4.8/bin,编译。这次好点,编译了大部分代码,但是在编译驱动时出错了:

hyh@ubuntu:~/linux-kernel-exynos$ make ARCH=arm CROSS_COMPILE=arm-eabi- zImage
CHK include/linux/version.h
CHK include/generated/utsrelease.h
make[1]: ‘include/generated/mach-types.h’ is up to date.
CALL scripts/checksyscalls.sh
CHK include/generated/compile.h
CC drivers/mfd/ezx-pcap.o
drivers/mfd/ezx-pcap.c: In function ‘pcap_isr_work’:
drivers/mfd/ezx-pcap.c:205:2: error: implicit declaration of function ‘irq_to_gpio’ [-Werror=implicit-function-declaration]
} while (gpio_get_value(irq_to_gpio(pcap->spi->irq)));
^
cc1: some warnings being treated as errors
scripts/Makefile.build:305: recipe for target ‘drivers/mfd/ezx-pcap.o’ failed
make[2]: *** [drivers/mfd/ezx-pcap.o] Error 1
scripts/Makefile.build:441: recipe for target ‘drivers/mfd’ failed
make[1]: *** [drivers/mfd] Error 2
Makefile:945: recipe for target ‘drivers’ failed
make: *** [drivers] Error 2
hyh@ubuntu:~/linux-kernel-samsung$

发现ezx-pcap.c文件中有一段如下代码:

static void pcap_isr_work(struct work_struct *work)
{
struct pcap_chip *pcap = container_of(work, struct pcap_chip, isr_work);
struct pcap_platform_data *pdata = pcap->spi->dev.platform_data;
u32 msr, isr, int_sel, service;
int irq;

do {
ezx_pcap_read(pcap, PCAP_REG_MSR, &msr);
ezx_pcap_read(pcap, PCAP_REG_ISR, &isr);

/* We can’t service/ack irqs that are assigned to port 2 */
if (!(pdata->config & PCAP_SECOND_PORT)) {
ezx_pcap_read(pcap, PCAP_REG_INT_SEL, &int_sel);
isr &= ~int_sel;
}

ezx_pcap_write(pcap, PCAP_REG_MSR, isr | msr);
ezx_pcap_write(pcap, PCAP_REG_ISR, isr);

local_irq_disable();
service = isr & ~msr;
for (irq = pcap->irq_base; service; service >>= 1, irq++) {
if (service & 1)
generic_handle_irq(irq);
}
local_irq_enable();
ezx_pcap_write(pcap, PCAP_REG_MSR, pcap->msr);
} while (gpio_get_value(irq_to_gpio(pcap->spi->irq)));
}

定位到申明的位置:<linux/gpio.h>,在gpio.h文件中有这样一段申明:

static inline int irq_to_gpio(unsigned irq)
{
/* irq can never have been returned from gpio_to_irq() */
WARN_ON(1);
return -EINVAL;
}

可见被加了static,外部无法访问。而且,看到注释,以及返回值,可见这个函数是无用的。分析在ezx-pcap.c文件中的这段代码,do while会执行一次这段代码,但irq_to_gpio()永远只会返回失败,所以一定只会执行一次,所以果断把 “do{” 和 “}while”  这两行代码屏蔽:

static void pcap_isr_work(struct work_struct *work)
{
struct pcap_chip *pcap = container_of(work, struct pcap_chip, isr_work);
struct pcap_platform_data *pdata = pcap->spi->dev.platform_data;
u32 msr, isr, int_sel, service;
int irq;

//do {
ezx_pcap_read(pcap, PCAP_REG_MSR, &msr);
ezx_pcap_read(pcap, PCAP_REG_ISR, &isr);

/* We can’t service/ack irqs that are assigned to port 2 */
if (!(pdata->config & PCAP_SECOND_PORT)) {
ezx_pcap_read(pcap, PCAP_REG_INT_SEL, &int_sel);
isr &= ~int_sel;
}

ezx_pcap_write(pcap, PCAP_REG_MSR, isr | msr);
ezx_pcap_write(pcap, PCAP_REG_ISR, isr);

local_irq_disable();
service = isr & ~msr;
for (irq = pcap->irq_base; service; service >>= 1, irq++) {
if (service & 1)
generic_handle_irq(irq);
}
local_irq_enable();
ezx_pcap_write(pcap, PCAP_REG_MSR, pcap->msr);
//} while (gpio_get_value(irq_to_gpio(pcap->spi->irq)));
}

再次编译,过了:

……

LD vmlinux.o
MODPOST vmlinux.o
GEN .version
CHK include/generated/compile.h
UPD include/generated/compile.h
CC init/version.o
LD init/built-in.o
LD .tmp_vmlinux1
KSYM .tmp_kallsyms1.S
AS .tmp_kallsyms1.o
LD .tmp_vmlinux2
KSYM .tmp_kallsyms2.S
AS .tmp_kallsyms2.o
LD vmlinux
SYSMAP System.map
SYSMAP .tmp_System.map
OBJCOPY arch/arm/boot/Image
Kernel: arch/arm/boot/Image is ready
GZIP arch/arm/boot/compressed/piggy.gzip
AS arch/arm/boot/compressed/piggy.gzip.o
SHIPPED arch/arm/boot/compressed/lib1funcs.S
AS arch/arm/boot/compressed/lib1funcs.o
LD arch/arm/boot/compressed/vmlinux
OBJCOPY arch/arm/boot/zImage
Kernel: arch/arm/boot/zImage is ready
hyh@ubuntu:~/linux-kernel-exynos$

5. 后记

最后一步的编译经常会出一些具体的问题,比如某个包含文件找不到、某段代码编译出错,这种错误需要仔细分析错误提示,往往能定位到问题。源码过于庞大,而实际的应用千差万别,导致自定义后的某些模块之间出现问题。所以这样的问题只能是具体问题具体分析,没用的代码甚至可以屏蔽。这恰恰也是导致部分自定义代码无法编译通过的原因。

 

Android SDK DDMS通过Wifi接收Android日志

将Android手机与PC通过USB连接,我们就可以在Android Device Monitor中看到Android手机的实时日志,这个对程序的调试非常有帮助。但是一般来讲手机只有一个usb口,如果usb被别的设备占用,我们就没法获取日志了。

实际上,Android Device Monitor是通过手机端的ADB服务与PC通讯来显示日志的。adb非常流弊,指令丰富,功能齐全,是黑进手机的必备工具。这里就用到了adb的网络功能。要想拿到日志,必须在手机端启动adb的tcp服务,然后在PC端通过adb与手机的adb服务连接,这样我们就可以通过网络获取日志了,而不是usb有线方式获取日志。

具体步骤如下:

step 1. 将手机通过usb连接到PC机

step 2. 通过adb的tcpip命令启动一个tcp监听服务

step 3. 通过adb的connect指令从PC端连接Android手机

step 4. 启动Dalvik Debug Monitor,就会看到日志从网络上发动到了本机

 

具体操作如下:

D:\Android>adb tcpip 8630
* daemon not running. starting it now on port 5037 *
* daemon started successfully *
restarting in TCP mode port: 8630

D:\Android>adb connect 192.168.3.42:8630
unable to connect to 192.168.3.42:8630

 

 

D:\Android>adb devices
List of devices attached
90a16a93 device
D:\Android>adb connect 192.168.3.42:8630
unable to connect to 192.168.3.42:8630

D:\Android>adb tcpip 9999
restarting in TCP mode port: 9999

D:\Android>adb connect 192.168.3.42:9999
connected to 192.168.3.42:9999

 

参考:http://stackoverflow.com/questions/2604727/how-can-i-connect-to-android-with-adb-over-tcp

 

QQ截图20160311143749

在命令模式下编译Android NDK 的 *.so 库

为Android编译c++写的库文件(*.so文件)有很多种方式,实际上就是有很多种不同的工具可以选择:Eclipse+ADT、Android Studio,本质上还是调用android-ndk下的build-ndk(.bat)指令来编译arm版本的、针对Android操作系统的so文件。

我们在用Eclipse编译so文件时基本上就是建一个Android工程,为其定义一个编译配置(配置好的Android ndk路径、src路径、workspace路径等等),然后为其增加Application.mk和Android.mk两个文件,Eclipse就会自动调用Application.mk和Android.mk make文件去编译、生成so文件了。

我一直都在用Eclipse配置、编译so库,但说实话,Eclipse不但配置繁琐、容易出错,Eclipse对工程的管理也是非常不灵活的:我要是有多个不相关的so库需要编译,每次打开时都会全部加载(也许是我用的不好吧),编译时又得指定要编译的库单独编译;如果某个库的路径变了,Eclipse得重新配置,否则一大堆错误,真正要编译的项目却淹没其中。

既然Eclipse也不过是调用了Android-ndk的指令去结合Application.mk和Android.mk文件实现编译,为什么不能从命令模式直接调用android-ndk命令结合Application.mk和Android.mk文件来编译呢?这样每个项目各自不会纠缠在一起,干净利落,岂不美哉?

下面拿一个项目做个测试。

step 1: 建立一个目录,名称为:PerceptionNeuronPrj

step 2: 将项目的源码拷贝进去

这里是PerceptionNeuronSDK目录,可以看到此SDK的所有源代码都放在了PerceptionNeuronSDK目录下的src下(文件太多,打印此目录树时暂时移走了),对外的头文件直接放在PerceptionNeuronSDK目录下;

step 3: 加入依赖的第三方库或源码

与PerceptionNeuronSDK目录同级的是Eigen-3.2.2和InhouseLibs,即PerceptionNeuronSDK依赖的第三方库或源代码;

step 4: 创建用于编译PerceptionNeuronSDK的NDK配置文件

在PerceptionNeuronSDK目录下新建一个目录,这里命名为build_Android,同时在build_Android下新建jni目录、libs目录;

step 5: 为PerceptionNeuronSDK增加Application.mk及Android.mk配置文件

这里一定要小心,不能再Android.mk里通过脚本加载第三方的源码进去,而应该在PerceptionNeuronSDK的src目录中,在使用到诸如Eigen的地方通过#include引用!

附1:Application.mk文件内容

APP_ABI           := all
#APP_ABI          := armeabi armeabi-v7a x86

APP_OPTIM         := release
APP_PLATFORM      := android-8
#APP_BUILD_SCRIPT := Android.mk

# GNU STL implements most C++11 features. Use either gnustl_static or gnustl_shared
# Without this your C++ code will not be able to access headers like <thread>, <mutex>
#APP_STL      := stlport_static
#APP_CPPFLAGS := -std=c++11 -frtti -fexceptions 
APP_STL       := gnustl_static
APP_CPPFLAGS  := -std=gnu++11 -pthread -frtti -fexceptions -DNDEBUG #-NDEBUG -mfpu=neon -fomit-frame-pointer -DULM_BLOCKED -msse3 -mfpmath=sse

附2: Android.mk文件内容

# Copyright (C) 2009 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE   := PNLib

# 锟斤拷锟斤拷锟皆硷拷锟斤拷源锟侥硷拷目录锟斤拷源锟侥硷拷锟斤拷缀锟斤拷
MY_FILES_PATH  :=  $(LOCAL_PATH)/../../src

#$(warning $(MY_FILES_PATH))

MY_FILES_SUFFIX := %.cpp %.c

# 递归遍历目录下的所有的文件
rwildcard=$(wildcard $1$2) $(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2))

# 获取相应的源文件
MY_ALL_FILES := $(foreach src_path,$(MY_FILES_PATH), $(call rwildcard,$(src_path),*.*) ) 
MY_ALL_FILES := $(MY_ALL_FILES:$(MY_CPP_PATH)/./%=$(MY_CPP_PATH)%)
MY_SRC_LIST  := $(filter $(MY_FILES_SUFFIX),$(MY_ALL_FILES)) 
MY_SRC_LIST  := $(MY_SRC_LIST:$(LOCAL_PATH)/%=%)

# 去除字串的重复单词
define uniq =
  $(eval seen :=)
  $(foreach _,$1,$(if $(filter $_,${seen}),,$(eval seen += $_)))
  ${seen}
endef

# 递归遍历获取所有目录
MY_ALL_DIRS := $(dir $(foreach src_path,$(MY_FILES_PATH), $(call rwildcard,$(src_path),*/) ) )
MY_ALL_DIRS := $(call uniq,$(MY_ALL_DIRS))

# 赋值给NDK编译系统
LOCAL_SRC_FILES  := $(MY_SRC_LIST)
LOCAL_C_INCLUDES := $(MY_ALL_DIRS)

# Add additional include directories
LOCAL_C_INCLUDES += $(LOCAL_PATH)/../../
#LOCAL_C_INCLUDES += $(LOCAL_PATH)/../../../Eigen-3.2.2   
#必须从Android.mk配置文件中拿掉对Eigen的直接包含,放到程序代码中用相对路径包含:
# #include "../../Eigen-3.2.2/Eigen"
# using namespace Eigen;

#$(warning $(LOCAL_SRC_FILES))
#$(warning $(LOCAL_C_INCLUDES))

# use log system in NDK
LOCAL_LDLIBS += -llog

include $(BUILD_SHARED_LIBRARY)

step 6: 为了方便使用,增加一个build.bat文件

在里面加入如下脚本:

ndk-build NDK_APPLICATION_MK=./Application.mk

pause

至此,所需文件及配置均已建立完毕,在windows中通过 ‘tree /f’ 指令可以看到其中的目录结构如下所示:

 

 

1

双击build.bat或通过命令行加载build.bat文件,即可编译出结果:

D:\PerceptionNeuronPrj\PerceptionNeuronSDK\build_Android\jni>build.bat

D:\PerceptionNeuronPrj\PerceptionNeuronSDK\build_Android\jni>ndk-build NDK_APPLI
CATION_MK=./Application.mk
[arm64-v8a] Compile++      : PNLib <= AntiJointCompensation.cpp
[arm64-v8a] Compile++      : PNLib <= BVHPlayerWrapper.cpp
[arm64-v8a] Compile++      : PNLib <= BoneMapping.cpp
[arm64-v8a] Compile++      : PNLib <= BoneMass.cpp
[arm64-v8a] Compile++      : PNLib <= BoneTable.cpp
[arm64-v8a] Compile++      : PNLib <= BvhBinaryOutputPacker.cpp
[arm64-v8a] Compile++      : PNLib <= BvhDataConvert.cpp
In file included from D:/PerceptionNeuronPrj/PerceptionNeuronSDK/build_Android/j
ni/../../src/./AvatarManagement/../PluginMngr/Interface/IPluginActionRecog.h:3:0
,
                 from D:/PerceptionNeuronPrj/PerceptionNeuronSDK/build_Android/j
ni/../../src/./AvatarManagement/Avatar.h:13,
                 from D:/PerceptionNeuronPrj/PerceptionNeuronSDK/build_Android/j
ni/../../src/BvhDataConvert.cpp:9:
D:/PerceptionNeuronPrj/PerceptionNeuronSDK/build_Android/jni/../../src/./AvatarM
anagement/../PluginMngr/Interface/IPluginObject.h:55:69: warning: 'visibility' a
ttribute ignored [-Wattributes]
         PNLIB_PLUGIN_EXPORT typedef IPluginObject* (*GetPluginFunc)();
....................
.....
.                                                                 ^

 

编译速度非常慢,看项目大小和编译的架构多少,十几分钟几十分钟不等。

编译完成后,so文件自动放到libs下的相应架构目录下,目录结构如下:

QQ截图20160311143749

 

QQ截图20160311143406

step 7: 清除及重新编译

由于ndk-build.bat编译完项目后会缓存所有中间文件(*.obj及其他中间文件),再次运行ndk-build.bat只是简单的从缓冲拷贝so文件,所以需要增加一个build clean,以便清除缓冲:

附3:build_clean.bat

#自动到NDK_PROJECT_PATH目录下找jni目录,利用其中的Android.mk清除上次编译的结果
ndk-build NDK_PROJECT_PATH=../ clean

pause

 

在Unity Android 程序中使用动态库及注意事项

很多C/C++代码以动态库的方式供第三方调用,在Unity中,这类文件(dll for windows, *.so file for Android/Linux, *.dylib for MAC OSX)叫插件。

在Unity开发的Android程序中使用动态库插件是非常方便的,曾经因为被误导而放弃使用unity,转而研究Android通过原生Java的JNI方式包装*.so文件,虽然还行,但是JNI晦涩丑陋的API实在看着不舒服。相对而言,C#也是可以直接包装*.so文件并在Android系统中直接调用的,而且Unity制作Android app跟Unity制作其他平台的app是无缝的,只是在发布时选择要发布的平台就行了,真正做到了平台无关,极大的方便了开发。

我还没仔细研究如何在Android Studio中开发NDK,这里只介绍如何用Eclipse开发NDK。

在Eclipse中开发NDK的几个必须的安装项:NDK编译环境、Android SDK直接从官网单独下载单独安装;CDT、ADT插件可以直接从Eclipse的help的插件管理中安装。

然后就可以创建项目了:

1、打开Eclipse,通过File->New Project,弹出对话框,填入项目名称:

01

2、点Next,出现Config Project窗口:

02

由于只用于编译NDK,所以把前两项的钩钩去掉,第三个勾上,标记为so项目;目录也自定义一下,因为通常情况下我只会为编译so文件配置项目,真正的代码会单独放在更顶层的目录,方便跨平台的其他编译项目使用。结果如下:

03

4、按Finish结束创建过程,目录结构如下:

04

5、添加Native代码支持。也就是JNI相关的东西:在左侧项目根目录上右键->Android Tools->Add Native Support…

05

在随后弹出的对话框中输入要生成的so文件的名字:

06

这时候会发现多了一个目录:

07

6、编译配置

在jni目录中加入Application.mk文件和Android.mk文件

Application.mk:

APP_ABI      := all
#APP_ABI       := armeabi-v7a
#APP_ABI       += armeabi

#APP_OPTIM        := release
APP_PLATFORM     := android-8
#APP_BUILD_SCRIPT := Android.mk

# GNU STL implements most C++11 features. Use either gnustl_static or gnustl_shared
# Without this your C++ code will not be able to access headers like <thread>, <mutex>
#APP_STL := stlport_static
#APP_CPPFLAGS := -std=c++11 -frtti -fexceptions 
APP_STL       := gnustl_static
APP_CPPFLAGS  := -std=gnu++11 -pthread -frtti -fexceptions -DNDEBUG #-NDEBUG -mfpu=neon -fomit-frame-pointer -DULM_BLOCKED -msse3 -mfpmath=sse

Android.mk:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := PNLib
LOCAL_SRC_FILES := PNLib.cpp

include $(BUILD_SHARED_LIBRARY)

本来Android.mk文件很简单,把所有头文件、cpp文件加入,直接编译就行。但是对于我的项目这远远不够,因为我的代码要跨平台,有各个平台的编译项目单独出去,同时使用一份src文件,所以代码被放到了顶层目录中的src目录下,我需要遍历这个目录并把它加入NDK编译系统中来,所以下面从网上找了一段遍历头文件和cpp文件的脚本来用了:

# Copyright (C) 2009 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE   := PNLib

# 锟斤拷锟斤拷锟皆硷拷锟斤拷源锟侥硷拷目录锟斤拷源锟侥硷拷锟斤拷缀锟斤拷
MY_FILES_PATH  :=  $(LOCAL_PATH)/../../src

#$(warning $(MY_FILES_PATH))

MY_FILES_SUFFIX := %.cpp %.c

# 递归遍历目录下的所有的文件
rwildcard=$(wildcard $1$2) $(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2))

# 获取相应的源文件
MY_ALL_FILES := $(foreach src_path,$(MY_FILES_PATH), $(call rwildcard,$(src_path),*.*) ) 
MY_ALL_FILES := $(MY_ALL_FILES:$(MY_CPP_PATH)/./%=$(MY_CPP_PATH)%)
MY_SRC_LIST  := $(filter $(MY_FILES_SUFFIX),$(MY_ALL_FILES)) 
MY_SRC_LIST  := $(MY_SRC_LIST:$(LOCAL_PATH)/%=%)

# 去除字串的重复单词
define uniq =
  $(eval seen :=)
  $(foreach _,$1,$(if $(filter $_,${seen}),,$(eval seen += $_)))
  ${seen}
endef

# 递归遍历获取所有目录
MY_ALL_DIRS := $(dir $(foreach src_path,$(MY_FILES_PATH), $(call rwildcard,$(src_path),*/) ) )
MY_ALL_DIRS := $(call uniq,$(MY_ALL_DIRS))

# 赋值给NDK编译系统
LOCAL_SRC_FILES  := $(MY_SRC_LIST)
LOCAL_C_INCLUDES := $(MY_ALL_DIRS)

# Add additional include directories
LOCAL_C_INCLUDES += $(LOCAL_PATH)/../../
#LOCAL_C_INCLUDES += $(LOCAL_PATH)/../../../Eigen-3.2.2   
#必须从Android.mk配置文件中拿掉对Eigen的直接包含,放到程序代码中用相对路径包含:
# #include "../../Eigen-3.2.2/Eigen"
# using namespace Eigen;

#$(warning $(LOCAL_SRC_FILES))
#$(warning $(LOCAL_C_INCLUDES))

# use log system in NDK
LOCAL_LDLIBS += -llog

include $(BUILD_SHARED_LIBRARY)

7、最后一步是为此项目配置一个NDK编译工具:

A、项目->右键->Properties,弹出项目属性对话框:

08

B、选中Builders,点击New按钮,弹出框中选中“Program”,点击OK:

09

C、出现新Builder配置对话框,随便起个名字“New_Builder_for_NDK”;

在Main标签页中,“Location”项,通过“Browse File System…“找到NDK的build文件,windows系统为ndk-build.cmd,Mac或其他类Linux系统为ndk-build;

工作目录”Working Directory“指定当前项目下的jni目录就行了。

10

D、切到Refresh页,勾选”Refresh resources upon completion“

11

E、切到”Build Options“页,勾选”Specify working set of relevant resources“,点击后面按钮”Specify Resources…“,指本项目下的jni目录。

12

最后的编译条如下:

13

点击左边锤子图标即可对项目进行编译。剩下的工作就是一步步修正跨平台代码,最后生成PNLib.so文件。

 

以上步骤有几个地方需要特别注意:

1、使用gun++11

NKD支持c++11,GCC已经支持大部分c++11特性,所以可以直接使用gun++11。实际上,如果用c++11,Android版程序可能报错:DLLNotFundException,编译出来的动态库在Android系统中无法加载,所以Application.mk文件中必须如下配置:

APP_STL       := gnustl_static
APP_CPPFLAGS  := -std=gnu++11 -pthread -frtti -fexceptions -DNDEBUG

2、x64版ndk目前还有bug

你的系统即便是x64的,也别下载x64版本的ndk,否则会报找不到make.exe的问题(make.exe不是内部命令XXXXXXXXX)

 

3、不识别vector、list等模板类的问题

Add

APP_STL := stlport_static

to their Application.mk file.

其实Android插件应该用APP_STL := gnustl_static

 

4、使用第三方库Eigen导致array等冲突的问题

原因是在NDK的Android.mk文件中把Eigen的路径也加入到里面了。

正确的做法是,不在Android.mk文件加入Eigen,而是在程序代码中直接包含Eigen头文件。

 

Eclipse 创建、生成NDK步骤总结

本说明基于Eclipse+ADT+CDT开发,Eclipse版本号:Mars.1 Release 4.5.1、ADT版本:23.0.7.2120684、CDT版本:8.8.0.201509131935。

注:部分内容来自网络,这里只是做个资料收集整理,为自己和其他读者提供便利。

1、搭建NDK开发环境

Eclipse和Android SDK的安装就不说了,注意顺序,尤其是CDT和ADT的安装顺序

(1)下载NDK包,并解压http://developer.android.com/tools/sdk/ndk/index.html

(2)下载CDT(C/C++开发环境插件),在Eclipse中安装此插件。

(3)下载ADT,在Ecplise中安装此插件,一定要选中NDK Plugin

(4)在Ecplise中配置Android SDK和NDK SDK的路径,在Eclipse的Window->Preferences->Android中设置,我本机的设置为Android SDK Location为“C:\Android\android-sdk”,NDK Location为“C:\Android\android-ndk-r8e”。

2、创建NDK项目

(1)首先在Eclipse中创建一个Android工程

(2)在Package Explorer中选中刚才创建的Android工程,右键选择Android Tools -> Add Native Support..,填写需要生成的动态库的名称,比如“HelloWorld”,此时工程中多了一个叫做“jni”的目录,并自动生成了一个C++文件和一个Androd.MK文件,所有的C++源代码就放在这个目录中。这样,就跟Eclipse工程添加了C/C++属性,就能够用CDT插件在Eclipse中进行C/C++开发了。

(3)在Java代码中声明本地方法并调用
    网上资料很多
(4)在C/C++代码中实现本地方法
    网上资料很多

从C++的dll中Callback到C#返回数组:只取到了第一个元素

假设C/C++的dll中定义回调函数:

// Received data callback
typedef void(__stdcall *ReceivedDataCallback)(SerialPortRef sp, unsigned char* data, int len);


/*********************************************************
* Library API definitions *
/*********************************************************/
#ifdef __cplusplus
extern "C" {
#endif

 // Register handl to receive data from a SerialPort.
 SERIALPORTRW_API void RegisterReceivedDataCallback(SerialPortRef sp, ReceivedDataCallback handle);

//......................

C#中包含dll,通过包装dll的函数和回调函数,以便dll中收到数据时回调到C#:

// Wrapper of library by C#
namespace PluginImport
{
	#region delegates
    public delegate void ReceivedDataCallback(IntPtr serialPortRef, [MarshalAs(UnmanagedType.LPArray)]byte[] data, int len);
	#endregion
		
	#region API Wraper
	public class SerialPortDevice
	{
		// Register handl to receive data from a SerialPort.
		[DllImport("SerialPortDevice", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
        public static extern void RegisterReceivedDataCallback(UIntPtr sp, ReceivedDataCallback handle);

使用此DLL包装的API:

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            ReceivedDataCallback = (IntPtr serialPortRef, byte[] data, int len) =>
            {
                byte[] localData = data;

                this.Dispatcher.Invoke(new Action(() => { tbLog.Text += ByteUtils.bytesToHexString(localData, 0, len); }));
            };

            SerialPortStatusChanged = (IntPtr serialPortRef, SerialPortStatus status, string msg) =>
            {
                this.Dispatcher.Invoke(new Action(() => { tbLog.Text += "\r\n" + msg; }));
            };


            spRef = SerialPortDevice.CreateSerialPort();
            if (spRef != UIntPtr.Zero)
            {
                SerialPortDevice.RegisterReceivedDataCallback(spRef, ReceivedDataCallback);
                SerialPortDevice.RegisterSerialPortStatusChangedCallback(spRef, SerialPortStatusChanged);
            }

发现上面红色部分的data的长度总是一个字节。

原因:

marshaller不知道data的长度到底是多少,只是简单的按照数据类型返回了数组中的第一个元素,根本没有办法marshal。

正确的做法是将data定义为指针,然后通过拷贝的方法将数据取到C#端:

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            ReceivedDataCallback = (IntPtr serialPortRef, IntPtr data, int len) =>
            {
                byte[] localData = new byte[len];
                Marshal.Copy(data, localData, 0, len);
                
                string strHex = ByteUtils.bytesToHexString(localData, 0, len);

                this.Dispatcher.Invoke(new Action(() => { tbLog.Text += "\r\n" + strHex; }));
            };

            SerialPortStatusChanged = (IntPtr serialPortRef, SerialPortStatus status, string msg) =>
            {
                this.Dispatcher.Invoke(new Action(() => { tbLog.Text += "\r\n" + msg; }));
            };


            spRef = SerialPortDevice.CreateSerialPort();
            if (spRef != UIntPtr.Zero)
            {
                SerialPortDevice.RegisterReceivedDataCallback(spRef, ReceivedDataCallback);
                SerialPortDevice.RegisterSerialPortStatusChangedCallback(spRef, SerialPortStatusChanged);
            }
        }

当然,在这之前要在C#端的回调函数定义中的数据也要改成指针:

public delegate void ReceivedDataCallback(IntPtr serialPortRef, IntPtr data, int len);

 

Unity 5.0 中使用C/C++开发的dll插件使用中的问题 DllNotFoundException 问题集

Unity 中会经常用到C/C++开发的动态库(dll),Unity称为第三方插件,或plugin,这是非常厉害的扩展方法,但是Unity使用dll也非常痛苦,错误百出。

最常见的问题是 DllNotFoundException,就是找不到动态库。导致DllNotFoundException的原因很多,现把一些可能的情况,以及其解决方案一一列举:

1、DllNotFoundException: SerialPortDevice.dll
demo.Start () (at Assets/demo.cs:39)

Unity基于Mono实现的跨平台特性,但是Mono的 .net framework只充分实现了.net 2.0 subet,至于.net framework 4.5,照目前的速度,估计要等到下辈子了。

操作串口有很多问题,于是我自己用C++分装了一个dll,用于管理串口。从dll中导出的C#代码如下:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

// Wrapper of library by C#
namespace PluginImport
{
	#region delegates
	public delegate void ReceivedDataCallback(IntPtr serialPortRef, ref byte[] data, int len);
	//
	public delegate void SerialPortStatusChanged(IntPtr serialPortRef, SerialPortStatus status, string msg);
	#endregion
	
	#region Data types	
	// Serial Port Status
	public enum SerialPortStatus
	{
		PortClosed,              // 端口已关闭
		PortOpened,              // 端口已打开
		PortError,               // 端口错误
		PortInvalid,             // 端口不可用
	};
	
	// SerialPort Identity	
	public struct SerialPortRef
	{
		public IntPtr Identity;
	};
	
	#endregion
	
	#region API Wraper
	public class SerialPortDevice
	{
		// Create a serial port for lately operations. return Identity of new serial port.
		// Return NULL if create failed.
		[DllImport("SerialPortDevice.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
		[return: MarshalAs(UnmanagedType.Struct)]
		public static extern SerialPortRef CreateSerialPort();

		// Register handl to receive data from a SerialPort.
		[DllImport("SerialPortDevice.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
		public static extern void RegisterReceivedDataCallback(SerialPortRef sp, ReceivedDataCallback handle);
		
		// Register handl to receive serial port status changed event.
		[DllImport("SerialPortDevice.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
		public static extern void RegisterSerialPortStatusChangedCallback(SerialPortRef sp, SerialPortStatusChanged handle);
		
		// Open a serial port by name.
		// Return false if open failed.
		[DllImport("SerialPortDevice.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
		public static extern bool OpenSerialPort(SerialPortRef sp, string portName, int bauntRate);
		
		// Write and send data by serial port
		// Return the length sent by serial port
		[DllImport("SerialPortDevice.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
		public static extern int SerialPortWrite(SerialPortRef sp, byte[] data, int len);
		
		// Read data from serial port
		// Return the length readed from serial port
		[DllImport("SerialPortDevice.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
		public static extern int SerialPortRead(SerialPortRef sp, ref byte[] buffer, int bufferLen);
		
		// Check wether a serail port is openning.
		[DllImport("SerialPortDevice.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
		public static extern bool IsSerialPortOpenning(SerialPortRef sp);
		
		// Close a serial port.
		[DllImport("SerialPortDevice.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
		public static extern void CloseSerialPort(SerialPortRef sp);
		
		// Destroy a serial port.
		[DllImport("SerialPortDevice.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
		public static extern void DestroySerialPort(SerialPortRef sp);
	};
	#endregion
}

然后把dll拷贝到Unit Prj 的assert目录下,报错:

DllNotFoundException: SerialPortDevice.dll
demo.Start () (at Assets/demo.cs:39)

折腾了很久,发现在导入dll根本不需要后缀“.dll”,即写上dll文件名就OK了,不需要.dll后缀:

		[DllImport("SerialPortDevice", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
		[return: MarshalAs(UnmanagedType.Struct)]

但是这是VS的C#项目的默认做法。一个习惯性的动作导致了这个问题。于是把所有“.dll”删除,OK了。

 

2、Failed to load ‘Assets/PNLib.dll’ with error ‘The specified module could not be found.‘, GetDllDirectory returned ”. If GetDllDirectory returned non empty path, check that you’re using SetDirectoryDll correctly.

这种情况是,找到dll了,但是无法装载。看提示基本还是一头雾水: ‘The specified module could not be found.‘, GetDllDirectory returned ”。看似还是路径的问题。

这个问题测试了很多种情况,基本上是,只要是dll放到Assets目录下(或Assets下创建的Plugin目录下)就都会提示这个错误。而放到Assets外与Assets同目录就OK了,这难道不是Unity的某个bug吗?更奇怪的是之前在Assets下的Plugin里放的另一个dll(SerialPortDevice.dll)就不会有这样的问题。何解?

 

如果还有其他问题,后续继续补充。。。。。。

 

Image not found when using dylib in xcode

Some errors like this:

dyld: Library not loaded: /usr/local/lib/libXXX.dylib
Referenced from: /sers/david/Projekte/Test/build/Debug/Test.app/Contents/MacOS/Test
Reason: image not found

 

Answer:

Make sure to install the command line tools from developer.apple.com and then add the library in your project . Run the following script in your Xcode :Targets->Build Phases->Run Script and just execute :
install_name_tool -id @executable_path/../Frameworks/librayName.dylib “$SRCROOT/librayName.dylib”
——itechnician

 

Important information:

Before going for solution, you should know what is new with dependent library “dylib” in MAC as compare to dependent library “dll” in windows.

The major difference in dylib vs dll is “install name“. The install name is a path baked into the dynamic library that says where to find the library at runtime. It does not matter where you copy your dylibs, It will always point to old path(except without changing install name). You can know original search path(install name) by using command as below

otool -L a.dylib

(just drag dylib in place of a.dylib)

For more detail about install name, refer the link : http://www.chilkatforum.com/questions/4235/how-to-distribute-a-dylib-with-a-mac-os-x-application

Now, solution for changing the new location for dylib(“install name”) is just use install_name_tool as below

install_name_tool -change old path of dylb new path of dylib

you can get old path by otool -L command described above already.(old path i.e. original path)

“install_name_tool -change” can change search path of dylib and executable as well.

You can use this in Xcode by writing in “Run script” file in your project.

——NIlesh Srivastava

Address:

http://stackoverflow.com/questions/4876740/xcode-keeps-searching-dylib-at-wrong-path

 

================================================

In fact, there are 8 steps to deal with this issue:

1. Drag your custom dylib file to project

2. Click “+” at top of “Build Phases” page to add a “Copy Files” segment

3. Select “Framework” at destination line

4. Click “+” at the bottom of copy files segment to add your dylib

5. Click “+” at top of “Build Phases” page to add a “Run Script” segment

6. Past bellow line in shell box:

install_name_tool -id @executable_path/../Frameworks/libraryName.dylib “$SRCROOT/libraryName.dylib”

7. Change “libraryName.dylib” to your library name

8. Build.

屏幕快照 2014-12-25 下午2.12.25

Good luck! :)

 

Elapsed time in milliseconds in a C program

Use ‘gettimeofdata’ to do this job:

#include <sys/time.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>

/* Return 1 if the difference is negative, otherwise 0.  */
int timeval_subtract(struct timeval *result, struct timeval *t2, struct timeval *t1)
{
    long int diff = (t2->tv_usec + 1000000 * t2->tv_sec) - (t1->tv_usec + 1000000 * t1->tv_sec);
    result->tv_sec = diff / 1000000;
    result->tv_usec = diff % 1000000;

    return (diff<0);
}

void timeval_print(struct timeval *tv)
{
    char buffer[30];
    time_t curtime;

    printf("%ld.%06ld", tv->tv_sec, tv->tv_usec);
    curtime = tv->tv_sec;
    strftime(buffer, 30, "%m-%d-%Y  %T", localtime(&curtime));
    printf(" = %s.%06ld\n", buffer, tv->tv_usec);
}

int main()
{
    struct timeval tvBegin, tvEnd, tvDiff;

    // begin
    gettimeofday(&tvBegin, NULL);
    timeval_print(&tvBegin);

    // lengthy operation
    int i,j;
    for(i=0;i<999999L;++i) {
    	j=sqrt(i);
    }

    //end
    gettimeofday(&tvEnd, NULL);
    timeval_print(&tvEnd);

    // diff
    timeval_subtract(&tvDiff, &tvEnd, &tvBegin);
    printf("%ld.%06ld\n", tvDiff.tv_sec, tvDiff.tv_usec);

    return 0;
}

 

Check this thread to find more formation:

http://stackoverflow.com/questions/1468596/calculating-elapsed-time-in-a-c-program-in-milliseconds

 

Returning Strings from a C++ API to C#

从C++的dll中返回字符串是一种经常用到的C#调用C++函数的方式,但是带有返回值的调用一直是说不清道不明的问题,不细细研究,的确没法用起来。

这里直接给出一个正确的做法,后面会提供一份Google出来的资料,讲的非常好。原理知道了,对于返回结构体也应该是可以按同理搞定的。

 

Code in C++ dll:

// If any error, you can call 'BRGetLastErrorMessage' to get error information
BDR_API char* BRGetLastErrorMessage()
{
    return Error::Instance()->GetLastError();

    // follow code is not platform-cross, so throw away.

#ifdef __OS_XUN__
    return Error::Instance()->GetLastError();
#else
    char* msg = Error::Instance()->GetLastError();
    int len = strlen(msg);

    char* pszReturn = (char*)::CoTaskMemAlloc(len);
    // Copy the contents of szSampleString
    // to the memory pointed to by pszReturn.
    strcpy(pszReturn, msg);
    // Return pszReturn.
    return pszReturn;
#endif
}

 

C# side code:

        // If any error, you can call 'BRGetLastErrorMessage' to get error information
        [DllImport("BVHDataReader.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        //[return: MarshalAs(UnmanagedType.LPStr)]
        private static extern IntPtr BRGetLastErrorMessage();
        public static string strBRGetLastErrorMessage()
        {
            // Receive the pointer to Unicde character array
            // from API.
            IntPtr ptr = BRGetLastErrorMessage();
            // Construct a string from the pointer.
            string str = Marshal.PtrToStringAnsi(ptr);
            // Display the string.
            Console.WriteLine("Returned string : " + str);
            return str;
        }

 

 

下面是同名资料:

Returning Strings from a C++ API to C#

1. Introduction.

1.1 APIs that return strings are very common. However, the internal nature of such APIs, as well as the use of such APIs in managed code, require special attention. This blog will demonstrate both concerns.

1.2 I will present several techniques for returning an unmanaged string to managed code. But before that I shall first provide an in-depth explanation on the low-level activities that goes on behind the scenes. This will pave the way towards easier understanding of the codes presented later in this blog.

2. Behind the Scenes.

2.1 Let’s say we want to declare and use an API written in C++ with the following signature :

char* __stdcall StringReturnAPI01();

This API is to simply return a NULL-terminated character array (a C string).

2.2 To start with, note that a C string has no direct representation in managed code. Hence we simply cannot return a C string and expect the CLR to be able to transform it into a managed string.

2.3 The managed string is non-blittable. It can have several representations in unmanaged code : e.g. C-style strings (ANSI and Unicode-based) and BSTRs. Hence, it is important that you specify this information in the declaration of the unmanaged API, e.g. :

[DllImport("<path to DLL>", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
[return: MarshalAs(UnmanagedType.LPStr)]
public static extern string StringReturnAPI01();

In the above declaration, note that the following line :

[return: MarshalAs(UnmanagedType.LPStr)]

indicates that the return value from the API is to be treated as a NULL-terminated ANSI character array (i.e. a typical C-style string).

2.4 Now this unmanaged C-style string return value will then be used by the CLR to create a managed string object. This is likely achieved by using theMarshal.PtrToStringAnsi() method with the incoming string pointer treated as an IntPtr.

2.5 Now a very important concept which is part and parcel of the whole API calling operation is memory ownership. This is an important concept because it determines who is responsible for the deallocation of this memory. Now the StringReturnAPI01() API supposedly returns a string. The string should thus be considered equivalent to an “out” parameter, It is owned by the receiver of the string, i.e. the C# client code. More precisely, it is the CLR’s Interop Marshaler that is the actual receiver.

2.6 Now being the owner of the returned string, the Interop Marshaler is at liberty to free the memory associated with the string. This is precisely what will happen. When the Interop Marshaler has used the returned string to construct a managed string object, the NULL-terminated ANSI character array pointed to by the returned character pointer will be deallocated.

2.7 Hence it is very important to note the general protocol : the unmanaged code will allocate the memory for the string and the managed side will deallocate it. This is the same basic requirement of “out” parameters.

2.8 Towards this protocol, there are 2 basic ways that memory for an unmanaged string can be allocated (in unmanaged code) and then automatically deallocated by the CLR (more specifically, the interop marshaler) :

  • CoTaskMemAlloc()/Marshal.FreeCoTaskMem().
  • SysAllocString/Marshal.FreeBSTR().

Hence if the unmanaged side used CoTaskMemAlloc() to allocate the string memory, the CLR will use the Marshal.FreeCoTaskMem() method to free this memory.

The SysAllocString/Marshal.FreeBSTR() pair will only be used if the return type is specified as being a BSTR. This is not relevant to the example given in point 2.1 above. I will demonstrate a use of this pair in section 5 later.

2.9 N.B. : Note that the unmanaged side must not use the “new” keyword or the “malloc()” C function to allocate memory. The Interop Marshaler will not be able to free the memory in these situations. This is because the “new” keyword is compiler dependent and the “malloc” function is C-library dependent. CoTaskMemAlloc(), and SysAllocString() on the other hand, are Windows APIs which are standard.

Another important note is that although GlobalAlloc() is also a standard Windows API and it has a counterpart managed freeing method (i.e. Marshal.FreeHGlobal()), the Interop Marshaler will only use the Marshal.FreeCoTaskMem() method for automatic memory freeing of NULL-terminated strings allocated in unmanaged code. Hence do not use GlobalAlloc() unless you intend to free the allocated memory by hand using Marshal.FreeHGlobal() (an example of this is give in section 6 below).

3. Sample Code.

3.1 In this section, based on the principles presented in section 2, I shall present sample codes to demonstrate how to return a string from an unmanaged API and how to declare such an API in managed code.

3.2 The following is a listing of the C++ function which uses CoTaskMemAlloc() :

extern "C" __declspec(dllexport) char*  __stdcall StringReturnAPI01()
{
    char szSampleString[] = "Hello World";
    ULONG ulSize = strlen(szSampleString) + sizeof(char);
    char* pszReturn = NULL;

    pszReturn = (char*)::CoTaskMemAlloc(ulSize);
    // Copy the contents of szSampleString
    // to the memory pointed to by pszReturn.
    strcpy(pszReturn, szSampleString);
    // Return pszReturn.
    return pszReturn;
}

3.4 The C# declaration and sample call :

[DllImport("<path to DLL>", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
[return: MarshalAs(UnmanagedType.LPStr)]
public static extern string StringReturnAPI01();

static void CallUsingStringAsReturnValue()
{
  string strReturn01 = StringReturnAPI01();
  Console.WriteLine("Returned string : " + strReturn01);
}

3.5 Note the argument used for the MarshalAsAttribute : UnmanagedType.LPStr. This indicates to the Interop Marshaler that the return string from StringReturnAPI01() is a pointer to a NULL-terminated ANSI character array.

3.6 What happens under the covers is that the Interop Marshaler uses this pointer to construct a managed string. It likely uses the Marshal.PtrToStringAnsi() method to perform this. The Interop Marshaler will then use the Marshal.FreeCoTaskMem() method to free the character array.

4. Using a BSTR.

4.1 In this section, I shall demonstrate here how to allocate a BSTR in unmanaged code and return it in managed code together with memory deallocation.

4.2 Here is a sample C++ code listing :

extern "C" __declspec(dllexport) BSTR  __stdcall StringReturnAPI02()
{
  return ::SysAllocString((const OLECHAR*)L"Hello World");
}

4.3 And the C# declaration and usage :

[DllImport("<path to DLL>", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
[return: MarshalAs(UnmanagedType.BStr)]
public static extern string StringReturnAPI02();

static void CallUsingBSTRAsReturnValue()
{
  string strReturn = StringReturnAPI02();
  Console.WriteLine("Returned string : " + strReturn);
}

Note the argument used for the MarshalAsAttribute : UnmanagedType.BStr. This indicates to the Interop Marshaler that the return string from StringReturnAPI02() is a BSTR.

4.4 The Interop Marshaler then uses the returned BSTR to construct a managed string. It likely uses the Marshal.PtrToStringBSTR() method to perform this. The Interop Marshaler will then use the Marshal.FreeBSTR() method to free the BSTR.

5. Unicode Strings.

5.1 Unicode strings can be returned easily too as the following sample code will demonstrate.

5.2 Here is a sample C++ code listing :

extern "C" __declspec(dllexport) wchar_t*  __stdcall StringReturnAPI03()
{
  // Declare a sample wide character string.
  wchar_t  wszSampleString[] = L"Hello World";
  ULONG  ulSize = (wcslen(wszSampleString) * sizeof(wchar_t)) + sizeof(wchar_t);
  wchar_t* pwszReturn = NULL;

  pwszReturn = (wchar_t*)::CoTaskMemAlloc(ulSize);
  // Copy the contents of wszSampleString
  // to the memory pointed to by pwszReturn.
  wcscpy(pwszReturn, wszSampleString);
  // Return pwszReturn.
  return pwszReturn;
}

5.3 And the C# declaration and usage :

[DllImport("<path to DLL>", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
[return: MarshalAs(UnmanagedType.LPWStr)]
public static extern string StringReturnAPI03();

static void CallUsingWideStringAsReturnValue()
{
  string strReturn = StringReturnAPI03();
  Console.WriteLine("Returned string : " + strReturn);
}

The fact that a wide charactered string is now returned requires the use of the UnmanagedType.LPWStr argument for the MarshalAsAttribute.

5.4 The Interop Marshaler uses the returned wide-charactered string to construct a managed string. It likely uses the Marshal.PtrToStringUni() method to perform this. The Interop Marshaler will then use the Marshal.FreeCoTaskMem() method to free the wide-charactered string.

6. Low-Level Handling Sample 1.

6.1 In this section, I shall present some code that will hopefully cement the reader’s understanding of the low-level activities that had been explained in section 2 above.

6.2 Instead of using the Interop Marshaler to perform the marshaling and automatic memory deallocation, I shall demonstrate how this can be done by hand in managed code.

6.3 I shall use a new API which resembles the StringReturnAPI01() API which returns a NULL-terminated ANSI character array :

extern "C" __declspec(dllexport) char*  __stdcall PtrReturnAPI01()
{
  char   szSampleString[] = "Hello World";
  ULONG  ulSize = strlen(szSampleString) + sizeof(char);
  char*  pszReturn = NULL;

  pszReturn = (char*)::GlobalAlloc(GMEM_FIXED, ulSize);
  // Copy the contents of szSampleString
  // to the memory pointed to by pszReturn.
  strcpy(pszReturn, szSampleString);
  // Return pszReturn.
  return pszReturn;
}

6.4 And the C# declaration :

[DllImport("<path to DLL>", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr PtrReturnAPI01();

Note that this time, I have indicated that the return value is an IntPtr. There is no [return : …] declaration and so no unmarshaling will be performed by the Interop Marshaler.

6.5 And the C# low-level call :

static void CallUsingLowLevelStringManagement()
{
  // Receive the pointer to ANSI character array
  // from API.
  IntPtr pStr = PtrReturnAPI01();
  // Construct a string from the pointer.
  string str = Marshal.PtrToStringAnsi(pStr);
  // Free the memory pointed to by the pointer.
  Marshal.FreeHGlobal(pStr);
  pStr = IntPtr.Zero;
  // Display the string.
  Console.WriteLine("Returned string : " + str);
}

This code demonstrates an emulation of the Interop Marshaler in unmarshaling a NULL-terminated ANSI string. The returned pointer from PtrReturnAPI01() is used to construct a managed string. The pointer is then freed. The managed string remains intact with a copy of the returned string.

The only difference between this code and the actual one by the Interop Marshaler is that the GlobalAlloc()/Marshal.FreeHGlobal() pair is used. The Interop Marshaler always uses Marshal.FreeCoTaskMem() and expects the unmanaged code to use ::CoTaskMemAlloc().

7. Low-Level Handling Sample 2.

7.1 In this final section, I shall present one more low-level string handling technique similar to the one presented in section 6 above.

7.2 Again we do not use the Interop Marshaler to perform the marshaling and memory deallocation. Additionally, we will also not release the memory of the returned string.

7.3 I shall use a new API which simply returns a NULL-terminated Unicode character array which has been allocated in a global unmanaged memory :

wchar_t gwszSampleString[] = L"Global Hello World";

extern "C" __declspec(dllexport) wchar_t*  __stdcall PtrReturnAPI02()
{
  return gwszSampleString;
}

This API returns a pointer to the pre-allocated global Unicode string “gwszSampleString”. Because it is allocated in global memory and may be shared by various functions in the DLL, it is crucial that it is not deleted.

7.4 The C# declaration for PtrReturnAPI02() is listed below :

[DllImport("<path to DLL>", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr PtrReturnAPI02();

Again, there is no declaration for interop marshaling (no use of the [return : …] declaration). The returned IntPtr is returned as is.

7.5 And a sample C# code to manage the returned IntPtr :

static void CallUsingLowLevelStringManagement02()
{
  // Receive the pointer to Unicde character array
  // from API.
  IntPtr pStr = PtrReturnAPI02();
  // Construct a string from the pointer.
  string str = Marshal.PtrToStringUni(pStr);
  // Display the string.
  Console.WriteLine("Returned string : " + str);
}

Here, the returned IntPtr is used to construct a managed string from an unmanaged NULL-terminated Unicode string. The memory of the unmanaged Unicode string is then left alone and is not deleted.

Note that because a mere IntPtr is returned, there is no way to know whether the returned string is ANSI or Unicode. In fact, there is no way to know whether the IntPtr actually points to a NULL-terminated string at all. This knowledge has to be known in advance.

7.6 Furthermore, the returned IntPtr must not point to some temporary string location (e.g. one allocated on the stack). If this was so, the temporary string may be deleted once the API returns. The following is an example :

extern "C" __declspec(dllexport) char* __stdcall PtrReturnAPI03()
{
  char szSampleString[] = "Hello World";
  return szSampleString;
}

By the time this API returns, the string contained in “szSampleString” may be completely wiped out or be filled with random data. The random data may not contain any NULL character until many bytes later. A crash may ensue a C# call like the following :

IntPtr pStr = PtrReturnAPI03();
// Construct a string from the pointer.
string str = Marshal.PtrToStringAnsi(pStr);

Link:http://limbioliong.wordpress.com/2011/06/16/returning-strings-from-a-c-api/

 

Apple ios8 beta download

WWDC2014于2014年6月2日(北京时间6月3日凌晨)发布了IOS8 bet版,以及其他一些重量级内容!
这里只提供IOS8 beta下载地址,方便各位发烧友尝鲜 :)

 

官 方 下 载 地 址


iPad  Air

iPad air(Model A1474)WIFI
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_air_model_a1474__12a4265u.zip

iPad Air(model A1475)CDMA
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_air_model_a1475__12a4265u.zip

iPad Air(model A1476)GSM
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_air_model_a1476__12a4265u.zip

iPad  mini  Retina

iPad mini Retina(model A1489)WIFI
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_mini_model_a1489__12a4265u.zip

iPad mini Retina(model A1490)CDMA
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_mini_model_a1490__12a4265u.zip

iPad mini Retina(model A1491)GSM
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_mini_model_a1491__12a4265u.zip

iPad 4

iPad 4(generation model A1458)WIFI
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_4th_generation_model_a1458__12a4265u.zip

iPad 4(generation model A1459)GSM
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_4th_generation_model_a1459__12a4265u.zip

iPad 4(generation model A1460)CDMA
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_4th_generation_model_a1460__12a4265u.zip

iPad mini

iPad mini(model A1432)WIFI
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_mini_model_a1432__12a4265u.zip

iPad mini(model A1454)GSM
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_mini_model_a1454__12a4265u.zip

iPad mini(model A1455)CDMA
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_mini_model_a1455__12a4265u.zip

iPad 3

iPad WiFi(3rd generation)WIFI
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_wifi_3rd_generation__12a4265u.zip

iPad  3 WiFi+cellular(model for ATT)GSM
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_wifi__cellular_model_for_att__12a4265u.zip

iPad 3 WiFi+cellular(model for Verizon)CDMA
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_wifi__cellular_model_for_verizon__12a4265u.zip

iPad 2

iPad 2 WiFi(Rev A)
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_2_wifi_rev_a__12a4265u.zip

iPad 2  WiFi
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_2_wifi__12a4265u.zip

iPad 2 WiFi + 3G(GSM)
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_2_wifi__3g_gsm__12a4265u.zip

iPad 2 WiFi + 3G(CDMA)
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipad_2_wifi__3g_cdma__12a4265u.zip

iPhone 5S

iPhone 5S(model A1453,A1533)CDMA
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__iphone_5s_model_a1453_a1533__12a4265u.zip

iPhone 5S(model A1457,A1518,A1526,A1529)GSM
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__iphone_5s_model_a1457_a1518_a1528_a1530__12a4265u.zip

iPhone 5C

iPhone 5C(model A1456,A1532)GSM
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__iphone_5c_model_a1456_a1532__12a4265u.zip

iPhone 5C(model A1507,A1516,A1526,A1529)CDMA
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__iphone_5c_model_a1507_a1516_a1526_a1529__12a4265u.zip

iPhone 5

iPhone 5(model A1428)GSM
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__iphone_5_model_a1428__12a4265u.zip

iPhone 5(model A1429)CDMA
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__iphone_5_model_a1429__12a4265u.zip

iPhone 4S
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__iphone_4s__12a4265u.zip

iPod touch 5
http://adcdownload.apple.com//wwdc_2014/ios_8_beta_wg4j9a/ios_8_beta__ipod_touch_5th_generation__12a4265u.zip

 

Capturing the Desktop Screen with the Mouse Cursor Image

下面的文章介绍了两种截屏方式及如何“截取”光标的方法。

通常情况下是无法截取光标的,因为光标不属于窗口中的windows元素,而是独立与窗口系统的另一层东西,叫Hot Spot。

申明:本文以下内容归原作者所有!

By 27 Jan 2006

DesktopCaptureWithMouse

Introduction

This article shows how you can capture screen images including the mouse cursor.

Background

Screen capturing is a very useful way of resource sharing as used in applications like Remote Desktop, Virtual Network Computing (VNC), where a user can access, view, and interact with a remote desktop as his own desktop. Also, it is used in non ethical applications like hacking applications, where a hacker can hack a computer using some malicious server application, and the server then frequently takes screenshots of the prey machine and sends them to the clients. You will see a lot of source code resources over the internet discussing how to take screenshots of the desktop or an area of the desktop but none of them discuss how to capture the mouse cursor bitmap with the screenshot. Sometimes it becomes necessary to capture the mouse to see the whole activity of the hacked machine. First, we will discuss here what the actual problem is.

Problem

Most of us think that the mouse cursor image is a part of the desktop display but actually it works on an upper layer over the desktop. Windows always tracks mouse with a “Hot Spot”, the actual position seen by the Windows.

Currently, there are two common ways to capture and manipulate the desktop image:

  1. Copy the desktop bitmap data from Video Memory to the System Memory. Do processing and then again blit it back to the Video Memory. This can be easily done using the BitBlt() or the StretchBlt() APIs provided by Win32.
  2. Another way is to directly manipulate the desktop bitmap in the Video Memory if enough memory is available as provided by DirectDraw.

Both of these don’t provide us the facility to capture the mouse cursor image with the desktop image.

Solution

The solution to the problem of capturing the mouse cursor image with the desktop image is quite simple.

  1. First, get the bitmap of the screen using BitBlt(). I have provided a simple function namedCaptureDesktop() in the CaptureScreen.cs file that captures the screen bitmap as almost all the codes available over the internet do.
  2. Then capture the mouse cursor bitmap as:

    Get Cursor Icon:

    First, get the cursor information using the Win32 GetCursorInfo(). The function fills the CURSORINFOstructure provided as a parameter. Don’t forget to initialize the cbSize member of the structure before passing it as an argument. Then we check whether the cursor is visible or not, by checking for the equality of the flagsmember of the filled structure with the CURSOR_SHOWING constant. If they are equal then we get the handle to the cursor icon using the CopyIcon() function that takes the hCursor member of the above filled structure.

    Get Cursor Position:

    Now, we have to get the Icon information so that we can get the hotspot position. This information is easily retrieved using the GetIconInfo() function. Here is the C# implementation of the mouse capturing function:

    static Bitmap CaptureCursor(ref int x, ref int y)
    {
       Bitmap bmp;
       IntPtr hicon;
       Win32Stuff.CURSORINFO ci = new Win32Stuff.CURSORINFO(); 
       Win32Stuff.ICONINFO icInfo;
       ci.cbSize = Marshal.SizeOf(ci);
       if(Win32Stuff.GetCursorInfo(out ci))
       {
           if (ci.flags == Win32Stuff.CURSOR_SHOWING)
           { 
               hicon = Win32Stuff.CopyIcon(ci.hCursor);
               if(Win32Stuff.GetIconInfo(hicon, out icInfo))
               {
                   x = ci.ptScreenPos.x - ((int)icInfo.xHotspot);
                   y = ci.ptScreenPos.y - ((int)icInfo.yHotspot);
                   Icon ic = Icon.FromHandle(hicon);
                   bmp = ic.ToBitmap(); 
    
                   return bmp;
               }
           }
       }
       return null;
    }
  3. We now have both the bitmaps, i.e., the desktop bitmap and the mouse cursor bitmap with its position on the screen. Now, it’s time to place the mouse cursor bitmap on the desktop bitmap. I have provided the following function that places the mouse cursor image over the desktop bitmap at the proper position:
    public static Bitmap CaptureDesktopWithCursor()
    {     
       int cursorX = 0;
       int cursorY = 0;
       Bitmap desktopBMP;
       Bitmap cursorBMP;
       Bitmap finalBMP;
       Graphics g;
       Rectangle r;
       desktopBMP = CaptureDesktop();
       cursorBMP = CaptureCursor(ref cursorX, ref cursorY);
       if(desktopBMP != null)
       {
           if (cursorBMP != null)
           {
               r = new Rectangle(cursorX, cursorY, 
                       cursorBMP.Width, cursorBMP.Height);
               g = Graphics.FromImage(desktopBMP);
               g.DrawImage(cursorBMP, r);
               g.Flush();
               return desktopBMP;
           }
           else
               return desktopBMP;
       }
       return null;
    }
  4. The bitmap with the cursor is now ready to be rendered over a viewer surface (a PictureBox used here). Since the viewer’s visible area is smaller than the desktop bitmap area, scaling has been used in the function that displays the cooked desktop image.
    // ssWithMouseViewer is the PictureBox control
    private void Display(Bitmap desktop)
    {
        Graphics g;
        Rectangle r;
        if(desktop != null)
        {
            r = new Rectangle(0,0,ssWithMouseViewer.Width, 
                                ssWithMouseViewer.Height);
            g = ssWithMouseViewer.CreateGraphics();
            g.DrawImage(desktop,r);
            g.Flush();
        }
    }

Note

The binary image provided is compiled with Visual Studio .NET 2005 so you have to install .NET Framework 2.0.

Known problems

I have tested the application on my machine with no known problems, so I expect the same behavior on your machine.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

本文转自:http://www.codeproject.com/Articles/12850/Capturing-the-Desktop-Screen-with-the-Mouse-Cursor

十大滤波算法程序大全(Arduino精编无错版)

最近用Arduino做电子秤,为了解决数据的跳变研究了不少滤波算法。网上能找到大把的十大滤波算法帖子,每一篇都不太一样,都号称精编啊,除错啊什么的,可是放到板子里却没一个能正常跑起来的。于是决定自己整理一下这些程序,完美移植到Arduino中。

所以大家看到这个帖子的时候,不要怀疑我重复发帖。我的代码都是经过反复试验,复制到Arduino中就能开跑的成品代码,移植到自己的程序中非常方便。而且都仔细研究了各个算法,把错误都修正了的(别的程序连冒泡算法都是溢出的,不信自己找来细看看),所以也算个小原创吧,在别人基础上的原创。

转载请注明出处:极客工坊  http://www.geek-workshop.com/thread-7694-1-1.html

By shenhaiyu 2013-11-01

 

1、限幅滤波法(又称程序判断滤波法)
2、中位值滤波法
3、算术平均滤波法
4、递推平均滤波法(又称滑动平均滤波法)
5、中位值平均滤波法(又称防脉冲干扰平均滤波法)
6、限幅平均滤波法
7、一阶滞后滤波法
8、加权递推平均滤波法
9、消抖滤波法
10、限幅消抖滤波法
11、新增加 卡尔曼滤波(非扩展卡尔曼),感谢zhangzhe0617分享

程序默认对int类型数据进行滤波,如需要对其他类型进行滤波,只需要把程序中所有int替换成long、float或者double即可。

1、限幅滤波法(又称程序判断滤波法)

ARDUINO 代码
/*
A、名称:限幅滤波法(又称程序判断滤波法)
B、方法:
    根据经验判断,确定两次采样允许的最大偏差值(设为A),
    每次检测到新值时判断:
    如果本次值与上次值之差<=A,则本次值有效,
    如果本次值与上次值之差>A,则本次值无效,放弃本次值,用上次值代替本次值。
C、优点:
    能有效克服因偶然因素引起的脉冲干扰。
D、缺点:
    无法抑制那种周期性的干扰。
    平滑度差。
E、整理:shenhaiyu 2013-11-01
*/
 
int Filter_Value;
int Value;
 
void setup() {
  Serial.begin(9600);       // 初始化串口通信
  randomSeed(analogRead(0)); // 产生随机种子
  Value = 300;
}
 
void loop() {
  Filter_Value = Filter();       // 获得滤波器输出值
  Value = Filter_Value;          // 最近一次有效采样的值,该变量为全局变量
  Serial.println(Filter_Value); // 串口输出
  delay(50);
}
 
// 用于随机产生一个300左右的当前值
int Get_AD() {
  return random(295, 305);
}
 
// 限幅滤波法(又称程序判断滤波法)
#define FILTER_A 1
int Filter() {
  int NewValue;
  NewValue = Get_AD();
  if(((NewValue - Value) > FILTER_A) || ((Value - NewValue) > FILTER_A))
    return Value;
  else
    return NewValue;
}

2、中位值滤波法

ARDUINO 代码
/*
A、名称:中位值滤波法
B、方法:
    连续采样N次(N取奇数),把N次采样值按大小排列,
    取中间值为本次有效值。
C、优点:
    能有效克服因偶然因素引起的波动干扰;
    对温度、液位的变化缓慢的被测参数有良好的滤波效果。
D、缺点:
    对流量、速度等快速变化的参数不宜。
E、整理:shenhaiyu 2013-11-01
*/
 
int Filter_Value;
 
void setup() {
  Serial.begin(9600);       // 初始化串口通信
  randomSeed(analogRead(0)); // 产生随机种子
}
 
void loop() {
  Filter_Value = Filter();       // 获得滤波器输出值
  Serial.println(Filter_Value); // 串口输出
  delay(50);
}
 
// 用于随机产生一个300左右的当前值
int Get_AD() {
  return random(295, 305);
}
 
// 中位值滤波法
#define FILTER_N 101
int Filter() {
  int filter_buf[FILTER_N];
  int i, j;
  int filter_temp;
  for(i = 0; i < FILTER_N; i++) {
    filter_buf[i] = Get_AD();
    delay(1);
  }
  // 采样值从小到大排列(冒泡法)
  for(j = 0; j < FILTER_N - 1; j++) {
    for(i = 0; i < FILTER_N - 1 - j; i++) {
      if(filter_buf[i] > filter_buf[i + 1]) {
        filter_temp = filter_buf[i];
        filter_buf[i] = filter_buf[i + 1];
        filter_buf[i + 1] = filter_temp;
      }
    }
  }
  return filter_buf[(FILTER_N - 1) / 2];
}

3、算术平均滤波法

ARDUINO 代码
/*
A、名称:算术平均滤波法
B、方法:
    连续取N个采样值进行算术平均运算:
    N值较大时:信号平滑度较高,但灵敏度较低;
    N值较小时:信号平滑度较低,但灵敏度较高;
    N值的选取:一般流量,N=12;压力:N=4。
C、优点:
    适用于对一般具有随机干扰的信号进行滤波;
    这种信号的特点是有一个平均值,信号在某一数值范围附近上下波动。
D、缺点:
    对于测量速度较慢或要求数据计算速度较快的实时控制不适用;
    比较浪费RAM。
E、整理:shenhaiyu 2013-11-01
*/
 
int Filter_Value;
 
void setup() {
  Serial.begin(9600);       // 初始化串口通信
  randomSeed(analogRead(0)); // 产生随机种子
}
 
void loop() {
  Filter_Value = Filter();       // 获得滤波器输出值
  Serial.println(Filter_Value); // 串口输出
  delay(50);
}
 
// 用于随机产生一个300左右的当前值
int Get_AD() {
  return random(295, 305);
}
 
// 算术平均滤波法
#define FILTER_N 12
int Filter() {
  int i;
  int filter_sum = 0;
  for(i = 0; i < FILTER_N; i++) {
    filter_sum += Get_AD();
    delay(1);
  }
  return (int)(filter_sum / FILTER_N);
}

4、递推平均滤波法(又称滑动平均滤波法)

ARDUINO 代码
/*
A、名称:递推平均滤波法(又称滑动平均滤波法)
B、方法:
    把连续取得的N个采样值看成一个队列,队列的长度固定为N,
    每次采样到一个新数据放入队尾,并扔掉原来队首的一次数据(先进先出原则),
    把队列中的N个数据进行算术平均运算,获得新的滤波结果。
    N值的选取:流量,N=12;压力,N=4;液面,N=4-12;温度,N=1-4。
C、优点:
    对周期性干扰有良好的抑制作用,平滑度高;
    适用于高频振荡的系统。
D、缺点:
    灵敏度低,对偶然出现的脉冲性干扰的抑制作用较差;
    不易消除由于脉冲干扰所引起的采样值偏差;
    不适用于脉冲干扰比较严重的场合;
    比较浪费RAM。
E、整理:shenhaiyu 2013-11-01
*/
 
int Filter_Value;
 
void setup() {
  Serial.begin(9600);       // 初始化串口通信
  randomSeed(analogRead(0)); // 产生随机种子
}
 
void loop() {
  Filter_Value = Filter();       // 获得滤波器输出值
  Serial.println(Filter_Value); // 串口输出
  delay(50);
}
 
// 用于随机产生一个300左右的当前值
int Get_AD() {
  return random(295, 305);
}
 
// 递推平均滤波法(又称滑动平均滤波法)
#define FILTER_N 12
int filter_buf[FILTER_N + 1];
int Filter() {
  int i;
  int filter_sum = 0;
  filter_buf[FILTER_N] = Get_AD();
  for(i = 0; i < FILTER_N; i++) {
    filter_buf[i] = filter_buf[i + 1]; // 所有数据左移,低位仍掉
    filter_sum += filter_buf[i];
  }
  return (int)(filter_sum / FILTER_N);
}

5、中位值平均滤波法(又称防脉冲干扰平均滤波法)

ARDUINO 代码
/*
A、名称:中位值平均滤波法(又称防脉冲干扰平均滤波法)
B、方法:
    采一组队列去掉最大值和最小值后取平均值,
    相当于“中位值滤波法”+“算术平均滤波法”。
    连续采样N个数据,去掉一个最大值和一个最小值,
    然后计算N-2个数据的算术平均值。
    N值的选取:3-14。
C、优点:
    融合了“中位值滤波法”+“算术平均滤波法”两种滤波法的优点。
    对于偶然出现的脉冲性干扰,可消除由其所引起的采样值偏差。
    对周期干扰有良好的抑制作用。
    平滑度高,适于高频振荡的系统。
D、缺点:
    计算速度较慢,和算术平均滤波法一样。
    比较浪费RAM。
E、整理:shenhaiyu 2013-11-01
*/
 
int Filter_Value;
 
void setup() {
  Serial.begin(9600);       // 初始化串口通信
  randomSeed(analogRead(0)); // 产生随机种子
}
 
void loop() {
  Filter_Value = Filter();       // 获得滤波器输出值
  Serial.println(Filter_Value); // 串口输出
  delay(50);
}
 
// 用于随机产生一个300左右的当前值
int Get_AD() {
  return random(295, 305);
}
 
// 中位值平均滤波法(又称防脉冲干扰平均滤波法)(算法1)
#define FILTER_N 100
int Filter() {
  int i, j;
  int filter_temp, filter_sum = 0;
  int filter_buf[FILTER_N];
  for(i = 0; i < FILTER_N; i++) {
    filter_buf[i] = Get_AD();
    delay(1);
  }
  // 采样值从小到大排列(冒泡法)
  for(j = 0; j < FILTER_N - 1; j++) {
    for(i = 0; i < FILTER_N - 1 - j; i++) {
      if(filter_buf[i] > filter_buf[i + 1]) {
        filter_temp = filter_buf[i];
        filter_buf[i] = filter_buf[i + 1];
        filter_buf[i + 1] = filter_temp;
      }
    }
  }
  // 去除最大最小极值后求平均
  for(i = 1; i < FILTER_N - 1; i++) filter_sum += filter_buf[i];
  return filter_sum / (FILTER_N - 2);
}
 
 
//  中位值平均滤波法(又称防脉冲干扰平均滤波法)(算法2)
/*
#define FILTER_N 100
int Filter() {
  int i;
  int filter_sum = 0;
  int filter_max, filter_min;
  int filter_buf[FILTER_N];
  for(i = 0; i < FILTER_N; i++) {
    filter_buf[i] = Get_AD();
    delay(1);
  }
  filter_max = filter_buf[0];
  filter_min = filter_buf[0];
  filter_sum = filter_buf[0];
  for(i = FILTER_N - 1; i > 0; i--) {
    if(filter_buf[i] > filter_max)
      filter_max=filter_buf[i];
    else if(filter_buf[i] < filter_min)
      filter_min=filter_buf[i];
    filter_sum = filter_sum + filter_buf[i];
    filter_buf[i] = filter_buf[i - 1];
  }
  i = FILTER_N - 2;
  filter_sum = filter_sum - filter_max - filter_min + i / 2; // +i/2 的目的是为了四舍五入
  filter_sum = filter_sum / i;
  return filter_sum;
}*/

6、限幅平均滤波法

ARDUINO 代码
/*
A、名称:限幅平均滤波法
B、方法:
    相当于“限幅滤波法”+“递推平均滤波法”;
    每次采样到的新数据先进行限幅处理,
    再送入队列进行递推平均滤波处理。
C、优点:
    融合了两种滤波法的优点;
    对于偶然出现的脉冲性干扰,可消除由于脉冲干扰所引起的采样值偏差。
D、缺点:
    比较浪费RAM。
E、整理:shenhaiyu 2013-11-01
*/
 
#define FILTER_N 12
int Filter_Value;
int filter_buf[FILTER_N];
 
void setup() {
  Serial.begin(9600);       // 初始化串口通信
  randomSeed(analogRead(0)); // 产生随机种子
  filter_buf[FILTER_N - 2] = 300;
}
 
void loop() {
  Filter_Value = Filter();       // 获得滤波器输出值
  Serial.println(Filter_Value); // 串口输出
  delay(50);
}
 
// 用于随机产生一个300左右的当前值
int Get_AD() {
  return random(295, 305);
}
 
// 限幅平均滤波法
#define FILTER_A 1
int Filter() {
  int i;
  int filter_sum = 0;
  filter_buf[FILTER_N - 1] = Get_AD();
  if(((filter_buf[FILTER_N - 1] - filter_buf[FILTER_N - 2]) > FILTER_A) || ((filter_buf[FILTER_N - 2] - filter_buf[FILTER_N - 1]) > FILTER_A))
    filter_buf[FILTER_N - 1] = filter_buf[FILTER_N - 2];
  for(i = 0; i < FILTER_N - 1; i++) {
    filter_buf[i] = filter_buf[i + 1];
    filter_sum += filter_buf[i];
  }
  return (int)filter_sum / (FILTER_N - 1);
}

7、一阶滞后滤波法

ARDUINO 代码
/*
A、名称:一阶滞后滤波法
B、方法:
    取a=0-1,本次滤波结果=(1-a)*本次采样值+a*上次滤波结果。
C、优点:
    对周期性干扰具有良好的抑制作用;
    适用于波动频率较高的场合。
D、缺点:
    相位滞后,灵敏度低;
    滞后程度取决于a值大小;
    不能消除滤波频率高于采样频率1/2的干扰信号。
E、整理:shenhaiyu 2013-11-01
*/
 
int Filter_Value;
int Value;
 
void setup() {
  Serial.begin(9600);       // 初始化串口通信
  randomSeed(analogRead(0)); // 产生随机种子
  Value = 300;
}
 
void loop() {
  Filter_Value = Filter();       // 获得滤波器输出值
  Serial.println(Filter_Value); // 串口输出
  delay(50);
}
 
// 用于随机产生一个300左右的当前值
int Get_AD() {
  return random(295, 305);
}
 
// 一阶滞后滤波法
#define FILTER_A 0.01
int Filter() {
  int NewValue;
  NewValue = Get_AD();
  Value = (int)((float)NewValue * FILTER_A + (1.0 - FILTER_A) * (float)Value);
  return Value;
}

8、加权递推平均滤波法

ARDUINO 代码
/*
A、名称:加权递推平均滤波法
B、方法:
    是对递推平均滤波法的改进,即不同时刻的数据加以不同的权;
    通常是,越接近现时刻的数据,权取得越大。
    给予新采样值的权系数越大,则灵敏度越高,但信号平滑度越低。
C、优点:
    适用于有较大纯滞后时间常数的对象,和采样周期较短的系统。
D、缺点:
    对于纯滞后时间常数较小、采样周期较长、变化缓慢的信号;
    不能迅速反应系统当前所受干扰的严重程度,滤波效果差。
E、整理:shenhaiyu 2013-11-01
*/
 
int Filter_Value;
 
void setup() {
  Serial.begin(9600);       // 初始化串口通信
  randomSeed(analogRead(0)); // 产生随机种子
}
 
void loop() {
  Filter_Value = Filter();       // 获得滤波器输出值
  Serial.println(Filter_Value); // 串口输出
  delay(50);
}
 
// 用于随机产生一个300左右的当前值
int Get_AD() {
  return random(295, 305);
}
 
// 加权递推平均滤波法
#define FILTER_N 12
int coe[FILTER_N] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};    // 加权系数表
int sum_coe = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12; // 加权系数和
int filter_buf[FILTER_N + 1];
int Filter() {
  int i;
  int filter_sum = 0;
  filter_buf[FILTER_N] = Get_AD();
  for(i = 0; i < FILTER_N; i++) {
    filter_buf[i] = filter_buf[i + 1]; // 所有数据左移,低位仍掉
    filter_sum += filter_buf[i] * coe[i];
  }
  filter_sum /= sum_coe;
  return filter_sum;
}

9、消抖滤波法

ARDUINO 代码
/*
A、名称:消抖滤波法
B、方法:
    设置一个滤波计数器,将每次采样值与当前有效值比较:
    如果采样值=当前有效值,则计数器清零;
    如果采样值<>当前有效值,则计数器+1,并判断计数器是否>=上限N(溢出);
    如果计数器溢出,则将本次值替换当前有效值,并清计数器。
C、优点:
    对于变化缓慢的被测参数有较好的滤波效果;
    可避免在临界值附近控制器的反复开/关跳动或显示器上数值抖动。
D、缺点:
    对于快速变化的参数不宜;
    如果在计数器溢出的那一次采样到的值恰好是干扰值,则会将干扰值当作有效值导入系统。
E、整理:shenhaiyu 2013-11-01
*/
 
int Filter_Value;
int Value;
 
void setup() {
  Serial.begin(9600);       // 初始化串口通信
  randomSeed(analogRead(0)); // 产生随机种子
  Value = 300;
}
 
void loop() {
  Filter_Value = Filter();       // 获得滤波器输出值
  Serial.println(Filter_Value); // 串口输出
  delay(50);
}
 
// 用于随机产生一个300左右的当前值
int Get_AD() {
  return random(295, 305);
}
 
// 消抖滤波法
#define FILTER_N 12
int i = 0;
int Filter() {
  int new_value;
  new_value = Get_AD();
  if(Value != new_value) {
    i++;
    if(i > FILTER_N) {
      i = 0;
      Value = new_value;
    }
  }
  else
    i = 0;
  return Value;
}

10、限幅消抖滤波法

ARDUINO 代码
/*
A、名称:限幅消抖滤波法
B、方法:
    相当于“限幅滤波法”+“消抖滤波法”;
    先限幅,后消抖。
C、优点:
    继承了“限幅”和“消抖”的优点;
    改进了“消抖滤波法”中的某些缺陷,避免将干扰值导入系统。
D、缺点:
    对于快速变化的参数不宜。
E、整理:shenhaiyu 2013-11-01
*/
 
int Filter_Value;
int Value;
 
void setup() {
  Serial.begin(9600);       // 初始化串口通信
  randomSeed(analogRead(0)); // 产生随机种子
  Value = 300;
}
 
void loop() {
  Filter_Value = Filter();       // 获得滤波器输出值
  Serial.println(Filter_Value); // 串口输出
  delay(50);
}
 
// 用于随机产生一个300左右的当前值
int Get_AD() {
  return random(295, 305);
}
 
// 限幅消抖滤波法
#define FILTER_A 1
#define FILTER_N 5
int i = 0;
int Filter() {
  int NewValue;
  int new_value;
  NewValue = Get_AD();
  if(((NewValue - Value) > FILTER_A) || ((Value - NewValue) > FILTER_A))
    new_value = Value;
  else
    new_value = NewValue;
  if(Value != new_value) {
    i++;
    if(i > FILTER_N) {
      i = 0;
      Value = new_value;
    }
  }
  else
    i = 0;
  return Value;
}

 

11、卡尔曼滤波(非扩展卡尔曼)

ARDUINO 代码
#include <Wire.h> // I2C library, gyroscope

// Accelerometer ADXL345
#define ACC (0x53)    //ADXL345 ACC address
#define A_TO_READ (6)        //num of bytes we are going to read each time (two bytes for each axis)


// Gyroscope ITG3200 
#define GYRO 0x68 // gyro address, binary = 11101000 when AD0 is connected to Vcc (see schematics of your breakout board)
#define G_SMPLRT_DIV 0x15   
#define G_DLPF_FS 0x16   
#define G_INT_CFG 0x17
#define G_PWR_MGM 0x3E

#define G_TO_READ 8 // 2 bytes for each axis x, y, z


// offsets are chip specific. 
int a_offx = 0;
int a_offy = 0;
int a_offz = 0;

int g_offx = 0;
int g_offy = 0;
int g_offz = 0;
////////////////////////

////////////////////////
char str[512]; 

void initAcc() {
  //Turning on the ADXL345
  writeTo(ACC, 0x2D, 0);      
  writeTo(ACC, 0x2D, 16);
  writeTo(ACC, 0x2D, 8);
  //by default the device is in +-2g range reading
}

void getAccelerometerData(int* result) {
  int regAddress = 0x32;    //first axis-acceleration-data register on the ADXL345
  byte buff[A_TO_READ];
  
  readFrom(ACC, regAddress, A_TO_READ, buff); //read the acceleration data from the ADXL345
  
  //each axis reading comes in 10 bit resolution, ie 2 bytes.  Least Significat Byte first!!
  //thus we are converting both bytes in to one int
  result[0] = (((int)buff[1]) << 8) | buff[0] + a_offx;   
  result[1] = (((int)buff[3]) << 8) | buff[2] + a_offy;
  result[2] = (((int)buff[5]) << 8) | buff[4] + a_offz;
}

//initializes the gyroscope
void initGyro()
{
  /*****************************************
  * ITG 3200
  * power management set to:
  * clock select = internal oscillator
  *     no reset, no sleep mode
  *   no standby mode
  * sample rate to = 125Hz
  * parameter to +/- 2000 degrees/sec
  * low pass filter = 5Hz
  * no interrupt
  ******************************************/
  writeTo(GYRO, G_PWR_MGM, 0x00);
  writeTo(GYRO, G_SMPLRT_DIV, 0x07); // EB, 50, 80, 7F, DE, 23, 20, FF
  writeTo(GYRO, G_DLPF_FS, 0x1E); // +/- 2000 dgrs/sec, 1KHz, 1E, 19
  writeTo(GYRO, G_INT_CFG, 0x00);
}


void getGyroscopeData(int * result)
{
  /**************************************
  Gyro ITG-3200 I2C
  registers:
  temp MSB = 1B, temp LSB = 1C
  x axis MSB = 1D, x axis LSB = 1E
  y axis MSB = 1F, y axis LSB = 20
  z axis MSB = 21, z axis LSB = 22
  *************************************/

  int regAddress = 0x1B;
  int temp, x, y, z;
  byte buff[G_TO_READ];
  
  readFrom(GYRO, regAddress, G_TO_READ, buff); //read the gyro data from the ITG3200
  
  result[0] = ((buff[2] << 8) | buff[3]) + g_offx;
  result[1] = ((buff[4] << 8) | buff[5]) + g_offy;
  result[2] = ((buff[6] << 8) | buff[7]) + g_offz;
  result[3] = (buff[0] << 8) | buff[1]; // temperature
  
}


float xz=0,yx=0,yz=0;
float p_xz=1,p_yx=1,p_yz=1;
float q_xz=0.0025,q_yx=0.0025,q_yz=0.0025;
float k_xz=0,k_yx=0,k_yz=0;
float r_xz=0.25,r_yx=0.25,r_yz=0.25;
  //int acc_temp[3];
  //float acc[3];
  int acc[3];
  int gyro[4];
  float Axz;
  float Ayx;
  float Ayz;
  float t=0.025;
void setup()
{
  Serial.begin(9600);
  Wire.begin();
  initAcc();
  initGyro();
  
}

//unsigned long timer = 0;
//float o;
void loop()
{
  
  getAccelerometerData(acc);
  getGyroscopeData(gyro);
  //timer = millis();
  sprintf(str, "%d,%d,%d,%d,%d,%d", acc[0],acc[1],acc[2],gyro[0],gyro[1],gyro[2]);
  
  //acc[0]=acc[0];
  //acc[2]=acc[2];
  //acc[1]=acc[1];
  //r=sqrt(acc[0]*acc[0]+acc[1]*acc[1]+acc[2]*acc[2]);
  gyro[0]=gyro[0]/ 14.375;
  gyro[1]=gyro[1]/ (-14.375);
  gyro[2]=gyro[2]/ 14.375;
  
   
  Axz=(atan2(acc[0],acc[2]))*180/PI;
  Ayx=(atan2(acc[0],acc[1]))*180/PI;
  /*if((acc[0]!=0)&&(acc[1]!=0))
    {
      Ayx=(atan2(acc[0],acc[1]))*180/PI;
    }
    else
    {
      Ayx=t*gyro[2];
    }*/
  Ayz=(atan2(acc[1],acc[2]))*180/PI;
  
  
//kalman filter
  calculate_xz();
  calculate_yx();
  calculate_yz();
  
  //sprintf(str, "%d,%d,%d", xz_1, xy_1, x_1);
  //Serial.print(xz);Serial.print(",");
  //Serial.print(yx);Serial.print(",");
  //Serial.print(yz);Serial.print(",");
  //sprintf(str, "%d,%d,%d,%d,%d,%d", acc[0],acc[1],acc[2],gyro[0],gyro[1],gyro[2]);
  //sprintf(str, "%d,%d,%d",gyro[0],gyro[1],gyro[2]);
    Serial.print(Axz);Serial.print(",");
    //Serial.print(Ayx);Serial.print(",");
    //Serial.print(Ayz);Serial.print(",");
  //Serial.print(str);
  //o=gyro[2];//w=acc[2];
  //Serial.print(o);Serial.print(",");
  //Serial.print(w);Serial.print(",");
  Serial.print("\n");

  
  //delay(50);
}
void calculate_xz()
{

xz=xz+t*gyro[1];
p_xz=p_xz+q_xz;
k_xz=p_xz/(p_xz+r_xz);
xz=xz+k_xz*(Axz-xz);
p_xz=(1-k_xz)*p_xz;
}
void calculate_yx()
{
  
  yx=yx+t*gyro[2];
  p_yx=p_yx+q_yx;
  k_yx=p_yx/(p_yx+r_yx);
  yx=yx+k_yx*(Ayx-yx);
  p_yx=(1-k_yx)*p_yx;

}
void calculate_yz()
{
  yz=yz+t*gyro[0];
  p_yz=p_yz+q_yz;
  k_yz=p_yz/(p_yz+r_yz);
  yz=yz+k_yz*(Ayz-yz);
  p_yz=(1-k_yz)*p_yz;

}


//---------------- Functions
//Writes val to address register on ACC
void writeTo(int DEVICE, byte address, byte val) {
   Wire.beginTransmission(DEVICE); //start transmission to ACC 
   Wire.write(address);        // send register address
   Wire.write(val);        // send value to write
   Wire.endTransmission(); //end transmission
}


//reads num bytes starting from address register on ACC in to buff array
void readFrom(int DEVICE, byte address, int num, byte buff[]) {
  Wire.beginTransmission(DEVICE); //start transmission to ACC 
  Wire.write(address);        //sends address to read from
  Wire.endTransmission(); //end transmission
  
  Wire.beginTransmission(DEVICE); //start transmission to ACC
  Wire.requestFrom(DEVICE, num);    // request 6 bytes from ACC
  
  int i = 0;
  while(Wire.available())    //ACC may send less than requested (abnormal)
  { 
    buff[i] = Wire.read(); // receive a byte
    i++;
  }
  Wire.endTransmission(); //end transmission
}

 

本文转自:http://www.geek-workshop.com/thread-7694-1-1.html

 

三种截取屏幕的方式

Various methods for capturing the screen

By 19 Sep 2006

Contents

Introduction

Some times, we want to capture the contents of the entire screen programmatically. The following explains how it can be done. Typically, the immediate options we have, among others, are using GDI and/or DirectX. Another option that is worth considering is Windows Media API. Here, we would consider each of them and see how they can be used for our purpose. In each of these approaches, once we get the screenshot into our application defined memory or bitmap, we can use it in generating a movie. Refer to the article Create Movie From HBitmap for more details about creating movies from bitmap sequences programmatically.

Capture it the GDI way

When performance is not an issue and when all that we want is just a snapshot of the desktop, we can consider the GDI option. This mechanism is based on the simple principle that the desktop is also a window – that is it has a window Handle (HWND) and a device context (DC). If we can get the device context of the desktop to be captured, we can just blit those contents to our application defined device context in the normal way. And getting the device context of the desktop is pretty straightforward if we know its window handle – which can be achieved through the function GetDesktopWindow(). Thus, the steps involved are:

  1. Acquire the Desktop window handle using the function GetDesktopWindow();
  2. Get the DC of the desktop window using the function GetDC();
  3. Create a compatible DC for the Desktop DC and a compatible bitmap to select into that compatible DC. These can be done using CreateCompatibleDC() and CreateCompatibleBitmap(); selecting the bitmap into our DC can be done with SelectObject();
  4. Whenever you are ready to capture the screen, just blit the contents of the Desktop DC into the created compatible DC – that’s all – you are done. The compatible bitmap we created now contains the contents of the screen at the moment of the capture.
  5. Do not forget to release the objects when you are done. Memory is precious (for the other applications).

Example

Void CaptureScreen()
{
    int nScreenWidth = GetSystemMetrics(SM_CXSCREEN);
    int nScreenHeight = GetSystemMetrics(SM_CYSCREEN);
    HWND hDesktopWnd = GetDesktopWindow();
    HDC hDesktopDC = GetDC(hDesktopWnd);
    HDC hCaptureDC = CreateCompatibleDC(hDesktopDC);
    HBITMAP hCaptureBitmap =CreateCompatibleBitmap(hDesktopDC, 
                            nScreenWidth, nScreenHeight);
    SelectObject(hCaptureDC,hCaptureBitmap); 
    BitBlt(hCaptureDC,0,0,nScreenWidth,nScreenHeight,
           hDesktopDC,0,0,SRCCOPY|CAPTUREBLT); 
    SaveCapturedBitmap(hCaptureBitmap); //Place holder - Put your code
                                //here to save the captured image to disk
    ReleaseDC(hDesktopWnd,hDesktopDC);
    DeleteDC(hCaptureDC);
    DeleteObject(hCaptureBitmap);
}

In the above code snippet, the function GetSystemMetrics() returns the screen width when used withSM_CXSCREEN, and returns the screen height when called with SM_CYSCREEN. Refer to the accompanying source code for details of how to save the captured bitmap to the disk and how to send it to the clipboard. Its pretty straightforward. The source code implements the above technique for capturing the screen contents at regular intervals, and creates a movie out of the captured image sequences.

And the DirectX way of doing it

Capturing the screenshot with DirectX is a pretty easy task. DirectX offers a neat way of doing this.

Every DirectX application contains what we call a buffer, or a surface to hold the contents of the video memory related to that application. This is called the back buffer of the application. Some applications might have more than one back buffer. And there is another buffer that every application can access by default – the front buffer. This one, the front buffer, holds the video memory related to the desktop contents, and so essentially is the screen image.

By accessing the front buffer from our DirectX application, we can capture the contents of the screen at that moment.

Accessing the front buffer from the DirectX application is pretty easy and straightforward. The interfaceIDirect3DDevice9 provides the GetFrontBufferData() method that takes a IDirect3DSurface9 object pointer and copies the contents of the front buffer onto that surface. The IDirect3DSurfce9 object can be generated by using the method IDirect3DDevice8::CreateOffscreenPlainSurface(). Once the screen is captured onto the surface, we can use the function D3DXSaveSurfaceToFile() to save the surface directly to the disk in bitmap format. Thus, the code to capture the screen looks as follows:

extern IDirect3DDevice9* g_pd3dDevice;
Void CaptureScreen()
{
    IDirect3DSurface9* pSurface;
    g_pd3dDevice->CreateOffscreenPlainSurface(ScreenWidth, ScreenHeight,
        D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &pSurface, NULL);
    g_pd3dDevice->GetFrontBufferData(0, pSurface);
    D3DXSaveSurfaceToFile("Desktop.bmp",D3DXIFF_BMP,pSurface,NULL,NULL);
    pSurface->Release(); 
}

In the above, g_pd3dDevice is an IDirect3DDevice9 object, and has been assumed to be properly initialized. This code snippet saves the captured image onto the disk directly. However, instead of saving to disk, if we just want to operate on the image bits directly – we can do so by using the method IDirect3DSurface9::LockRect(). This gives a pointer to the surface memory – which is essentially a pointer to the bits of the captured image. We can copy the bits to our application defined memory and can operate on them. The following code snippet presents how the surface contents can be copied into our application defined memory:

extern void* pBits;
extern IDirect3DDevice9* g_pd3dDevice;
IDirect3DSurface9* pSurface;
g_pd3dDevice->CreateOffscreenPlainSurface(ScreenWidth, ScreenHeight,
                                          D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, 
                                          &pSurface, NULL);
g_pd3dDevice->GetFrontBufferData(0, pSurface);
D3DLOCKED_RECT lockedRect;
pSurface->LockRect(&lockedRect,NULL,
                   D3DLOCK_NO_DIRTY_UPDATE|
                   D3DLOCK_NOSYSLOCK|D3DLOCK_READONLY)));
for( int i=0 ; i < ScreenHeight ; i++)
{
    memcpy( (BYTE*) pBits + i * ScreenWidth * BITSPERPIXEL / 8 , 
        (BYTE*) lockedRect.pBits + i* lockedRect.Pitch , 
        ScreenWidth * BITSPERPIXEL / 8);
}
g_pSurface->UnlockRect();
pSurface->Release();

In the above, pBits is a void*. Make sure that we have allocated enough memory before copying into pBits. A typical value for BITSPERPIXEL is 32 bits per pixel. However, it may vary depending on your current monitor settings. The important point to note here is that the width of the surface is not same as the captured screen image width. Because of the issues involved in the memory alignment (memory aligned to word boundaries are assumed to be accessed faster compared to non aligned memory), the surface might have added additional stuff at the end of each row to make them perfectly aligned to the word boundaries. The lockedRect.Pitch gives us the number of bytes between the starting points of two successive rows. That is, to advance to the correct point on the next row, we should advance by Pitch, not by Width. You can copy the surface bits in reverse, using the following:

for( int i=0 ; i < ScreenHeight ; i++)
{
    memcpy((BYTE*) pBits +( ScreenHeight - i - 1) * 
        ScreenWidth * BITSPERPIXEL/8 , 
        (BYTE*) lockedRect.pBits + i* lockedRect.Pitch , 
        ScreenWidth* BITSPERPIXEL/8);
}

This may come handy when you are converting between top-down and bottom-up bitmaps.

While the above technique of LockRect() is one way of accessing the captured image content onIDirect3DSurface9, we have another more sophisticated method defined for IDirect3DSurface9, the GetDC()method. We can use the IDirect3DSurface9::GetDC() method to get a GDI compatible device context for the DirectX image surface, which makes it possible to directly blit the surface contents to our application defined DC. Interested readers can explore this alternative.

The sample source code provided with this article implements the technique of copying the contents of an off-screen plain surface onto a user created bitmap for capturing the screen contents at regular intervals, and creates a movie out of the captured image sequences.

However, a point worth noting when using this technique for screen capture is the caution mentioned in the documentation: The GetFrontBufferData() is a slow operation by design, and should not be considered for use in performance-critical applications. Thus, the GDI approach is preferable over the DirectX approach in such cases.

Windows Media API for capturing the screen

Windows Media 9.0 supports screen captures using the Windows Media Encoder 9 API. It includes a codec namedWindows Media Video 9 Screen codec that has been specially optimized to operate on the content produced through screen captures. The Windows Media Encoder API provides the interface IWMEncoder2 which can be used to capture the screen content efficiently.

Working with the Windows Media Encoder API for screen captures is pretty straightforward. First, we need to start with the creation of an IWMEncoder2 object by using the CoCreateInstance() function. This can be done as:

IWMEncoder2* g_pEncoder=NULL; 
CoCreateInstance(CLSID_WMEncoder,NULL,CLSCTX_INPROC_SERVER,
        IID_IWMEncoder2,(void**)&g_pEncoder);

The Encoder object thus created contains all the operations for working with the captured screen data. However, in order to perform its operations properly, the encoder object depends on the settings defined in what is called a profile. A profile is nothing but a file containing all the settings that control the encoding operations. We can also create custom profiles at runtime with various customized options, such as codec options etc., depending on the nature of the captured data. To use a profile with our screen capture application, we create a custom profile based on the Windows Media Video 9 Screen codec. Custom profile objects have been supported with the interfaceIWMEncProfile2. We can create a custom profile object by using the CoCreateInstance() function as:

IWMEncProfile2* g_pProfile=NULL;
CoCreateInstance(CLSID_WMEncProfile2,NULL,CLSCTX_INPROC_SERVER,
        IID_IWMEncProfile2,(void**)&g_pProfile);

We need to specify the target audience for the encoder in the profile. Each profile can hold multiple number of audience configurations, which are objects of the interface IWMEncAudienceObj. Here, we use one audience object for our profile. We create the audience object for our profile by using the methodIWMEncProfile::AddAudience(), which would return a pointer to IWMEncAudienceObj which can then be used for configurations such as video codec settings (IWMEncAudienceObj::put_VideoCodec()), video frame size settings (IWMEncAudienceObj::put_VideoHeight() and IWMEncAudienceObj::put_VideoWidth()) etc. For example, we set the video codec to be Windows Media Video 9 Screen codec as:

extern IWMEncAudienceObj* pAudience;
#define VIDEOCODEC MAKEFOURCC('M','S','S','2') 
    //MSS2 is the fourcc for the screen codec

long lCodecIndex=-1;
g_pProfile->GetCodecIndexFromFourCC(WMENC_VIDEO,VIDEOCODEC,
    &lCodecIndex); //Get the Index of the Codec
pAudience->put_VideoCodec(0,lCodecIndex);

The fourcc is a kind of unique identifier for each codec in the world. The fourcc for the Windows Media Video 9 Screen codec is MSS2. The IWMEncAudienceObj::put_VideoCodec() accepts the profile index as the input to recognize a particular profile – which can be obtained by using the method IWMEncProfile::GetCodecIndexFromFourCC().

Once we have completed configuring the profile object, we can choose that profile into our encoder by using the method IWMEncSourceGroup :: put_Profile() which is defined on the source group objects of the encoder. A source group is a collection of sources where each source might be a video stream or audio stream or HTML stream etc. Each encoder object can work with many source groups from which it get the input data. Since our screen capture application uses only a video stream, our encoder object need to have one source group with a single source, the video source, in it. This single video source needs to configured to use the Screen Device as the input source, which can be done by using the method IWMEncVideoSource2::SetInput(BSTR) as:

extern IWMEncVideoSource2* pSrcVid;
pSrcVid->SetInput(CComBSTR("ScreenCap://ScreenCapture1");

The destination output can be configured to save into a video file (wmv movie) by using the methodIWMEncFile::put_LocalFileName() which requires an IWMEncFile object. This IWMEncFile object can be obtained by using the method IWMEncoder::get_File() as:

IWMEncFile* pOutFile=NULL;
g_pEncoder->get_File(&pOutFile);
pOutFile->put_LocalFileName(CComBSTR(szOutputFileName);

Now, once all the necessary configurations have been done on the encoder object, we can use the methodIWMEncoder::Start() to start capturing the screen. The methods IWMEncoder::Stop() andIWMEncoder::Pause might be used for stopping and pausing the capture.

While this deals with full screen capture, we can alternately select the regions of capture by adjusting the properties of input video source stream. For this, we need to use the IPropertyBag interface of the IWmEnVideoSource2 object as:

#define WMSCRNCAP_WINDOWLEFT CComBSTR("Left")
#define WMSCRNCAP_WINDOWTOP CComBSTR("Top")
#define WMSCRNCAP_WINDOWRIGHT CComBSTR("Right")
#define WMSCRNCAP_WINDOWBOTTOM CComBSTR("Bottom")
#define WMSCRNCAP_FLASHRECT CComBSTR("FlashRect")
#define WMSCRNCAP_ENTIRESCREEN CComBSTR("Screen")
#define WMSCRNCAP_WINDOWTITLE CComBSTR("WindowTitle")
extern IWMEncVideoSource2* pSrcVid;
int nLeft, nRight, nTop, nBottom;
pSrcVid->QueryInterface(IID_IPropertyBag,(void**)&pPropertyBag);
CComVariant varValue = false;
pPropertyBag->Write(WMSCRNCAP_ENTIRESCREEN,&varValue);
varValue = nLeft;
pPropertyBag->Write( WMSCRNCAP_WINDOWLEFT, &varValue );
varValue = nRight;
pPropertyBag->Write( WMSCRNCAP_WINDOWRIGHT, &varValue );
varValue = nTop;
pPropertyBag->Write( WMSCRNCAP_WINDOWTOP, &varValue );
varValue = nBottom;
pPropertyBag->Write( WMSCRNCAP_WINDOWBOTTOM, &varValue );

The accompanied source code implements this technique for capturing the screen. One point that might be interesting, apart from the nice quality of the produced output movie, is that in this, the mouse cursor is also captured. (By default, GDI and DirectX are unlikely to capture the mouse cursor).

Note that your system needs to be installed with Windows Media 9.0 SDK components to create applications using the Window Media 9.0 API.

To run your applications, end users must install the Windows Media Encoder 9 Series. When you distribute applications based on the Windows Media Encoder SDK, you must also include the Windows Media Encoder software, either by redistributing Windows Media Encoder in your setup, or by requiring your users to install Windows Media Encoder themselves.

The Windows Media Encoder 9.0 can be downloaded from:

Conclusion

All the variety of techniques discussed above are aimed at a single goal – capturing the contents of the screen. However, as can be guessed easily, the results vary depending upon the particular technique that is being employed in the program. If all that we want is just a random snapshot occasionally, the GDI approach is a good choice, given its simplicity. However, using Windows Media would be a better option if we want more professional results. One point worth noting is, the quality of the content captured through these mechanisms might depend on the settings of the system. For example, disabling hardware acceleration (Desktop properties | Settings | Advanced | Troubleshoot) might drastically improve the overall quality and performance of the capture application.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)

How can I return a string from a C dll to C#?

// c code:
#include <ole2.h> /* needed for CoTaskMemAlloc */
EXPORT char* getUrlFromBaseURLInList(char *ListFilePath, char *SrchBaseURL, char* strBuffer)
{
    static const char[] sTest =  "Test My String";
    /* you must use CoTaskMemAlloc to allocate the memory, not malloc, new, or anything else */
    char* returnedString = CoTaskMemAlloc(sizeof(sTest));
    strcpy(returnedString, sTest);
    return returnedString;
}

// c# code:
[DllImport("SLSSeoUrlFunc.dll", CharSet=CharSet.Ansi, CallingConvention=CallingConvention.Cdecl)]
public static extern string getUrlFromBaseURLInList(byte[] ListFilePath, byte[] sSrchUrl);

string myTestString = SLSSeoUrlFuncDllWrap.getUrlFromBaseURLInList(null, null);
Console.WriteLine("Eureka - {0}", myTestString);

svn、bugzilla等穿过防火墙访问:如何在 Windows 防火墙中打开端口

如果 Windows 防火墙阻止某一程序,而您希望允许该程序通过防火墙进行通信,通常可以通过在 Windows 防火墙允许的程序列表(也称为“例外列表”)中选中该程序来实现。若要了解如何进行此操作,请参阅允许程序通过 Windows 防火墙进行通信

但是,如果没有列出该程序,则可能需要打开一个端口。例如,当您与朋友联机进行多人游戏时,可能需要为该游戏打开一个端口,这样防火墙才能允许游戏信息到达您的计算机。端口始终保持打开状态,因此请确保关闭不需要打开的端口。

  1. 通过单击「开始」按钮 「开始」按钮的图片,然后单击“控制面板”,打开“Windows 防火墙”。 在搜索框中,键入防火墙,然后单击“Windows 防火墙”

  2. 在左窗格中,单击“高级设置” 需要管理员权限 如果系统提示您输入管理员密码或进行确认,请键入该密码或提供确认。

  3. “高级安全 Windows 防火墙”对话框的左窗格中,单击“入站规则”,然后在右窗格中,单击“新建规则”

  4. 按照新建入站规则向导中的说明进行操作。

如果您无法通过 Windows 防火墙让其他计算机与您的计算机通信,则可以尝试使用“传入连接”疑难解答自动查找并修复一些常见问题。

通过单击“开始”按钮 「开始」按钮的图片,然后单击“控制面板”,打开“传入的连接”疑难解答。在搜索框中,键入疑难解答,然后单击“疑难解答”。单击“查看全部”,然后单击“传入的连接”

解决 error C1083: 无法打开预编译头文件xxx.pch: No such file or directory

问题产生:

通常情况下不会出现这种错误,我出现这样的错误提示是因为,我要发布的是一个静态库,于是把要发布的头文件放到了include目录下,把其他文件(包括stdAfx.h及stdAfx.cpp)放到了src目录下,于是编译时提示:

错误 1 error C1083: 无法打开预编译头文件:“Debug\PlotData.pch”: No such file or directory e:\mcp prj\tmdataplotting\plotdata\src\stdafx.cpp 5 1 PlotData

 

解决办法:

在解决方案资源管理器中:

1、在stdafx.cpp上右键–>属性

2、弹出的stdafx.cpp属性框中,选择配置属性–>C/C++–>预编译头

3、在预编译头中选择“创建”,而不是原来默认的“使用”

4、其他cpp文件默认“使用”就OK了

 

 PS:

预编译头.pch文件是咋回事?

编译器一般都是以文件为单位进行编译,如果修改了工程中的一个文件,那么将导致所有文件都要从新编译,这样的编译将耗费很长时间。
为了提高编译速度,将那些不常被修改,比较稳定,文件单独包含到一个指定的头文件中, 然后生成一个预编译头文件 *.pch 。

VC中默认的头文件为 stdAfx.h, 但光有头文件无法编译,所以还要用到 StdAfx.cpp里只包含一句有效代码,这样编译时,通过编译 stdAfx.cpp就把大部分系统头文件编译进来, Debug目录下便会产生一个 存储了预编译信息的 *.pch 文件。
如果 *.pch文件损坏或者不存在,就会出现,题目所示的无法打开预编译头文件的错。

 

可见,你也可以在项目属性中选择“不使用预编译头”,但是编译大项目时可能会很慢

 

 

【屌丝们,让老夫来拯救你们吧!】MAC OSX 10.9 下通过U盘安装Win8/Win8.1

这顿折腾就不说了,折磨啊,苦逼的想谋害心爱的MBP了,重点是,最后参考了官网的说明,终于定位到了问题所在:10.9后BootCamp更新到了5.1了,这个版本只支持部分操作系统的安装,不是所有的Win7、Win8版本都可以安装!或者说,Win8的有些版本对硬件(尤其是硬盘)的要求,决定了BootCamp只能支持部分系统的安装,而不是所有!!!

 

apple官网Frequently asked questions提到:

Which versions of Windows work with Boot Camp 5.1?
You can use:

  • Microsoft Windows 8.0 and 8.1 (64-bit)
  • Microsoft Windows 8.0 and 8.1 Pro (64-bit)
  • Microsoft Windows 7 Home Premium (64-bit)
  • Microsoft Windows 7 Professional (64-bit)
  • Microsoft Windows 7 Ultimate (64-bit)

Windows XP, Windows Vista, and Enterprise versions of Windows 7 and Windows 8 are not supported.

通常情况下,大部分人按照网上满天飞的MBP安装Win8的“教程”还是都顺利装上了,但是一部分人就没这么幸运了,没被折磨死也差不多半死了,最后还是无功而返,废话就不说了。

MacBookPro上通过BootCamp安装Win8通常要解决两个问题:

1、制作U盘Win8安装盘

通常情况下,带有光驱的MBP是“无法”通过BootCamp用U盘来制作Win8启动盘的,这也许是因为Apple的人认为有了光驱,干嘛还要用U盘安装呢?实际上,各种需求各种可能都有——光驱被替换成固态硬盘?光驱坏了?俺就没有光盘安装盘?U盘满天飞,就想用U盘装?我是技术控,就想玩玩U盘安装的心跳???

这部分人就必须破解BootCamp了,方法如下所术,精彩的是第四步,因为Mac OSX的10.9不像10.8了):

So basically, I have trying to install windows on my mbp using a usb drive. However bootcamp wont allow me to do so since I have a optical drive on the laptop. I have been searching for a long time and eventually came across this solution and I would like to share this so u guys dont have to google all over the place again .

The solutions given before by changing info.plist is correct except that now Bootcamp crashes everytime you change it in OSX 10.9.

 

Full solution:

 

1. Add your Boot Rom Version(from system info) under DARequiredROMVersions.

2. Add Model Identifier(from system info) under PreUSBBootSupportedModels

3. Delete “Pre” from “PreUSBBootSupportedModels”, so you have “USBBootSupportedModels”

 

The first 3 steps are same as before and if its not clear you can easily google solutions with screenshots.

The next step is only for OSX 10.9, as it employs some kind of code signature to prevent you from changing info.plist and cause bootcamp to crash.

 

4. Open your terminal, use the following command

sudo codesign -fs – /Applications/Utilities/Boot\ Camp\ Assistant.app

 

Sudo means using administrator privilege and u need to enter your mac password. And the command resigns the bootcamp application so that it runs with the new info.plist file and not crash.

 

5. Continue on with your installation….

 

Cheers.

 

2、安装

启动盘成功制作完毕(或者已经有了U盘启动盘的同学),接下来就可以进入安装步骤了,悲剧的就在这一步:一开始就用错Win8版本,那么安装到选择安装盘时,总是提示:无法在带有MBR的硬盘上安装系统,要求必须装载PGT硬盘上!!!

这就是为什么,同样的教程,一拨人乐呵呵的装完了,一拨人却倒下了,无论如何也装不上,各种猜测各种尝试均失败,苦逼啊。

10.9后BootCamp更新到了5.1了,这个版本不是所有的Win7、Win8版本都可以安装:

apple官网Frequently asked questions提到:

Which versions of Windows work with Boot Camp 5.1?
You can use:

  • Microsoft Windows 8.0 and 8.1 (64-bit)
  • Microsoft Windows 8.0 and 8.1 Pro (64-bit)
  • Microsoft Windows 7 Home Premium (64-bit)
  • Microsoft Windows 7 Professional (64-bit)
  • Microsoft Windows 7 Ultimate (64-bit)

Windows XP, Windows Vista, and Enterprise versions of Windows 7 and Windows 8 are not supported.

看到了吧????要多人走狗屎运,选对了iso版本,所以过了,儿有些人踩到了狗屎,选什么企业版,悲催的命运从一开始就注定了,而整个过程怀疑各种可能,就没怀疑到这里!