从枚举Windows系统进程看python ctypes与C的Win32混和编程
环境:python2.7
下面是枚举Windows所有进程,根据已知进程名获取进程信息(pid)的实例。
1 | def getPidByName(processName): |
基本数据类型
了解一种编程语言的数据类型是通往这条编程大道的基本入门法则,下面是ctypes中python与C对应类型表:
|ctypes type|C type|Python type|
| :———| :—-| :———|
| c_bool | _Bool | bool (1) |
| c_char | char | 1-character string |
| c_wchar | wchar_t | 1-character unicode string |
| c_byte | char | int/long |
| c_ubyte | unsigned char | int/long |
| c_short | short | int/long |
| c_ushort | unsigned short | int/long |
| c_int | int | int/long |
| c_uint | unsigned int | int/long |
| c_long | long | int/long |
| c_ulong | unsigned long | int/long |
| c_longlong | __int64 or long long | int/long |
| c_ulonglong | unsigned __int64 or unsigned long long | int/long |
| c_float | float | float |
| c_double | double | float |
| c_longdouble | long double | float |
| c_char_p | char * (NUL terminated) | string or None |
| c_wchar_p | wchar_t * (NUL terminated) | unicode or None |
| c_void_p | void * | int/long or None |
先简单看下基本类型说明:
初始化及构造
基本类型都可以通过类似于C构造函数的方式来声明和初始化。
比如要声明一个c_bool,cb = c_bool(True)None对应C中的NULL。
NULL在win32编程中经常用到,所以这一条还是很有用的。实际上,用0也能表示,毕竟在Windows中NULL是0用宏来实现的。c_char_p、 c_wchar_p、 c_void_p 构建出的指针,其内容是不可变的,赋新值时实际上是指向了新地址。官方的例子很生动,引用下:
1
2
3
4
5
6
7
8
9
10>>> s = "Hello, World"
>>> c_s = c_char_p(s)
>>> print c_s
c_char_p('Hello, World')
>>> c_s.value = "Hi, there"
>>> print c_s
c_char_p('Hi, there')
>>> print s # first string is unchanged
Hello, World
>>>
可以看出s最终是没有被修改的。也证实了一点,Python中string类型是不可变(immutable)的。
- 创建可变的string buffer。
Win32编程中经常要用到字符数组。比如传递空的字符数组到某API中,获取信息并保存到数组中。那么这里肯定需要可变的string buffer了。
ctypes中可使用create_string_buffer()和create_unicode_buffer()两个方法。
其中,create_string_buffer()生成c_char类型的字符数组,而create_unicode_buffer()生成c_wchar类型的。
普通数组
沿用最上面的枚举进程的代码,这个进程id数组声明如下:aProcesses = (wintypes.DWORD * 1024)()
可以看出,基本格式是: (类型 * 大小)()
数组可以直接作为指针传递
指针与引用
Python ctypes中
指针: pointer()
引用: byref()
指针相当于C中的左值, 本身可以被修改,被操作(取地址):
1 | >>> from ctypes import * |
注意:pointer(t)相当于POINTER(type(t)), 也就是说可以用POINTER()来生成(可以理解为注册)新指针类型。
1 | >>> from ctypes import * |
在Win32编程中,一般有两种情况需要用到ctypes的指针和引用。
一种是普通的一维指针传递,这时使用byref()即可。还一种是多维指针,这里就需要用到pointer()构建指针对象,最后使用byref()传递。
例如**T这种二维指针。
类型转换
使用cast()来进行类型转换(强转)。
例如:
1 | >>> from ctypes import * |
DLL加载
说到Win32编程,肯定是需要调用各种DLL的,比如系统库,C运行库等。
用法比较简单,两种方式:
ctypes.LibraryLoader.LoadLibrary(dllname)
调用该dll的方法较多时,可先保存module再直接使用变量调用。自定义dll使用此方式,dllname即为dll路径ctypes.LibraryLoader.dllname
在调用方法较少的情况下使用比较方便。
注意:如果dll不存在,C/C++下加载dll可以判断句柄是否为空,但这里两种方式都会抛异常。可以使用find_library(name)先查找,如果存在时再加载。
回调函数
回调机制在Win32编程中十分重要,很多API都需要传入一个回调函数作为参数。
在ctypes中,有几种声明回调函数的方式。我们来看看它们有什么区别。
CFUNCTYPE(restype, *argtypes, use_errno=False, use_last_error=False)
一般C回调函数,__stdcall的调用方式。WINFUNCTYPE(restype, *argtypes, use_errno=False, use_last_error=False)
Window API专用回调函数,和C中WINAPI这个宏有异曲同工之妙。自然是__cdecl的调用方式了。PYFUNCTYPE(restype, *argtypes)
Python回调函数,混和编程中较少使用。
扩展:__cdecl和__stdcall有什么区别呢?
__stdcall:参数由右向左压入堆栈;堆栈由函数本身清理。
__cdecl:参数也是由右向左压入堆栈;但堆栈由调用者清理。
两者在同一名字修饰约定下,编译过后变量和函数的名字也不一样。
Window下开发环境安装教程
Window包管理工具Chocolatey安装
一个能让你在Window下体验Linux apt/yum类似一键安装开发环境的包管理器。
官网地址 https://chocolatey.org/
安装要求
- Windows 7+ / Windows Server 2003+
- PowerShell v2+ (Not PowerShell Core yet though)
- .NET Framework 4+ (the installation will attempt to install .NET 4.0 if you do not have it installed)
使用cmd.exe安装
1 | @"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command "iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin" |
使用PowerShell.exe安装
1 | Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) |
运行
choco
查看帮助来获取更多信息,基本命令: search, install, upgrade, uninstall
比如,安装svn
以管理员身份运行命令行并输入:
choco search svn1
2
3
4> choco search svn
Chocolatey v0.10.15
svn 1.8.17 [Approved] Downloads cached for licensed users
tortoisesvn 1.12.2.28653 [Approved]选择安装tortoisesvn,
choco install tortoisesvn -y
更新Chocolatey
choco upgrade chocolatey
查看过期并更新
choco outdated
1 | λ choco outdated |
Windows安装SDK7.1
Win10安装会比较麻烦,下面会特殊说明。
卸载
如果本地上有安装过VS2010的话,或者安装失败时,很有可能是因为运行库的版本问题。需要卸载相关组件。
Visual c++ 2010 x86 redistributable
Visual c++ 2010 x64 redistributable(64位windows)
也可以直接通过命令行来卸载(没有安装的话会提示不存在):1
2msiexec /x {2F8B731A-5F2D-3EA8-8B25-C3E5E43F4BDB}
msiexec /x {81455DEB-FC7E-3EE5-85CA-2EBDD9FD61EB}Microsoft Visual C++ Compilers 2010 X86和X64版本
需要在注册表中查找是否存在,路径为HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall,
查找Display name为“Microsoft Visual C++ Compilers 2010 Standard-enu-x86”和“Microsoft Visual C++ Compilers 2010 Standard-enu-x64”,然后卸载。
下载、运行安装SDK7.1程序
.NET 4.0下载地址
如果本地已安装.NET4.0,可以不用安装。如果本机没有安装VS2010而且有编译的需求,那下载任意版本安装下。
SDK7.1下载地址
如果已安装.NET4.0,可能会提示.NET版本问题,直接忽略。
Win10注意事项
- 下载操作系统对应版本的ISO文件:
ISO 镜像下载说明
ISO 32位下载地址
ISO 64位下载地址 - 直接运行Setup\SDKSetup.exe安装。
修复VS2010
如果有需要的话,可以重新运行VS2010安装程序或者在控制面板->程序->VS2010右击修改,选择修复并执行。
不想在Win10下折腾的、折腾了还是失败的童鞋,可以选择直接安装VS2012或者VS2015(单独选择SDK)解决。
论pyqt 编码的蹊跷——QTextStream、QString、string、unicode相关
环境:Pyqt4.8 32位,python2.7.3 32位
你是不是每次看到什么字符编码、文件编码和字符串类型,都会有些懵逼呢?反正我有点,以前情况不复杂,这次遇到个坑,特此记录下。
背景:
从qrc文件中读取某文本文件,然后解析成json,并显示在Qt控件上。该文件以utf-8编码,并保存有中文,对,就是这个中文的引出的话题,不是中文也就不复杂了……
分析:
1. 从rcc编译的qrc文件中读取文件,也就意味着无法使用python的标准代码:
1
2
3
with open(file_path) as f:
content = json.load(f)
print content
别无选择,只能使用QFile。
2. 说到QFile,自然要用到QTextStream了。
3. 再使用python unicode()函数将str对象解码。
4. 最后使用json库loads()方法,解析成json对象。
基本代码是这样的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def read_file(path):
try:
f = QtCore.QFile(path)
if not f.open(QtCore.QFile.ReadOnly | QtCore.QFile.Text):
return ""
ts = QtCore.QTextStream(f)
tsData = ts.readAll()
content = unicode(tsData, "utf-8", "ignore")
return json.loads(content)
except:
import traceback
traceback.print_exc()
finally:
f.close()
最后发现报错了……
ValueError: Invalid control character at: line 14 column
5. 尝试将tsData先转换为utf-8编码,结果还是报错……
还尝试着直接使用str()等等方法,包括网上的一些技巧,比如:json.loads(content, strict=False),都失败了……解决及总结:
1. QTextStream在读取文本文件时,会默认使用Local的字符编码,如果不指定编码,会使后续的处理寸步难行……
后续有个官方链接说明了
For Python v2 the following conversions are done by default.
If Qt expects a char *, signed char * or an unsigned char * (or a const version) then PyQt4 will accept a unicode or QString that contains only ASCII characters, a str, a QByteArray, or a Python object that implements the buffer protocol.
If Qt expects a char, signed char or an unsigned char (or a const version) then PyQt4 will accept the same types as for char *, signed char * and unsigned char * and also require that a single character is provided.
If Qt expects a QString then PyQt4 will accept a unicode, a str that contains only ASCII characters, a QChar or a QByteArray.
If Qt expects a QByteArray then PyQt4 will accept a unicode that contains only Latin-1 characters, or a str
2. Unicode()在不指定encoding参数的情况下,有两种操作。如果字符串是str对象,则会调用str(),也就是使用python默认的ascci编码来解码。如果已经是Unicode对象则不会任何附加操作。
If no optional parameters are given, unicode() will mimic the behaviour of str() except that it returns Unicode strings instead of 8-bit strings. More precisely, if object is a Unicode string or subclass it will return that Unicode string without any additional decoding applied.
所以在这里,我们需要指定utf-8的编码格式,才能转化为unicode对象。
最后代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@contextmanager
def read_file(path):
try:
f = QtCore.QFile(path)
if f.open(QtCore.QFile.ReadOnly | QtCore.QFile.Text):
ts = QtCore.QTextStream(f)
ts.setCodec("utf-8")
tsData = ts.readAll()
content = unicode(tsData.toUtf8(), "utf-8", "ignore")
yield json.loads(content)
else:
yield ""
except:
import traceback
traceback.print_exc()
yield ""
finally:
f.close()
有个需要注意的地方,如果要gui控件能正常显示中文,content = unicode(tsData.toUtf8(), "utf-8", "ignore")中的toUtf8()是必不可少,不然会显示为乱码。
总结:
一句话总结:区分什么是编码,什么是对象,Unicode是中转对象,str->unicode是解码,unicode->str是编码。
不知在谁的blog上看到的了,很形象,很深刻……谢谢这样仁兄!