Pyhton/Windows 编程

这篇文章很早就准备写了, 但是一直拖着... 故事的起因是我在 Windows 下 rm -rf 了自己的用户目录, 因此在该起事件后就研究起来如何 rm 的时候把文件放入回收站而不是直接删除, Windows 提供了 C++ 版本的 api 来实现这一需求, 但作为一个懒人我并不想玩 C++, 所以, 用 Python 吧.

概览

在 Windows 平台下, Python 通过 ctypes 可以很容易的与 Windows API 进行交互. 如下的代码将在桌面创建一个 MessageBoxW, 并在通知的内容中显示当前时间.

import ctypes
import datetime

c = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
ctypes.windll.user32.MessageBoxW(0, c, '查询时间', 1)

img

MessageBoxW 的 C++ 接口文档位于 https://msdn.microsoft.com/en-us/library/windows/desktop/ms645505(v=vs.85).aspx, 如文档所见, 我们可以很方便的修改包括标题, 正文, 按钮和 icon 等在内的几乎所有内容. 与上述 Python 代码等效的 C++ 代码如下所示:

#include <windows.h>
#pragma comment (lib, "User32.lib")

int WinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR lpCmdLine,
    int nCmdShow
){
    MessageBoxW(NULL, (LPCWSTR)L"2018-05-20 10:22:38", (LPCWSTR)L"查询时间", MB_OKCANCEL);
    return 0;
}

如何删除文件至回收站

现在要实现稍微复杂一点的编程: 删除文件至回收站. 查阅 MSDN 找到如下文档: https://msdn.microsoft.com/en-us/library/windows/desktop/bb762164(v=vs.85).aspx, 发现正好可以满足需求.

SHFileOperation 函数的说明是 "Copies, moves, renames, or deletes a file system object."

与使用 rm 或 os.remove 等删除命令/函数不同, 该函数经过操作系统的封装, 他会在删除文件时询问你"是否真的删除该文件"或在复制文件时提醒你"文件已存在, 是否覆盖", 大部分情况下, 你可以将他等价为你在 Windows 的文件系统里执行某一操作, 就像你在手动操作一样.

int SHFileOperation(
  _Inout_ LPSHFILEOPSTRUCT lpFileOp
);

很好! 我们已经知道该调用哪个函数了. 但现在遇到了一个新的问题, SHFileOperation 的参数 lpFileOp 既不是我们熟知的整形, 也不是字符串, 而是一个名为 LPSHFILEOPSTRUCT 的结构体:

typedef struct _SHFILEOPSTRUCT {
  HWND         hwnd;
  UINT         wFunc;
  PCZZTSTR     pFrom;
  PCZZTSTR     pTo;
  FILEOP_FLAGS fFlags;
  BOOL         fAnyOperationsAborted;
  LPVOID       hNameMappings;
  PCTSTR       lpszProgressTitle;
} SHFILEOPSTRUCT, *LPSHFILEOPSTRUCT;

为了在 Python 中调用该函数, 我们必须实现 LPSHFILEOPSTRUCT 在 Python 中的映射. 定义一个类并继承 ctypes.Structure, 然后为其添加 _fields_ 字段, 字段内部是成员名与类型的元组.

import ctypes
import ctypes.wintypes


class LPSHFILEOPSTRUCT(ctypes.Structure):

    _fields_ = [
        ('hwnd', ctypes.wintypes.HWND),
        ('wFunc', ctypes.wintypes.UINT),
        ('pFrom', ctypes.wintypes.PCHAR),
        ('pTo', ctypes.wintypes.PCHAR),
        ('fFlags', ctypes.wintypes.INT),
        ('fAnyOperationsAborted', ctypes.wintypes.BOOL),
        ('hNameMappings', ctypes.wintypes.LPVOID),
        ('lpszProgressTitle', ctypes.wintypes.PCHAR)
    ]


FO_DELETE = 3

FOF_SILENT = 4
FOF_NOCONFIRMATION = 16
FOF_ALLOWUNDO = 64
FOF_NOCONFIRMMKDIR = 512
FOF_NOERRORUI = 1024
FOF_NO_UI = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_NOCONFIRMMKDIR


def rm(p):
    r = ctypes.windll.shell32.SHFileOperation(LPSHFILEOPSTRUCT(
        hwnd=0,
        wFunc=FO_DELETE,
        pFrom=ctypes.create_string_buffer(p.encode()),
        fFlags=FOF_ALLOWUNDO | FOF_NO_UI
    ))
    if r:
        raise Exception(r)

完成了! 调用 rm 函数即可删除文件至回收站. 注意上面的代码, 我为 LPSHFILEOPSTRUCT 的 fFlags 设置了几个标签, 以实现静默删除文件和删除文件夹至回收站.