枫易录

Journey of a thousand miles begins with a single step.


  • Home

  • Archives

Untitled

Posted on 2019-12-03

从枚举Windows系统进程看python ctypes与C的Win32混和编程

Posted on 2019-12-03 | In 编程

环境:python2.7

下面是枚举Windows所有进程,根据已知进程名获取进程信息(pid)的实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def getPidByName(processName):
ret = []
aProcesses = (wintypes.DWORD * 1024)()
cbNeeded = wintypes.DWORD(0)

if not ctypes.windll.psapi.EnumProcesses(aProcesses, ctypes.sizeof(aProcesses), ctypes.byref(cbNeeded)):
return ret

cProcesses = cbNeeded.value / ctypes.sizeof(wintypes.DWORD)
for i in range(cProcesses):
if aProcesses[i] != 0:
szProcessName = ctypes.create_unicode_buffer(ctypes.sizeof(wintypes.WCHAR) * wintypes.MAX_PATH)
hProcess = ctypes.windll.kernel32.OpenProcess(0x0400 | 0x0010, False, aProcesses[i])
if hProcess != None:
ctypes.windll.psapi.GetModuleBaseNameW(hProcess, None, szProcessName, ctypes.sizeof(szProcessName) / ctypes.sizeof(wintypes.WCHAR))
if str(szProcessName.value) == processName:
ret.append(int(aProcesses[i]))
ctypes.windll.kernel32.CloseHandle(hProcess)
return ret

基本数据类型

了解一种编程语言的数据类型是通往这条编程大道的基本入门法则,下面是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 |

先简单看下基本类型说明:

  1. 初始化及构造
    基本类型都可以通过类似于C构造函数的方式来声明和初始化。
    比如要声明一个c_bool, cb = c_bool(True)

  2. None 对应C中的 NULL。
    NULL在win32编程中经常用到,所以这一条还是很有用的。实际上,用0也能表示,毕竟在Windows中NULL是0用宏来实现的。

  3. 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)的。

  1. 创建可变的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
2
3
4
5
6
7
>>> from ctypes import *
>>> n = c_int(0)
>>> p = pointer(n)
>>> p1 = POINTER(c_int)
>>> pp = byref(p)
>>> type(pp)
<type 'CArgObject'>

注意:pointer(t)相当于POINTER(type(t)), 也就是说可以用POINTER()来生成(可以理解为注册)新指针类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
>>> from ctypes import *
>>> new_p_type = POINTER(c_int * 10)
>>> type(new_p_type)
<type '_ctypes.PyCPointerType'>
>>> n = new_p_type((c_int * 10)(0))
>>> type(n)
<class '__main__.LP_c_long_Array_10'>
>>> new_p1_type = POINTER(c_int)
>>> type(new_p1_type)
<type '_ctypes.PyCPointerType'>
>>> n1 = new_p1_type(c_int(0))
>>> type(n1)
<class '__main__.LP_c_long'>
```
而引用相当于C中的右值,本身没有被分配空间:
```python
>>> from ctypes import *
>>> n = c_int(0)
>>> f = byref(n)
>>> ff = byref(f)
Traceback (most recent call last):
File "<input>", line 1, in <module>
TypeError: byref() argument must be a ctypes instance, not 'CArgObject'

在Win32编程中,一般有两种情况需要用到ctypes的指针和引用。
一种是普通的一维指针传递,这时使用byref()即可。还一种是多维指针,这里就需要用到pointer()构建指针对象,最后使用byref()传递。
例如**T这种二维指针。

类型转换

使用cast()来进行类型转换(强转)。
例如:

1
2
3
4
5
6
7
>>> from ctypes import *
>>> n = pointer(c_ubyte(1))
>>> n1 = cast(n, POINTER(c_uint))
>>> n
<__main__.LP_c_ubyte object at 0x05D866C0>
>>> n1
<__main__.LP_c_ulong object at 0x053A5850>

DLL加载

说到Win32编程,肯定是需要调用各种DLL的,比如系统库,C运行库等。
用法比较简单,两种方式:

  1. ctypes.LibraryLoader.LoadLibrary(dllname)
    调用该dll的方法较多时,可先保存module再直接使用变量调用。自定义dll使用此方式,dllname即为dll路径
  2. ctypes.LibraryLoader.dllname
    在调用方法较少的情况下使用比较方便。

注意:如果dll不存在,C/C++下加载dll可以判断句柄是否为空,但这里两种方式都会抛异常。可以使用find_library(name)先查找,如果存在时再加载。

回调函数

回调机制在Win32编程中十分重要,很多API都需要传入一个回调函数作为参数。
在ctypes中,有几种声明回调函数的方式。我们来看看它们有什么区别。

  1. CFUNCTYPE(restype, *argtypes, use_errno=False, use_last_error=False)
    一般C回调函数,__stdcall的调用方式。
  2. WINFUNCTYPE(restype, *argtypes, use_errno=False, use_last_error=False)
    Window API专用回调函数,和C中WINAPI这个宏有异曲同工之妙。自然是__cdecl的调用方式了。
  3. PYFUNCTYPE(restype, *argtypes)
    Python回调函数,混和编程中较少使用。

扩展:__cdecl和__stdcall有什么区别呢?

__stdcall:参数由右向左压入堆栈;堆栈由函数本身清理。

__cdecl:参数也是由右向左压入堆栈;但堆栈由调用者清理。

两者在同一名字修饰约定下,编译过后变量和函数的名字也不一样。

Window下开发环境安装教程

Posted on 2019-09-03 | In 编程

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

  1. 以管理员身份运行命令行并输入:choco search svn

    1
    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]
  2. 选择安装tortoisesvn, choco install tortoisesvn -y

更新Chocolatey

choco upgrade chocolatey

查看过期并更新

choco outdated

1
2
3
4
5
6
7
8
λ choco outdated
Chocolatey v0.10.15
Outdated Packages
Output is package name | current version | available version | pinned?

python3|3.7.3|3.7.4|false

Chocolatey has determined 1 package(s) are outdated.

Windows安装SDK7.1

Posted on 2019-08-29 | In 编程

Win10安装会比较麻烦,下面会特殊说明。

卸载

如果本地上有安装过VS2010的话,或者安装失败时,很有可能是因为运行库的版本问题。需要卸载相关组件。

  • Visual c++ 2010 x86 redistributable

  • Visual c++ 2010 x64 redistributable(64位windows)
    也可以直接通过命令行来卸载(没有安装的话会提示不存在):

    1
    2
    msiexec /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注意事项

  1. 下载操作系统对应版本的ISO文件:
    ISO 镜像下载说明
    ISO 32位下载地址
    ISO 64位下载地址
  2. 直接运行Setup\SDKSetup.exe安装。

修复VS2010

如果有需要的话,可以重新运行VS2010安装程序或者在控制面板->程序->VS2010右击修改,选择修复并执行。

不想在Win10下折腾的、折腾了还是失败的童鞋,可以选择直接安装VS2012或者VS2015(单独选择SDK)解决。

论pyqt 编码的蹊跷——QTextStream、QString、string、unicode相关

Posted on 2017-01-14 | In 编程

环境: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上看到的了,很形象,很深刻……谢谢这样仁兄!


相关链接:
PyQt 4.12 Reference Guide

xinglinsky

xinglinsky

改变从今天开始~~~

5 posts
1 categories
7 tags
RSS
© 2019 xinglinsky
Powered by Hexo
|
Theme — NexT.Gemini v5.1.4