函数调用约定(stdcall cdecl thiscall fastcall)

2016/08/31

引子

一位朋友在使用函数指针时出现了一个错误:这个函数指针 FP 要求有 4 个参数,而他将一个只有 3 个参数的函数作为 FP 使用,编译和运行都没有报错,但这样造成的运行结果可能是不正确的。下面一个例子来重现这个问题:

如上例,这两个函数的签名不同。 FunPt 要求 4 个参数,而函数 add 只有 3 个参数。虽然在编译和运行时都没有报错,但毫无疑问,返回的结果是错误的。为什么会出现这样的错误呢,这要从函数的调用约定说起。

__cdecl 调用约定

从 C 语言时代开始就有了这个调用约定。它又称 C调用约定,是 C 程序默认的调用约定(现在也是 C++ 程序默认的调用约定)。在这种约定下,函数参数从右向左入栈,函数堆栈由调用者清理,所以它允许函数参数的个数不确定,且它生成的可执行文件大小 会比 __stdcall 函数大。
按C编译方式,_cdecl调用约定仅在输出函数名前面加下划线,形如_functionname。

__stacall 调用约定

它是 Pascal 程序的默认调用方式,所以又称 Pascal 调用约定。和 __decel 一样,参数是从右到左入栈的方式,不同的是堆栈将由被调用函数来清理。
按C编译方式,_stdcall调用约定在输出函数名前面加下划线,后面加“@”符号和参数的字节数

__fastcall 调用约定

它通 CPU 寄存器传递参数,所以调用较快。这也是为什么叫 “fastcall” 的原因。
按C编译方式,_fastcall调用约定在输出函数名前面加“@”符号,后面加“@”符号和参数的字节数

__thiscall 调用约定

它是 C++ 成员函数是默认调用约定。由于成员函数调用还有一个this指针,因此必须特殊处理。如果参数个数确定,this指针通过ecx传递给被调用者,函数自身清理栈;如果参数个数不确定,this指针在所有参数压栈后被压入堆栈,由调用者清理栈。它的参数也是从右向左入栈的。

比较

下面来比较 cdecl 和 stdcall 两种方式的代码:

借助 Visual Studio ,我们查看汇编代码:

可以看出:main 在调用由 __cdecl 标记的 cAdd 函数后,清理了栈,而调用由 __stdcall标记的 sAdd 函数后,并没有清理栈。

Note 850

GBK 字符中的转义符陷阱

2016/07/19

在工作中很多时候我们会使用 GBK 编码来存储数据。但在有些操作中,例如在解析 JSON/XML 或操作数据库时,会因为 GBK 引发一些问题,导致操作失败或引发异常。 这是因为,GBK 中,有些字符会带有 0x5C 的数据。它本是字符串的一部分,但很可能被当作转义符来处理(‘\’),这就使得操作的数据不正确或引发异常。 这些字符是:

乗俓僜刓匼哱圽塡奬媆峔嶾廫慭怽揬昞朶梊榎橽歕沑漒瀄焅燶猏玕琝甛璡痋盶癨瞈砛碶穃竆筡篭糪絓綷縗繺羂耚肻腬臶臷芢蒤薥蚛蝄蟎衆蟎裓覾譢豛赲踈躙輁郳醆鈂鉢鎈鏫閈闬隲頫颸餦馶骪鯸鮘鳿鵟鸤黒齖

除此之外,还有一些特殊符号,如一些制表符,扩充汉字等,详情请戳《gbk-汉字内码扩展规范编码表》下载,表里凡是在 0x5C 位置的字符都在此列。

qq%e6%88%aa%e5%9b%be20160919114316

 

我们在使用GBK编码时要特别注意这些字符

 

Note 705

多语言编程之 C and Java: Android NDK

2016/07/11

NDK(Native Debelopment Kit) 是 Android 提供的一个工具集,它允许在 Android app 使用 native 代码(C/CPP). 它集成了交叉编译器, 使用 mk 文件对平台、CPU 等进行隔离。在不同的平台下,只需要配置相应的 mk 文件即可以编译相就的模块。

1 NDK project 基本结构

ndkprojectFoloer

 

 

 

 

 

2 Android.mk

Android.mk 文件存在于工程路径中的 jni/ 路径下。它可以看作是 GNUMakefile 的一部分,用来向编译器描述如何从 native 源码构建共享库(动态库)。
Android.mk 可以将源码分成不同的模块。这些 模块 可以是静态库、共享库(甚至是可执行文件)。静态库用来生成共享库,共享库则会和 app 打包。

阅读全文…

多语言编程之 C and Java:JNI进阶–字段访问与方法回调

2016/07/09

在上一章(《多语言编程之 C and Java:JNI 30 分钟入门》)中讲解了 JNI 的基本使用。这一章将讨论一些更加实用的用法:如何在 JNI 中访问 Java 对象的成员变量或静态变量,以及回调 Java 中的成员方法或静态方法。

1 JNI 访问 Java 成员变量

1.1 实例

Java 实现

这个 Java 类中包含 3 个私有成员变量,名为 age 的 int 型变量, 名为 name 的 String 型变量和一个名为 years 的 int 型静态变量。我们将在使用 C 来访问或修改这些成员变量。

C 实现

1.2 访问 Java 变量

在 JNI 中我们获取了这 3 个变量,并对 age 变量进行了修改。
由这段代码可以看出,访问 Java 成员变量的一般步骤为:

  1. 通过 GetObjectClass() 获取 Java 类的引用。
  2. 通过 GetFieldID()/GetStaticFieldID() 从 Java 类中获取 FieldID 。
  3. 基于 FieldID, 通过 Get<type>Field()/GetStatic<type>Field() 取回变量。
  4. 基于 FieldID, 通过 Set<type>Field()/GetStatic<type>Field() 修改变量。

GetFieldID()/GetStaticFieldID() 原型如下:

阅读全文…

多语言编程之 C and Java:JNI 30 分钟入门

2016/06/29

1 引言

1.1 简介

在实际项目中,很多时候是以多种语言混合开发的。常用的模式是使用 C/C++ 做底层支撑,然后上层辅之以高级语言。 JNI可以使 native 代码与 Java 对象交互,而不会像 Java 代码中的功能那样受到诸多限制, 往往充作 Java 与 C/C++/Assembly 语言的粘合剂。

JNI 明确分开了 Java 代码与 native 代码的执行,为两者的通信定义了一套清晰的API.避免 native 代码对 JVM 的直接内存引用。

2 JNI 入门

2.1 JNI 调用 C

2.1.1  java 中的 native 方法声明

HelloJNI.java

当HelloJNI类加载的时候,静态初始化调用 System.loadLibrary() 来加载本地库 hello.dll(或libhello.so)。如果不能加载 hello 库,会抛出 UnsatisfiedLinkError 异常。
然后我们使用 native 关键字声明了 native 方法 sayHello() ,这表明该方法是使用其它语言实现的。
最后 main() 方法实例化了 HelloJNI 并调用了 sayHello().
然后我们编译这个类:

2.1.2  生成 C/C++ 头文件

使用 javah 创建头文件:

会得到如下 .h 文件 HelloJNI.h

这份自动生成的头文件里声明一个 C 函数 Java_HelloJNI_sayHello:

这个方法名是由javah转换来的,转换规则为:

包名中的 点 会被转换为 下划线
函数中带有两个参数(虽然我们在本例中并没有使用这两个参数)

  • JNIEnv*: 指向 JNI environment ,提供对 JNI 方法的访问
  • jobject: 指向 this Java 对象

2.1.3 C 代码的实现

HelloJNI.c

这个实现将在控制台打印万能的 “Hello World!”
文件中包含了 jni.h 头文件。这个头文件由 java 提供,位于 %JAVA_HOME%/include 路径下(Windows下还需要引用 %JAVA_HOME%/include/win32).

2.1.4 编译 C 库

接下来,编译这个 c 文件:

2.1.5 检查成果

阅读全文…

Apache Thrift

2016/05/26

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自定义数据类型 .包括
    1. 基本数据类型
    2. 特殊数据类型(binary)
    3. 结构体
    4. 容器(list,set,map).容器元素可以是服务以外的任何thrift type .
    5. 异常(Exceptions)
    6. 服务
  3. 编译thrift文件,生成可供服务端和客户调用的源码.命令语法如下:

2.2 简单实例

2.2.1 编辑thrift文件

代码清单如下:

2.2.2 生成cpp代码

使用thrift.exe :

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

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

2.2.3 实现业务逻辑

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

其中strHost为服务器的IP.
阅读全文…

Coding 962