Python_Windows系统编程_深入浅出_上篇

Windows系统编程

Python是一门强大的脚本语言,它可以把其他语言编写的程序粘合在一起,可以很容易地调用外部程序,以及调用其他语言编写的动态链接库中的代码,甚至可以将Python程序打包为.exe可执行程序以便在没有安装Python的Windows系统中运行。

1. 注册表编程

对于Windows操作系统,注册表是非常重要的组成部分,Windows将几乎所有软/硬件系统配套信息都保存在注册表中。通过读取注册表中的数据,可以获取Windows平台的相应信息,比如,已安装的服务和程序列表,开机自动运行的程序列表、文件类型与程序的关联关系等;通过修改注册表中的数据,可以对Windows系统进行详细的配置。
Windows注册表有如下5个根键.

  • HKEY_LOCAL_MACHINE(HKLM)
  • HKEY_CURRENT_CONFIG(HKCC)
  • HKEY_CLASSES_ROOT(HKCR)
  • HKEY_USERS(HKU)
  • HKEY_CURRENT_USER(HKCU)
    在Windows shell中输入regedit可以打开注册表编辑器:
    Python_Windows系统编程_深入浅出_上篇_第1张图片
    在注册表编辑器界面中可以对注册表的键和值进行增删改查等操作
    在注册表中,值可以为数值、字符串等多种类型,详细类型如下:
类型名 说明
REG_NONE 没有类型
REG_SZ 字符串类型
REG_EXPAND_SZ 一个可扩展的字符串值,其中可以包括环境变量
REG_BINARY 二进制类型
REG_DWORD/REG_DWORD_LITTLE_ENDIAN DWORD类型,用于存储32位无符号整数,以little-endian格式存储
REG_DWORD_BIG_ENDIAN DWORD类型,存储32位无符号整数,以big-endian格式存储
REG_LINK 到其他注册表键的链接,指定根键或到目标键的路径
REG_MULTI_SZ 一个多字符串值,指定一个非空字符串的排序列表
REG_RESOURCE_LIST 资源列表,用于枚举即插即用硬件及其配置
REG_FULL_RESOURCE_DESCRIPTOR 资源标识符,用于枚举即插即用硬件极其配置
REG_RESOURCE_REQUIREMENTS_LIST 资源需求列表,用于枚举即插即用硬件极其配置
REG_QWORD/REG_QWORD_LITTLE_ENDIAN QWORD类型,用于存储64位无符号整数,以littel-endian格式存储或未指定存储格式

对于注册表编程,可以使用win32api模块和win32con模块,其中win32api模块封装了Windows API 函数,提供了非常友好的接口。该模块中常用的注册表操作函数有8个。

  • RegOpenKey()/RegOpenKeyEx():打开注册表
  • RegCloseKey() :关闭注册表
  • RegQueryValue()/RegQueryValueEx():读取项值
  • RegCreateKey()/RegCreateKeyEx():添加项
  • RegDeleteKey():删除项
  • RegEnumKey():枚举子键
  • RegDeletetValue():删除值

例子,查询注册表并输出本机安装的IE浏览器软件版本信息:

import win32api
import win32con

key = win32api.RegOpenKey(win32con.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Microsoft\\Internet Explorer', 0,
                          win32con.KEY_ALL_ACCESS)
print(win32api.RegQueryValue(key,''))
print(win32api.RegQueryValueEx(key,'Version'))
print(win32api.RegQueryInfoKey(key))
win32api.RegCloseKey(key)

(该程序需要在管理员权限下运行)结果:
Python_Windows系统编程_深入浅出_上篇_第2张图片

检测随系统启动而启动的程序列表

from win32api import *
from win32con import *


def get_values(fullname):
    name = str.split(fullname, '\\', 1)
    try:
        if name[0] == 'HKEY_LOCAL_MACHINE':
            key = RegOpenKey(HKEY_LOCAL_MACHINE, name[1], 0, KEY_READ)
        elif name[0] == 'HKEY_CURRENT_USER':
            key = RegOpenKey(HKEY_CURRENT_USER, name[1], 0, KEY_READ)
        elif name[0] == 'HKEY_CLASSES_ROOT':
            key = RegOpenKey(HKEY_CLASSES_ROOT, name[1], 0, KEY_READ)
        elif name[0] == 'HKEY_CURRENT_CONFIG':
            key = RegOpenKey(HKEY_CURRENT_CONFIG, name[1], 0, KEY_READ)
        elif name[0] == 'HKEY_USERS':
            key = RegOpenKey(HKEY_USERS, name[1], 0, KEY_READ)
        else:
            raise ValueError('Error,no key named', name[0])
        info = RegQueryInfoKey(key)
        for i in range(0, info[1]):
            ValueName = RegEnumValue(key, i)
            print(str.ljust(ValueName[0], 20), ValueName[1])
        RegCloseKey(key)
    except Exception as e:
        print('Sth is wrong')
        print(e)


if __name__ == '__main__':
    KeyNames = ['HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run',
                r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce',
                r'HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run',
                r'HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce']
    for KeyName in KeyNames:
        print('*'*30)
        print(KeyName)
        get_values(KeyName)

结果:
Python_Windows系统编程_深入浅出_上篇_第3张图片

操作Windows注册表的另外一种常用方式是使用Python模块winreg,该模块提供了OpenKey(),DeleteKey(),DeleteValue(),CreateKey(),SetValue(),QueryValueEx(),EnumValue()EnumKey()等大量用于注册表访问和操作的方法。
有一篇博客总结得很好,详情

  • 下面的代码演示了使用模块winreg枚举注册表值的用法:
import winreg

key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r'Software\Microsoft\Windows\CurrentVersion\Explorer')
try:
    i = 0
    while 1:
        Name, Value, Type = winreg.EnumValue(key, i)
        print(repr(Name), ':', repr(Value), ':', Type)
        i += 1
except WindowsError as e:
    # print(e)
    pass

"""
_winreg.QueryValueEx(key,value_name)
该函数用于获取某个key中指定value的data和type,返回类型是tuple
"""
print('=' * 20)
Name = 'link'
Value, Type = winreg.QueryValueEx(key, Name)
print(Name, Value)

结果:
Python_Windows系统编程_深入浅出_上篇_第4张图片

2.创建可执行文件

将Python程序转换为.exe版本可执行文件之后再发布,可以保证在没有安装Python环境的Windows平台上运行。为了将Python程序转换为.exe可执行文件,需要用到py2exe和distutils模块

py2exe 有一定的限制性,可用在python2版本以上或者python3.4版本以下

例如,将上面检测随系统启动而启动的程序列表的代码保存为文件CheckAndViewAutoRunsInSystem.py,然后编写setup.py文件,内容为:

import distutils
import py2exe
distutils.core.setup(console=['CheckAndViewAutoRunsInSystem.py']) # 对于GUI应用程序,应该将关键字console改为windows

最后在命令提示符下执行如下命令

    python setup.py py2exe

如果创建成功会在当前文件夹下生成一个dist子文件夹,其中包含了最终程序执行所需要的所有内容。

另一个更好用的Python程序打包工具是pyinstaller,可以通过

   pip install pyinstaller

来安装该工具。
安装好了之后直接在shell中使用命令

   pyinstaller -F -w python文件名   

即可将当前目录下的Python程序以及其依赖包打包称为python文件名.exe.

使用方法参考http://c.biancheng.net/view/2690.html

如果安装失败,可以尝试先运行这段shell

    pip install future  -i https://pypi.tuna.tsinghua.edu.cn/simple,

然后再

    pip install pyinstaller

3. 调用外部程序

3.1 使用os模块的方法调用外部程序

import os
os.system('notepad.exe') # 打开系统自带的记事本
os.system('notepad c:\\dir.txt') # 用记事本文件编辑c盘下的dir,txt
0

使用上面的system方法也可以调用Windows系统命令,如dir,xcopy等,但有一个缺点,不论启动什么程序会先启动一个控制台窗口,然后再打开被调程序
也可以使用os模块的popen()方法打开外部程序,这样不会出现命令提示符窗口.

os.popen(r'C:\Program Files (x86)\Tencent\WeChat\WeChat.exe') # 打开微信

或者,还可以使用os模块的startfile()方法来打开外部程序或文件,系统将自动关联相应的程序来打开或执行文件

os.startfile(r'C:\Users\ASUS\Desktop\CheckMD5OfFile.py') # 打开Py文件,系统会使用其模式打开方式打开它,我的就是pycharm

3.2 使用win32api模块用ShellExecute()函数来启动外部程序

具体用法

import win32api
win32api.ShellExecute(0,'open','notepad.exe','','',0) # 0表示后台运行程序
42
win32api.ShellExecute(0,'open','notepad.exe','','',1) # 1表示前台运行程序
42
win32api.ShellExecute(0,'open','notepad.exe',r'C:\Users\ASUS\Desktop\student.txt','',1) # 传递参数path打开指定文件
42
win32api.ShellExecute(0,'open','www.baidu.com',',','',1) # 打开网址
42
win32api.ShellExecute(0,'open',r'C:\Users\ASUS\Desktop\student.txt','','',1) # 相当于双击文件
42

使用这种方式运行程序或者打开文件时,不会像os模块的system()方法那样先打开一个cmd,并且系统将根据文件类型自动关联相应程序并打开文件,类似于双击打开文件。

3.3 通过创建进程来启动外部程序

import win32process
 # 打开记事本程序
handle = win32process.CreateProcess(r'C:\windows\notepad.exe','',None,None,0,win32process.CREATE_NO_WINDOW,None,None,win32process.STARTUPINFO())
# 关闭刚刚打开的记事本程序
win32process.TerminateProcess(handle[0],0)
handle = win32process.CreateProcess(r'C:\windows\notepad.exe','',None,None,0,win32process.CREATE_NO_WINDOW,None,None,win32process.STARTUPINFO())
import win32event
win32event.WaitForSingleObject(handle[0],-1) # 设置打开的记事本程序需要手动关闭
0

3.4 通过ctypes来调用动态链接库代码

  • ctypes是Python处理动态链接库的标准扩展模块,提供了与C语言兼容的数据类型,允许再python程序中调用动态链接库或共享库中的代码,从而支持Python与其他编程语言的混合编程,充分发挥各自的优势,大幅提高开发效率和允许效率。
  • 另外,NumPy模块也提供了一个函数numpy.ctypeslib.load_library()用来打开指定的动态链接库并返回一个ctypes对象,通过该对象可以访问动态链接库中的函数。
  • 或者,使用SciPy库的Weave模块也可以方便地将C++程序以字符串的形式嵌入到Python程序中。

ctypes 提供了3种方法调用动态链接库:

  • cdll :cdll加载的库导出的函数必须使用标准的cdecl调用约定(函数的参数从右往左依次压入栈内,在函数执行完成后,由函数的调用者负责函数的栈帧平衡)
  • windll :windll加载的库导出的函数必须使用stdcall调用约定(Win32 API的原生约定)
  • oledll :oledll方法和windll类似,不过假设函数返回一个HRESULT错误代码

下面的代码调用Windows动态链接库user32.dll中的MessageBoxA()函数来显示对话框:

import ctypes
user32 = ctypes.windll.LoadLibrary('user32.dll')
user32.MessageBoxA(0,str.encode('Hello world!'),str.encode('Python ctypes'),0)
1

或者使用下面更为简洁的形式:

import ctypes
ctypes.windll.user32.MessageBoxA(0,str.encode('Hello world!'),str.encode('Python ctypes'),0)
1

下面的代码调用标准C函数库msvcrt中的printf()函数输出文本:

import ctypes
msvcrt = ctypes.cdll.LoadLibrary('msvcrt')
printf = msvcrt.wprintf
printf('hello world!')
12

或者使用下面形式:

import ctypes
ctypes.cdll.msvcrt.wprintf('hello python!')
13

上面的程序在jupternotebook中输出的是字符数量而不是字符,如果将上面的代码保存为.py文件,通过python命令符来运行就可以输出字符串。
ctypes提供了与C语言兼容的数据类型,但在Python中使用C语言的结构体时,需要通过类来改写。下表给出了基本类型的对应关系:

ctypes 类型 C 类型 Python 类型
c_bool _Bool bool (1)
c_char char 单字符字节对象
c_wchar wchar_t 单字符字符串
c_byte char 整型
c_ubyte unsigned char 整型
c_short short 整型
c_ushort unsigned short 整型
c_int int 整型
c_uint unsigned int 整型
c_long long 整型
c_ulong unsigned long 整型
c_longlong __int64long long 整型
c_ulonglong unsigned __int64unsigned long long 整型
c_size_t size_t 整型
c_ssize_t ssize_tPy_ssize_t 整型
c_float float 浮点数
c_double double 浮点数
c_longdouble long double 浮点数
c_char_p char * (NUL terminated) 字节串对象或 None
c_wchar_p wchar_t * (NUL terminated) 字符串或 None
c_void_p void * int 或 None
枚举进程列表
from ctypes.wintypes import *
from ctypes import *
import collections

kernel32 = windll.kernel32


class tagPROCESSENTRY32(Structure):
    """定义结构体"""
    _fields_ = [('dwSize', DWORD),
                ('cntUsage', DWORD),
                ('th32ProcessID', DWORD),
                ('th32DefaultHeapID', POINTER(ULONG)),
                ('th32ModuleID', DWORD),
                ('cntThreads', DWORD),
                ('th32ParentProcessID', DWORD),
                ('pcPriClassBase', LONG),
                ('dwFlags', DWORD),
                ('szExeFile', c_char * 260),
                ]


def enumProcess():
    hSnapshot = kernel32.CreateToolhelp32Snapshot(15, 0)
    fProcessEntry32 = tagPROCESSENTRY32()
    processClass = collections.namedtuple("processInfo","processName processID")
    processSet = []
    if hSnapshot:
        fProcessEntry32.dwSize = sizeof(fProcessEntry32)
        listloop = kernel32.Process32First(hSnapshot,byref(fProcessEntry32))
        while listloop:
            processName = (fProcessEntry32.szExeFile)
            processID = fProcessEntry32.th32ProcessID
            processSet.append(processClass(processName,processID))
            listloop=kernel32.Process32Next(hSnapshot,byref(fProcessEntry32))
        return processSet

for i in enumProcess():
    print("Name:",i.processName,"\tID:",i.processID)

结果:
Python_Windows系统编程_深入浅出_上篇_第5张图片

你可能感兴趣的:(Python_Windows系统编程_深入浅出_上篇)