从ctypes的windll中获取错误信息。

3
我将使用Python脚本来更改Windows 7电脑上的壁纸。如果有影响,我会从node-webkit应用程序中调用该脚本。
缩短后的脚本如下所示:
# ...
result = ctypes.windll.user32.SystemParametersInfoA(20, 0, path, 0)

通常情况下它是有效的,但有时候它好像是随机失效的。是否有任何方法可以检索到更多的错误信息而不仅仅是状态码(0或1)?

我一直在尝试使用GetLastError,这个函数有时与ctypes库相关联,但是我无法提取任何错误信息。

2个回答

4

ctypes文档建议使用use_last_error=True以安全的方式捕获GetLastError()。请注意,在引发WinError时需要检索错误代码:

from ctypes import *

SPI_SETDESKWALLPAPER = 0x0014
SPIF_SENDCHANGE = 2
SPIF_UPDATEINIFILE = 1

def errcheck(result, func, args):
    if not result:
        raise WinError(get_last_error())

user32 = WinDLL('user32',use_last_error=True)
SystemParametersInfo = user32.SystemParametersInfoW
SystemParametersInfo.argtypes = [c_uint,c_uint,c_void_p,c_uint]
SystemParametersInfo.restype = c_int
SystemParametersInfo.errcheck = errcheck

SystemParametersInfo(SPI_SETDESKWALLPAPER,0,r'xxx',SPIF_UPDATEINIFILE | SPIF_SENDCHANGE)

输出:

Traceback (most recent call last):
  File "C:\test.py", line 17, in <module>
    SystemParametersInfo(SPI_SETDESKWALLPAPER,0,r'c:\xxx',SPIF_UPDATEINIFILE | SPIF_SENDCHANGE)
  File "C:\test.py", line 9, in errcheck
    raise WinError(get_last_error())
FileNotFoundError: [WinError 2] The system cannot find the file specified.

除了所有这些工作,还有一个选择是使用pywin32并调用win32gui.SystemsParametersInfo


如果有可能解释器执行更改Win32线程的LastErrorValue的中断处理程序,则在主线程上使用use_last_error很有用。实际上这种情况很少发生,所以我通常不强调使用此功能;也许我应该这样做。 - Eryk Sun
罗宾从未回复我,但我认为问题很简单,就是它没有更新注册表并广播WM_SETTINGCHANGE消息。 - Eryk Sun
@Robin,当我尝试使用PNG而不是JPG/BMP时,即使调用失败,我也没有收到任何错误信息。(有关更多详细信息,请参见您对答案的评论。)您确定在这种情况下看到了Win32错误代码2吗?为确保起见,请事先调用ctypes.set_last_error(0)。话虽如此,使用use_last_error可能是不必要的。从FFI调用返回路径上不应该有任何Win32调用会修改线程的LastErrorValue,因此它只是解决了信号处理程序或由于对象释放而导致的Win32调用的可能性。无论如何,这是一个好习惯。 - Eryk Sun
@Robin, @eryksun,如果我在上面的代码中使用raise WinError(),即使文件不存在,我也会得到The operation completed successfully。这似乎是一个需要使用use_last_error并通过get_last_error检索错误代码的情况。 - Mark Tolonen
1
@Robin,关于上面的代码对你不起作用的问题。我已经测试了Python 2和3,32位和64位,它都可以正常工作(使用.JPG)。要检查的一件事是确保如果您使用SystemParametersInfoW,则传递Unicode字符串,而SystemParametersInfoA需要一个字节字符串。使用c_void_p作为第三个参数时,它不会检查您是否正确。或者根据需要将c_void_p更改为c_char_pc_wchar_p以强制进行检查。 - Mark Tolonen
显示剩余5条评论

1
如果您将壁纸设置为不支持的格式的现有文件,则会收到“操作已成功完成”的消息,即使壁纸未设置。

以下是我使用的代码,如果您输入不存在的路径,则会引发错误。如果文件存在但格式不受支持,则该函数不会引发错误。

import ctypes, os, sys
from ctypes import WinError, c_int, WINFUNCTYPE, windll
from ctypes.wintypes import BOOL, HWND, LPCSTR, UINT

path = os.path.abspath(os.path.join(os.getcwd(), sys.argv[1]))

SPIF_SENDCHANGE = 2
SPIF_UPDATEINIFILE = 1

prototype = WINFUNCTYPE(BOOL, UINT, UINT, LPCSTR, UINT)
paramflags = (1, "SPI", 20), (1, "zero", 0), (1, "path", "test"), (1, "flags", 0)
SetWallpaperHandle = prototype(("SystemParametersInfoA", windll.user32), paramflags)
def errcheck (result, func, args):
    if result == 0:
        raise WinError()
    return result

SetWallpaperHandle.errcheck = errcheck 
try:
    i = SetWallpaperHandle(path=str(path), flags=(SPIF_UPDATEINIFILE | SPIF_SENDCHANGE))
    print(i)
except Exception as e:
    print(e)

在将图片设置为壁纸之前,我将它们转换为.jpg格式来解决问题。


错误信息完全没有用,它打印:“[错误 0] 操作成功完成。”所以我正在寻找其他定位错误的方法,如果您有任何建议。 - Robin
我正在64位Windows上使用它,除了消息无用之外没有任何问题。然而,我正在尝试使用函数原型重写它,但到目前为止还没有成功。如果我有任何进展,我会再联系你的。感谢您抽出时间来尝试帮助! - Robin
已经让原型也能正常工作了,但错误信息仍然是“[Error 0] The operation completed successfully.”。我想我会在几台不同的机器上进行测试,看看结果是否一致。感谢您的帮助! - Robin
1
我附加了一个调试器来检查当尝试设置PNG而不是JPG/BMP时会发生什么。结果发现,系统调用user32!NtUserSystemParametersInfo返回的NTSTATUS代码设置为0(成功),尽管它显然失败了。在这种情况下,真的没有办法知道此调用在内核模式(win32k.sys)的深层中失败了。 - Eryk Sun

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接