使用swig创建Python模块

做Python开发,有时出于性能考虑或需要调用已有C/C++实现的东西时,我们就要使用Python的ctypes模块提供的CDLL方法或者自己编写扩展模块。
ctypes的CDLL方法对不含有C++类的dll或者so的访问还是比较方便靠谱的,但是对C++类方法的访问似乎不太好用。这时我们就需要自己编写扩展模块了。

编写扩展模块可以使用Python提供的API从0开始实现(比较累),也可以借助boost、swig或者sip等工具库来实现。知名度很高的PyQt4(Qt4的Python绑定)就是使用sip来完成的。
这里笔者以上一篇文章介绍的minimd5项目为例,记录使用swig创建Python扩展模块的方法,以便日后翻查。
以下步骤中所述源码,可在笔者上传到github上的minimd5项目中找到

  1. 首先安装swig和python开发环境,笔者在ubuntu下使用如下命令完成

    1
    sudo apt-get install swig python-dev
  2. 编写pyminimd5.c,实现函数: char md5sum(const char const file);

  3. 编写pyminimd5.i,该文件由swig加载生成pyminimd5_wrap.cxx文件,具体方法看下一步中的Makefile代码片段

    1
    2
    3
    4
    5
    6
    %module minimd5
    %{
    extern char *md5sum(const char *const file);
    %}

    extern char *md5sum(const char *const file);
  4. 修改minimd5的Makfile,增加用于编译_minimd5.so的代码

    1
    2
    3
    4
    pyminimd5: $(PYMODULE_SOURCES)
    swig -python -c++ pyminimd5.i
    g++ -c pyminimd5.c pyminimd5_wrap.cxx md5.c -I/usr/include/python2.7/ -fPIC
    g++ -shared pyminimd5.o pyminimd5_wrap.o md5.o -o $(PYMODULE_SO)
  5. make pyminimd5生成_minimd5.so,使用Python加载并测试

minimd5

在kindle电子书的源码中,util-linux_2.12r\lib目录下有md5.c和md5.h两个文件。md5.c实现了计算md5值的算法,且没有外部依赖。md5.h内有一行

1
#include "../defines.h"

实际上并不需要defines.h,注视掉即可。利用这两个文件,我们可以实现一个很小的md5计算器,我把它叫做minimd5。源码也灰常简单。已经放在Github上minimd5

效果如图:

开源的TCP/UDP网络通信调试器

笔者在参与的一些项目中,经常需要对使用TCP或UDP通信的协议进行调试。除针对业务协议写一些测试客户端或者脚本外,笔者也会借助网上的现有TCP/UDP调试工具做一些简单的连通和ECHO回包测试。
用过一些工具,总有些不顺手的地方,于是利用琐碎时间,自己用PyQt4做了一个叫PySockDebugger的小东西,已在Github开源PySockDebuger(Debugger误写为Debuger,不再修改)。
1.0Beta版本的windows版本已经通过Py2exe打包并上传到Github项目的release目录下。欢迎下载体验:)
因为使用Python实现,所以在Mac和Linux桌面环境下均可使用(需安装PyQt4,另外有个获取本地IP的小BUG,暂未修改,若使用时遇到,只需按照堆栈错误提示,简单修改下即可)。
主要有以下模式:

  1. TCP服务器
  2. TCP客户端
  3. UDP服务器(主动绑定本地端口)
  4. UDP客户端

另外支持在同一个窗口内创建多个实例,不同的实例在Tab页中显示。下过如下图:

除重复发送和UDP客户端模式下的绑定本地端口,这两个功能暂未实现外(代码好写,但暂未有时间),其他功能都测试可用。
后面打算做个插件功能,由用户实现发送和接收数据的pack和unpack过程,这样在调试不同的协议时,只需加载实现了pack和unpack功能的插件即可。而因为PySockDebuger使用Python实现,这样的插件实现起来还是很方便的。

python的logging模块

这里不打算详细介绍logging模块的使用方法,因为该模块使用简单,而且网上也有比较多的介绍文章。
仅把个人使用Python写小东西时候的logging模块用法记录下来,以便日后copy

文件 log.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#-*- coding: utf-8 -*-
import logging
import sys

logger = logging.getLogger("")
__formatter = logging.Formatter("[%(levelname)-7s][%(asctime)s][%(filename)s:%(lineno)d] %(message)s", "%d %b %Y %H:%M:%S")
__streamHandler = logging.StreamHandler(sys.stderr)
__streamHandler.setFormatter(__formatter)
__fileHandler = logging.FileHandler("debug.log")
__fileHandler.setFormatter(__formatter)

logger.setLevel(logging.DEBUG)
logger.addHandler(__streamHandler)
logger.addHandler(__fileHandler)
使用
1
2
3
4
from log import logger
logger.debug("this is a debug message")
logger.warning("this is a warning message")
logger.error("this is a error message")
效果

在ubuntu中配置samba

在虚拟机中安装了一个Ubuntu,用samba访问资源是非常方便的,之前在我的树莓派2中也安装了samba。这里备注下。

  1. 安装
    1
    sudo apt-get install samba

安装完成后samba的配置目录在/etc/samba下。

  1. 添加用户
    添加当前用户,需root权限。我的用户名是apache,所以我会这么写:
    1
    sudo smbpasswd -a apache

设置密码,完成添加。

  1. 为添加的用户书写配置
    编辑/etc/samba/smb.conf,我在自己的虚拟机上使用,只需在smb.conf的末尾添加如下配置即可:

    1
    2
    3
    4
    5
    6
    7
    [apache]
    comment = Apache's Home
    path = /home/apache
    browseable = yes
    read only = no
    guest ok = no
    create mask = 0600

  2. 重启samba,完成配置

    1
    sudo /etc/init.d/samba restart

现在Windows下敲击Win+R,输入\ubuntu-ip\apache
可以愉快的从windows资源管理器访问ubuntu下apache用户主目录下的文件了:)

在Android Studio中使用Butter Knife

  1. dagger
  2. androidannotations
  3. more…

论性能,Butter Knife因为使用了反射,与androidannotations在编译时生成执行代码的方式相比,自然略差。但其使用方式比较简单,对于干掉findViewById来说已经够用了(当然它能做的不止这个)。

在Android Studio中,可以按照如下步骤配置Butter Knife
  1. http://jakewharton.github.io/butterknife/ 下载Butter Knife的JAR包
  2. 拷贝下载的jar文件到AS的lib目录下
  3. 编辑app模块的build.gradle文件,添加下图中红色框内的内容

    4.点击AS菜单 Build - Make Project(Ctrl+F9)

若没有出错,就可以使用Butter Knife了。
其官网有详细的使用示例:
http://jakewharton.github.io/butterknife/

小提示:被注解的view对象不可以再使用private修饰了。

nodetcpproxy

nodetcpproxy 是在nodejs环境中运行的简易TCP转发服务器。

需求背景:

笔者在开发一款C/S架构的项目,C为Android移动客户端,S为Linux上C实现的服务器。
没有带公网IP的云主机,只有一台安装了VMware的笔记本。VMware中安装了Ubuntu15.04。
笔记本通过WiFi上网,因为XX原因,VMware的网络模式需要设置为NAT模式,因此Ubuntu中ifconfig得到的IP地址可能为192.168.45.133,笔记本VMware虚拟网卡地址为192.168.45.1,WiFi地址可能为10.65.195.129。
此时与笔记本在同一WiFi网络中的手机需要访问运行在笔记本虚拟机ubuntu中的服务器。
显然,手机端的服务器IP不能直接写192.168.45.133。因此笔者需要在笔记本Windows环境中运行一个TCP转发服务器。


虽然TCP转发服务器有hin多,但是笔记最近了解了nodejs相关的技术,觉得其事件驱动的网络模型hin好,代码简洁,性能不俗。于是参考了官方的API,折腾出了一个nodetcpproxy.js的东东,满足开发调试需求。

代码已经在Github开源https://github.com/uname/nodetcpproxy

测试:

首先要有nodejs环境,没有的话去这里下载。
首先根据实际情况设置本地和远程服务器IP/PORT,nodetcpproxy没有提供命令行参数,直接编辑位于源码开头的变量完成设置。
为验证工具是否可用,笔者为虚拟机中的sshd服务器设置个本地的代理。笔者的设置是这样的:

1
2
3
4
var LOCAL_ADDR  = "0.0.0.0";
var LOCAL_PORT = 8080;
var REMOTE_ADDR = "192.168.45.133";
var REMOTE_PORT = 22;

(往下翻看完整代码)

测试效果:

代码:
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
69
70
71
72
73
74
75
76
77
var net = require("net");

var LOG_DATA = true;
var HEX_MODE = true;

var LOCAL_ADDR = "0.0.0.0";
var LOCAL_PORT = 8080;
var REMOTE_ADDR = "192.168.45.133";
var REMOTE_PORT = 22;

var server = net.createServer();
server.listen(LOCAL_PORT, LOCAL_ADDR);

data2print = function(data)
{

if(!HEX_MODE) {
return data.toString();
}

var hexStr = data.toString("hex")
var outStr = "";
for(var i in hexStr) {
if(i % 2 == 0) {
outStr += " "
}
if(i % 32 == 0) {
outStr += "\n>> ";
}
outStr = outStr + hexStr[i];
}

return outStr
}

logData = function(data)
{

if(LOG_DATA) {
console.log(data)
}
}

server.on("connection", function(client)
{

console.log("Client connected " + client.remoteAddress + ":" + client.remotePort);
var proxySocket = new net.Socket();
proxySocket.connect(REMOTE_PORT, REMOTE_ADDR, function() {
logData("Connected to remote -> " + REMOTE_ADDR + ":" + REMOTE_PORT);
});

proxySocket.on("close", function(data) {
console.log("Remote closed");
client.destroy();
});

proxySocket.on("data", function(data) {
logData("Remote -> Proxy -> Client:\n" + data2print(data));
client.write(data);
});

client.on("close", function(data) {
console.log("Client closed");
proxySocket.destroy();
});

client.on("error", function() {
console.log("**Client error");
proxySocket.destroy();
});

client.on("data", function(data) {
logData("Client -> Proxy -> Remote:\n" + data2print(data));
proxySocket.write(data);
});

});

console.log("TCP proxy server started on " + LOCAL_ADDR + ":" + LOCAL_PORT);

在Android Studio中使用so

与Eclipse不同,Android Studio中使用so时需要手动添加依赖关系(至少截至到AS1.3版本时还是这样的)。
比如在使用腾讯地图SDK时,我们需要将libtencentloc.so文件拷贝到app的libs目录下(你也可以拷贝到别处),然后在app的build.gradle文件中添加代码:

1
2
3
4
5
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}

  • 项目的结构和app模块的build.gradle文件看起来是这样的:

在Android Studio中使用aar

什么是aar?

按照Google官方的说法,aar是以二进制形式发布的Android Library Project
aar文件以.aar作为扩展名,而实际上它就是一个zip压缩包,我们将.aar文件重名为为.zip,即可用解压缩软件打开(与apk一样)。
其目录结构如下图所示:

可以看到aar包含了项目的布局等资源文件。


使用aar

我们以github上一个开源的颜色选择器colorpicker为例。
这里我们也不直接编辑build.gradle文件,而是通过IDE提供的菜单入口进行aar文件的加载。

  1. 首先下载aar文件。下载的aar文件不必拷贝到你的工程目录下,在后面的加载过程中Android Studio会自动处理。
  2. Android Studio中按F4(或者你知道的其他方式)打开Project Structure面板,单击面板左上角的+添加新的Module,如图:
  3. 在打开的Create New Module面板中选择Import .JAR/AAR Package,单击Next,如图:
  4. 选择我们下载到colorpicker的aar文件,修改Subproject name为colorpicker,如图:
  5. 单击Finish,完成Module加载,完成后你会app的同级目录看到以colorpicker命名的字项目,如图:
  6. 再次打开Project Structure面板,选择app模块的Dependencies页面,为app模块添加Module Dependence,选择刚才加载的colorpicker模块,单击OK。

    7.如果我们的aar没有更多依赖的话,至此我们就完成了colorpicker.aar的加载,可以愉快的使用了。
    但是我们的colorpicker.aar还依赖了github上的MaterialEditText,所以我么继续下载MaterialEditText的aar文件,参考上述步骤完成加载。

  7. OK,colorpicker.aar加载OK了,我们在layout中使用colorpicker.aar提供的ColorPickerView测试下效果: