豌豆

没有荚

在使用 Android NDK 进行 native 代码调试时,尽管 Android.mk/Application.mk 与Gradle 中都指定了Debug编译,但 C 代码的断点一直无法命中。 排查后,使用 greadelf(Mac)/readelf(Unix) –header x.so 查看编译出来的 so ,发现其中没有debug信息,那么一定是 ndk-build 的过程中出现了问题。无意中发现ndk-build 编译完成以后,又调用了 strip ,这一步将 so 中的一些无用的符号移除了,当然也包括debug信息。

Android.mk 中将 strip 这一步做个处理,使其在debug时不执行:

1
2
3
ifeq ($(APP_OPTIM),debug)
cmd-strip :=
endif

cairo 是一个免费的矢量绘图软件库,它可以绘制多种输出格式。cairo 支持许多平台,如 Linux、BSD、OSX等,当然,还有 Windows。
cairo 官网提供了在 windows 下的编译方法,详情可戳这里。尽管它给出了看起来很详细的步骤,但在实际编译中,你有可能会遇到各种各样的问题。

我们先从官网的步骤开始。

  1. 下载并编译 zlib .

  2. 下载并编译 libpng .

  3. 安装 MozillaBuild编译环境。( 注意安装路径下不要有空格) .

  4. 找到 MozillaBuild 的安装路径,找到 VC++ 对应版本的启动脚本,如 start-shell-msvc2015.bat,按官网给出的方式进行修改,定义出 zlib \ libpng 等 lib、include 等的路径 .

  5. 运行修改后的 bat,到 pixman 所在路径下,运行 make 命令进行编译 .

1
make -f Makefile.win32 CFG=release

进行到这一步的时候,基本就无法进行下去了。因为在这一步开始就会出现各种各样的错误
下面我们将从这里开始,一步一步完成cairo的编译。

1. 修改MozillaBuild脚本:

  • 打开 VS 版本对应的 bat 文件,注意 MOZ_MSVCBITS 的值 :这里将值改为 32。我们将编译 32 位版本。

  • 打开 start-shell.bat 。在 “REM Reset some env vars and set some others.” 内容下方,按照官网上给出的方式,指定 zlib, libpng 的 lib 和 include 所在路径。

  • 如果你的PC是 64 位,那么 MozillaBuild 会默认使用 64 的 VS 进行编译,可能会引发平台不匹配的问题。此时需要将 “REM Prepend MSVC paths.” 下面的

1
SET TOOLCHAIN=64-bit cross-compile

改为

1
SET TOOLCHAIN=32-bit cross-compile

2. 修改 pixman-version.h.in

进入 pixman/pixman 文件夹下,修改 @PIXMAN_VERSION_MAJOR@ , @PIXMAN_VERSION_MINOR@ , @PIXMAN_VERSION_MICRO@ 的值为对应的版本号。

这里有一份修改好的文件,版本号为 0.35.1 ,可直接取用

1
git clone https://git.oschina.net/wandoer/pixmanversionheader.git

3. 编译pixman

运行 start-shell-msvc2015.bat ,转到 pixman 路径下,运行

1
make -f Makefile.win32 CFG=release

如果成功,则 pixman/pixman 路径下有 release 文件夹,内有 pixman-1.lib

4. 修改Makefile.win32.common

在 cairo/build/ 路径下找到 Makefile.win32.common 文件,指定 zlib 和 libpng 所在的路径( ZLIB_PATH 和 LIBPNG_PATH)。

修改 zdll.lib 为 zlib.lib。

5. 编译 cairo

转到 cairo/ 路径下运行命令:

1
make -f Makefile.win32 CFG=release

如不出意外,此步应编译成功

6. 提取 include 头文件

在 cairo/ 路径下,添加以下脚本并执行:

1
2
3
4
5
6
7
8
9
10
mkdir -vp include
cp ./src/cairo-features.h include/
cp ./src/cairo.h include/
cp ./src/cairo-deprecated.h include/
cp ./src/cairo-win32.h include/
cp ./src/cairo-script.h include/
cp ./src/cairo-ps.h include/
cp ./src/cairo-pdf.h include/
cp ./src/cairo-svg.h include/
cp ./cairo-version.h include/

至此,cairo 的编译已经完成。

目录

1 简介

1.1 概览

Apache Thrift官网
Thrift是一个跨语言的服务开发部署 框架,其最初由Facebook于2007年开发,于2008年加入Apache开源项目。
Thrift 通过编译thrift文件(由Thrift Types 组织的中间语言,用于定义RPC数据结构),生成不同语言的类。这些类负责RPC协议层和传输层的实现。

1.2 优点

相对于XML、Json,Thrift在性能和传输数据压缩上有绝对优势。

2 简单使用

2.1 过程

  1. 下载并编译 thrift编译器 .

  2. 编写thrift文件.需要用到 thrift自定义数据类型 .包括

  3. 基本数据类型

  4. 特殊数据类型(binary)

  5. 结构体

  6. 容器(list,set,map).容器元素可以是服务以外的任何thrift type .

  7. 异常(Exceptions)

  8. 服务

  9. 编译thrift文件,生成可供服务端和客户调用的源码.命令语法如下:

1
thrift --gen <language> <thrift filename>

2.2 简单实例

2.2.1 编辑thrift文件

代码清单如下:

1
2
3
4
5
6
7
8
9
10
11
12
namespace cpp im
struct Msg
{
1:i32 idFrom,
2:i32 idTo,
3:string content,
}

service transMsg
{
void trans(1:Msg m);
}

2.2.2 生成cpp代码

使用thrift.exe :

1
thrift.exe -gen cpp transMsg.thrift

此时会在 “gen-cpp” 文件夹(默认文件夹)下生成相应的cpp代码:

1
2
3
4
5
6
7
msg_constants.cpp
msg_constants.h
msg_types.cpp
msg_types.h
transMsg.cpp
transMsg.h
transMsg_server.skeleton.cpp

其中 “msg_ types” 和 “msg_ constants” 对数据类型进行了定义,“transMsg” 实现了基本逻辑。“transMsg_ server.skeleton” 则简单实现了一个server端的实例。

2.2.3 实现业务逻辑

新建两个工程,一个做为服务端,一个做为用户端。 将 gen-cpp 文件夹下的文件分别添加到两个工程中,并在工程中添加thrift的静态库(或直接引用源码).
Server端的工程可以直接使用生成的代码。Client端的业务则需要自己实现。
下面简单实现Client端的业务:

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
int main(int argc, char **argv) {
int port = 9090;
std::string strHost("192.168.3.11");

boost::shared_ptr<TTransport> socket(new TSocket(strHost.c_str(), port));
boost::shared_ptr<TTransport> transport(new TBufferedTransport(socket));
boost::shared_ptr<TProtocol> protocol(new TBinaryProtocol(transport));
transMsgClient client(protocol);

try
{
im::Msg m;
m.__set_idFrom(10);
m.__set_idTo(280);
m.__set_content("Hi, I`m a message. ");

transport->open();

client.trans(m);

transport->close();
}
catch (TException e)
{
printf("%s", e.what());
}

system("pause");
return 0;
}

其中strHost为服务器的IP.

Read more »

目录

1 线程标识

每个线程都有一个唯一标识:线程ID. 线程ID用 pthread_t 数据类型来表示,该结构体在不同的系统上有不同的实现,某些操作系统将其实现为 unsigned long ,某些操作系统将其实现为结构体。所有可移植代码中不可以对其直接比较,必须使用一个函数来对两个线程ID进行比较。

1
2
//返回值:若相等,则为非0;否则,返回0
int pthread_equl(pthread_t t1, pthread_t t2);

不同的实现方式带来的影响就是,不能使用一种可移植的方式来打印该数据类型的值。
获取当前线程:

1
2
3
4
5
//Unix
pthread_t pthread_self(void);

//Windows
HANDLE GetCurrentThread(void);

2 线程创建

新增的线程可以通过调用pthread_create 函数来创建

1
2
3
4
5
#include <pthread.h>
//thread: 当成功时,指向新增线程的内存单元
//返回值:若成功,则返回0;否则 返回错误编号
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);

新创建的线程从 start_routine 函数的地址开始运行。该函数有一个 void* 类型的指针做为参数。如果函数需要多个参数,需要将参数做为结构体,然后将结构体地址做为参数传入。 创建线程时并不能保证新增线程优先于其它线程先运行,这依赖于操作系统的线程实现与调度算法。

Read more »

在升级XCode到版本7以后,使用某些静态库(.a文件),并为IOS模拟器编译时,可能会出现如标题所示错误,提示这个库是为OSX系统的X86_64平台编译的。这可能是XCode版本的一个BUG,在链接静态库时,优先链接了OSX平台下的obj,并抛编译错误。

[

xcode error

](https://code.wandoer.com/wp-content/uploads/2016/03/111.jpg)

为解决此问题,我们在编译静态库时,不要将添加OSX系统下的平台,或者使用lipo将静态库中关于OSX系统平台的内容移除。

首先使用 "-info"选项查看静态库里包含了哪些平台内容:

1
2
$ lipo -info libuuid.a
Architectures in the fat file: libuuid3.a are: armv6 armv7 armv7s i386 arm64 x86_64

可以看到此静态库中包含了 i386和x86_64两个平台内容。然后使用 “-remove” 将其移除:

1
2
3
4
$ lipo libuuid.a -remove i386 -output libuuid.a
$ lipo libuuid.a -remove x86_64 -output libuuid.a
$ lipo -info libuuid.a
Architectures in the fat file: libuuid.a are: armv6 armv7 armv7s arm64

使用移除OSX平台内容后的静态库再次编译,问题解决。

但是会出现warning:

“ld: warning: ignoring file ……/libuuid.a, missing required architecture x86_64 in file ……/libuuid.a (4 slices)”

可以不用理会。但是在发布或部署到真机的时候,还是需要使用带有x86_64平台的静态库。

Statement

前面我们提到了Statement,但是到目前为止,我们只使用了session,至少从代码上来看是这样的。

而实际上,我们已经在使用statement了,只不过它在代码中隐藏了起来。我们来看看Session类中的<<操作符:

1
2
3
4
5
6
template <typename T>
Statement operator << (const T& t)
/// Creates a Statement with the given data as SQLContent
{
return _statementCreator << t;
}

这里<<操作符创建了一个statement并将其返回。前面例子中我们使用<<操作符创建了statement,但是这个statement并没有分配到变量,一直到"now"部分executed了这个隐藏的statement,然后将其销毁。

我们将前面的例子拿一个出来修改一下:

1
2
3
4
std::string name("zrKing");
Statement stmt = (session << "INSERT INTO tbA VALUES(?)",use(name));
stmt.execute();
poco_assert(stmt.done());

当execute执行以后,数据已经插入了。可以使用stmt.done()来确认操作是否已经完全完成。

Prepared Statements

上例中省略的代码now子句创建了一个prepared statement.

prepared statement 的优势在于性能。我们假设有如下的循环:

1
2
3
4
5
6
7
std::string name;
Statement stmt = (session << "INSERT INTO tbA VALUES(?)", use(name));
for(int i = 0; i<100; i++)
{
name.append("x");
stmt.execute();
}

在上例中,如果不使用prepated statement,我们需要创建和解析Statement 100次。而现在我们只需要创建并解析一次Statement,然后使用占位符匹配不同的值,多次执行来将name插入到数据中。

当然,对于如何将一系列值插入到数据库,上例的代码并不是最优解。通过Poco::Data与STL容器协作可以更好的解决这个问题。具体的我们将在"STL容器"这一节来讲。

Read more »

前言

POCO::Data 是Poco的数据库抽象层,为C++提供统一的结构化数据库访问接口,使得C++以简单而自然的方式访问关系型数据库。使用它可以方便地从多种数据库中存取数据。目前Poco::Data支持的数据库连接类型包括 SQLite,MySQL及ODBC。POCO框架支持扩展,所以你也可以添加其他的本地连接扩展。

下面是一个使用Poco::Data::SQLite的一个简单例子:

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include "Poco/Data/Session.h"
#include "Poco/Data/SQLite/Connector.h"
#include <vector>
#include <iostream>

using namespace Poco::Data::Keywords;
using Poco::Data::Session;
using Poco::Data::Statement;

struct Person
{
std::string name;
std::string address;
int age;
};

int main()
{
//注册SQLite连接器
Poco::Data::SQLite::Connector::registerConnector();

// 创建传话 session
Session session("SQLite", "sample.db");

// 删除表
session << "DROP TABLE IF EXISTS Person", now;

// 网创建表
session << "CREATE TABLE Person (Name VARCHAR(30), Address VARCHAR, Age INTEGER(3))", now;

// 插入记录
Person person =
{
"Bart Simpson",
"Springfield",
12
};

Statement insert(session);
insert << "INSERT INTO Person VALUES(?, ?, ?)",
use(person.name),
use(person.address),
use(person.age);

insert.execute();

person.name = "Lisa Simpson";
person.address = "Springfield";
person.age = 10;

insert.execute();

// 简单查询
Statement select(session);
select << "SELECT Name, Address, Age FROM Person",
into(person.name),
into(person.address),
into(person.age),
range(0, 1);

while (!select.done())
{
select.execute();
std::cout << person.name << " " << person.address << " " << person.age << std::endl;
}

return 0;
}

创建会话(Sessions)

通过Session 构造器可以创建Session:

1
Session session("SQLite","./sample.db");

这个例子创建了一个SQLite类型的session。

第一个参数是希望创建的Session类型,目前支持的连接类型有"SQLite",“ODBC”,“MySQL”。

第二个参数是数据库连接字符串,根据不同的数据库使用不同的连接字符串:

  • 对于Sqlite数据库,数据库路径即可做为连接字符串

  • 对于ODBC数据库,连接字符串类似于 “DSN=MyDSNName”.具体参数请参考ODBC驱动文档

  • 对于MySQL数据,连接字符串由一系列键值对组成,例如 “host=localhist;port=3306;db=mydb;user=thename;password=thepwd;compress=true;auto-reconnect=true”;具体请教参MySQL官方文档。

存取数据

单数据集

假设我们有一个表 tbA,表中有一个字段 fN,插入记录时我们可以这么写:

1
2
std::string name="wandoer";
session << "INSERT INTO tbA VALUES("<<name<<")",now;

是不是很简单。然而这种写法并不提倡,我们有更好的方式:占位符!使用占位符(placeholders)可以匹配变量和占位符,在执行语句时自动用变量替换占位符。普遍公认的占位符是问号(?),有些数据库也使用冒号(:)来做占位符。具体使用哪一种,请参考所选用数据库的说明文档。

那么上面的例子可以写做:

1
2
std::string name="wandoer";
session << "INSERT INTO tbA VALUES(?)",use(name), now;

此例中使用use(name)来匹配占位符。use是Poco::Data::Keywords下提供的关键字,可以将变量和占位符绑定(Binding)。这么做真正的意义,是将变量与SQL语句分隔开,防止SQL注入式攻击。

从数据库中取数据的操作与之类似。into关键字将数据库返回的值与C++对象匹配起来。并且支持指定默认值以防数据库返回null值。

1
2
3
std::string name;
session << "SELECT fN FROM tbA",into(name),now;
session << "SELECT fN FROM tbA",into(name,0,std::string("default")),now;

此处需要注意的是,指定默认值的into,其定义为:into(T& t, const Position& pos, const T& def),使用的是模样类。在有些编译器下,不能隐式转换数据类型,所以需要显示的让第一个参数和第三个参数的数据类型保持一致。

into还可以和use联合使用:

1
2
3
std::string name;
std::string match="wandoer";
session << "SELECT fN FROM tbA WHERE fN=?",into(name),use(match),now;

当然,现实中的数据库不会总像例子中这么简单。在一个表中往往有多列,这样需要使用多次into/use关键字:

1
2
3
4
5
std::string firstName("Peter");
std::string lastName("Junior");
int age = 0;
ses << "INSERT INTO PERSON VALUES (?, ?, ?)", use(firstName), use(lastName), use(age), now;
ses << "SELECT (firstname, lastname, age) FROM Person", into(firstName), into(lastName), into(age), now;

那么此时尤为重要的,就是into和use的顺序了。第一个占位符使用第一个use,第二个占位符使用第二个use,以此类推。into亦如是。

Read more »

引子

前一篇文章讲到了C++11的新特性lambda表达式

lambda表达式的本质是什么呢?为探究这个问题,我们将一段写有lambda表达式的代码反编译,可能会发现一些秘密.

首先写段程序:

1
2
3
4
5
6
7
//#include...
auto flambda = [](int x,int y)->int{return x + y;};
int main()
{
cout<<flambda(3,2)<<endl;
return 0;
}

在调试该代码时我们打开汇编视图,发现如下代码:

1
2
3
callq  0x100000d80 <$_0::operator()(int, int) const>
mov 0x2c4(%rip),%rdi # 0x100001010
mov %eax,%esi

其中 <$_0::operator()(int, int) const> 即对应原代码中的lambda表达式。这里一串重载了"()"(operator())操作符的代码,即本文将要了解的仿函数

Note: 此处的 const ,也应证了前文 mutable 可变范围 中提到的,lambda的调用运算符为"const-by-value"的

定义

仿函数(finctor)是一种早期的叫法,在C++11中,标准的叫法为函数对象(function object)。仿函数是一种行为类似函数、具有函数特性的对象。

仿函数是一个对象,而不是函数。仿函数的类通过重载函数操作符"()"(operator())来实现函数调用的特性。

通过下面的例子来简单说明如何使用仿函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <functional>
//#include...

class myGreater
{
public:
bool operator()(int x,int y) //重载调用操作函数
{return x>y;}
};

int main()
{
greater<int> gt; //lineA
cout<<boolalpha<<gt(4,3)<<endl; //lineB
cout<<greater<int>()(3,4)<<endl; //lineC
cout<<myGreater()(5,3)<<endl; //lineD
return 0;
}
//OUTPUT:
//true
//false
//true

这个例子中,lineA声明了一个对象。这是一个函数对象,其类(结构)定义在functional文件中,是STL内置的模板类。该类用于比较两个参数的大小。

lineB则像函数一样直接给该对象传入两个参数。

lineC是另一种写法。greater() 声明了一个临时对象,然后传入参数(3,4)调用该临时对象。这种写法往往比lineB更加简洁。在STL源码里大量使用了这种写法。

lineD则是使用自定义的函数对象。

Note:lineB中使用了boolalpha,是iostream提供的一个操作符,这将使此后的标准输出中,将bool值输出为"true"或"false",而不是1/0。于其相反的操作为noboolalpha.

Read more »

LAMBDA简介[

lambda

](https://code.wandoer.com/wp-content/uploads/2016/01/images.jpeg)

从C++11开始,c++开始支持lambda表达式。

lambda用来创建一个可以捕获作用域内变量的匿名函数对象的闭包。这通常用来封装传递给算法或异步方法的少量代码块。

例如,简单实现一个Trim函数,去除字符串里的空格:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;

string trimall(string str)
{
vector<char> vec;
for_each(str.begin(),str.end(),[&vec](char c){if(!isspace(c)) vec.push_back(c);});
return string(vec.begin(),vec.end());
}

int main() {
std::string str = " Hi, lam b da!";

std::cout << trimall(str);

return 0;
}

//OUTPUT:
//Hi,lambda!

此例中,

1
[&vec](char c){if(!isspace(c)) vec.push_back(c);}

即是一个lambda表达式。其做为一个匿名函数,被算法 *for_each *使用,使得代码更简洁。

Read more »

一、类声明

Objective-C并没有像其他面向对象语言那样提供声明类的专用关键字或者语法,而是用编译处理指令来实现,特征是类声明语句都须以@符号开始

类声明的编译处理指令以 @interface 开始,以 @end 结尾,在这之间代码便是类属性和方法的声明。

1
2
3
4
5
@interface 类名 : 父类名
属性声明
实例方法声明
类方法声明
@end
属性声明

属性是获取和(或)设置类实例状态的接口。

语法如下:

1
@property (可选特性) 属性类型 属性名称

//TODO 属性的特性可以设置属性的存储主义和其他行为。

方法声明

语法如下:

1
方法类型 (返回值类型) 方法名部分1:(参数类型1)参数名1 方法名部分2:(参数类型2)参数名2 ...;

例如:

1
- (void) setpointX:(int)x andY:(int)y;
  • 方法类型 -表示该方法为实例方法 +表示该方法为类方法

  • 返回值类型和参数类型放入()中

  • C函数返回值缺省为int型。OC中缺省参数为id型的对象类型。

  • 习惯上类名首字母大写,方法名全部小写​。

二、类实现

类的实现要在@implementation这个编译处理指令中进行

1
2
3
4
5
6
7
@implementation 类名
{
变量声明
}
属性定义
实例方法定义
@end
变量声明

类的实例变量可以在类的接口或实现部分中声明。不过在类的公有接口中声明变量违反了OOP的特性之一—封装。所以最好在类的实现部分声明实例变量。类可以没有实例变量,这时{}可以忽略。

Object-C提供多种编译器指令设置变量的作用范围(即变量的访问控制):

  • private: 只能在声明它的类中被访问

  • protected: 只能在声明它的类以及其子类中被访问

  • public: 可以被任意代码访问(这会违反类的封装原则)

  • package: 可以被同一个包内的其它类实例或函数访问。这种作用范围通常用于库或框架类。

当用变量保存对象的时候,应该始终使用指针类型。Objective-C对变量包含的对象支持强弱两种类型。强类型指针的变量类型声明包含了类名。弱类型指针使用id作为对象的类型。弱类型指针常用于类的集合,在集合中对象精确的类型可以是未知的。

属性定义

在大多数情况下,属性是由变量支持的,所以,属性定义中会含有属性的getter,setter方法的定义、变量的声明,并在getter/setter方法中使用这个变量。OC提供了多种定义属性的方式:显式定义、关键字补全、自动补全。

  1. 显式定义: 在相应的代码中明确定义属性的访问器(getter,setter)方法。

  2. 关键字补全: 通过使用@synthesize关键字,可以使编译器自动生成属性定义。属性代码会在相应的类实现部分自动补全。

  3. 自动补全: Clang/LLVM(4.2+)是苹果公司推荐使用的OC编译器,他支持对已声明的属性进行自动补全。这意味着编译器可以自动补全以下已声明的属性:没有使用关键字(如@synthesize)进行代码补全的属性、不是通过@dynamic指令自动生成的属性。 编译器会自动补全已声明的方法和相应的变量。

Read more »
0%