Python脚本UAC提权时崩溃问题求助
问题描述
我编写了一款用于音频平衡配置及注册表项修改的Python脚本,由于修改注册表需要权限,脚本会通过pyuac模块按需请求UAC提权。目前遇到的问题是:负责提权与注册表修改的setKeyValue函数,在被wlstatus函数调用时运行完全正常,但被wltoggle函数调用时,会直接导致整个脚本崩溃,控制台多数情况下无任何输出仅重启;偶尔会出现comtypes相关的报错,包括访问违例错误与无VTable的COM方法调用错误。尝试改用ShellExecuteEx实现提权,问题仍然存在。
脚本代码如下:
from ctypes import cast, POINTER from comtypes import CLSCTX_ALL from pycaw.pycaw import AudioUtilities, IAudioEndpointVolume import time import sys, winreg, pyuac from PyQt5.QtWidgets import QDialog, QApplication from balancer_ui import * adminError = False class Window(QDialog): def devicesetup(self): devices = AudioUtilities.GetSpeakers() interface = devices.Activate(IAudioEndpointVolume._iid_, CLSCTX_ALL, None) volume = cast(interface, POINTER(IAudioEndpointVolume)) return devices, interface, volume def setKeyValue(self, value): global adminError try: if not pyuac.isUserAdmin(): adminError = False pyuac.runAsAdmin() sys.exit(0) except Exception as e: adminError = True self.ui.wlErrorPopup("Cannot run as Admin: ", e) try: keySet = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SYSTEM\CurrentControlSet\Control\Bluetooth\Audio\AVRCP\CT", 0, winreg.KEY_SET_VALUE) winreg.SetValueEx(keySet, 'DisableAbsoluteVolume', 0, winreg.REG_DWORD, value) winreg.CloseKey(keySet) except Exception as e: self.ui.wlErrorPopup("Error writing registry: ", e) return adminError def __init__(self): super().__init__() self.ui = Ui_Dialog() self.ui.setupUi(self) self.ui.balance.valueChanged.connect(self.main) self.ui.volume.valueChanged.connect(self.main) self.ui.wireless.released.connect(self.wltoggle) def wlstatus(): global adminError adminError = False try: keyRead = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SYSTEM\CurrentControlSet\Control\Bluetooth\Audio\AVRCP\CT", 0, winreg.KEY_QUERY_VALUE) keyReadValue = winreg.QueryValueEx(keyRead, 'DisableAbsoluteVolume') errorRead = False winreg.CloseKey(keyRead) except Exception as e: errorRead = True keyReadValue = False if not pyuac.isUserAdmin(): self.ui.wlErrorPopup("There has been an error reading the registry. Wireless audio balancing will not work. The program will automatically try to fix the error. If it fails, edit the registry manually. The error is: ", e) try: adminError = self.setKeyValue(0) except Exception as e: print(e) self.ui.wlErrorPopup("Something else went wrong: ", e) print(e) return errorRead, keyReadValue, adminError for i in range (2): errorRead, keyReadValue, adminError = wlstatus() if adminError: break if errorRead: self.ui.wireless.setEnabled(False) elif keyReadValue[0] == 1: self.ui.wireless.setChecked(True) devices, interface, volume = self.devicesetup() mastervolume = round(volume.GetMasterVolumeLevelScalar()*100) if mastervolume == 0: self.ui.volumeval.setText("0") self.ui.volume.setValue(mastervolume) self.show() def wltoggle(self): try: if self.ui.wireless.isChecked(): value = 98 else: value = 6 self.setKeyValue(value) keyReadValue = self.wlstatus() if keyReadValue[0] != value: self.ui.wlErrorPopup("Something else went wrong ", e) except Exception as e: print(e) self.ui.wlErrorPopup("Something else went wrong: ", e) def main(self): devices, interface, volume = self.devicesetup() target = self.ui.balance.value() mastervolume = self.ui.volume.value()/100 volume.SetMasterVolumeLevelScalar(mastervolume, None) self.ui.balanceval.setText(str(target-5)) if target != 5: targetL = (mastervolume/10)*(10-target) targetR = (mastervolume/10)*(target) else: targetL = mastervolume targetR = mastervolume volume.SetChannelVolumeLevelScalar(0, targetL, None) volume.SetChannelVolumeLevelScalar(1, targetR, None) if __name__=="__main__": app = QApplication(sys.argv) w = Window() w.show() sys.exit(app.exec())
报错信息
偶尔出现的报错:
Exception ignored in: <function _compointer_base.__del__ at 0x0000023A081B7380> Traceback (most recent call last): File "C:\Users\MJouv\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\LocalCache\local-packages\Python311\site-packages\comtypes\__init__.py", line 956, in __del__ self.Release() File "C:\Users\MJouv\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\LocalCache\local-packages\Python311\site-packages\comtypes\__init__.py", line 1211, in Release return self.__com_Release() ^^^^^^^^^^^^^^^^^^^^ OSError: exception: access violation reading 0xFFFFFFFFFFFFFFFF
以及:
Exception ignored in: <function _compointer_base.__del__ at 0x000001BBD2CB3420> Traceback (most recent call last): File "C:\Users\MJouv\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\LocalCache\local-packages\Python311\site-packages\comtypes\__init__.py", line 956, in __del__ self.Release() File "C:\Users\MJouv\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\LocalCache\local-packages\Python311\site-packages\comtypes\__init__.py", line 1211, in Release return self.__com_Release() ^^^^^^^^^^^^^^^^^^^^ ValueError: COM method call without VTable
原因分析
- UAC提权时机错误:
setKeyValue中在程序运行中途触发提权,会启动新的管理员进程并终止原进程。原进程持有音频设备的COM接口资源,进程终止时COM对象析构函数尝试释放资源,此时进程环境已不稳定,引发访问违例或VTable错误。 - 函数作用域错误:
wlstatus是__init__内的嵌套函数,wltoggle中调用self.wlstatus()会触发AttributeError,但异常处理掩盖了错误,最终导致COM资源释放崩溃。 - COM资源未显式管理:
devicesetup每次调用都会创建新的COM接口实例,但未显式释放,进程意外退出时自动释放逻辑会出错。 - 注册表值设置错误:
wltoggle中设置的98/6并非DisableAbsoluteVolume的有效值(该键仅接受0或1),会导致注册表操作异常。
解决方案
1. 重构提权逻辑,启动时检查权限
将提权操作移到程序启动最开始,避免在持有COM资源时终止进程:
def check_admin(): if not pyuac.isUserAdmin(): try: pyuac.runAsAdmin() sys.exit(0) except Exception as e: print(f"提权失败: {e}") sys.exit(1) if __name__=="__main__": check_admin() app = QApplication(sys.argv) w = Window() w.show() sys.exit(app.exec())
2. 将wlstatus改为类成员方法
把嵌套在__init__内的wlstatus移到类顶层,确保wltoggle能正确调用:
class Window(QDialog): # ... 其他代码 ... def wlstatus(self): global adminError adminError = False try: keyRead = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SYSTEM\CurrentControlSet\Control\Bluetooth\Audio\AVRCP\CT", 0, winreg.KEY_QUERY_VALUE) keyReadValue = winreg.QueryValueEx(keyRead, 'DisableAbsoluteVolume') errorRead = False winreg.CloseKey(keyRead) except Exception as e: errorRead = True keyReadValue = (0,) self.ui.wlErrorPopup("读取注册表错误,无线音频平衡功能将无法使用。程序会尝试自动修复,若失败请手动修改注册表。错误信息: ", e) try: adminError = self.setKeyValue(0) except Exception as e: print(e) self.ui.wlErrorPopup("修复失败: ", e) return errorRead, keyReadValue, adminError
3. 显式管理COM资源
添加COM资源释放逻辑,在窗口关闭时主动释放:
class Window(QDialog): def __init__(self): super().__init__() self.audio_devices = None self.audio_interface = None self.volume_ctrl = None self.init_audio_devices() # ... 其他初始化代码 ... def init_audio_devices(self): self.audio_devices = AudioUtilities.GetSpeakers() self.audio_interface = self.audio_devices.Activate(IAudioEndpointVolume._iid_, CLSCTX_ALL, None) self.volume_ctrl = cast(self.audio_interface, POINTER(IAudioEndpointVolume)) def release_audio_resources(self): if self.volume_ctrl: self.volume_ctrl.Release() if self.audio_interface: self.audio_interface.Release() if self.audio_devices: self.audio_devices.Release() def closeEvent(self, event): self.release_audio_resources() event.accept()
4. 修复wltoggle逻辑错误
修正注册表值设置(使用0/1),修复变量未定义问题:
def wltoggle(self): try: value = 1 if self.ui.wireless.isChecked() else 0 adminError = self.setKeyValue(value) if adminError: return errorRead, keyReadValue, _ = self.wlstatus() if not errorRead and keyReadValue[0] != value: self.ui.wlErrorPopup("修改失败,注册表值未更新") except Exception as e: print(e) self.ui.wlErrorPopup("操作出错: ", e)
修改后的完整代码
from ctypes import cast, POINTER from comtypes import CLSCTX_ALL from pycaw.pycaw import AudioUtilities, IAudioEndpointVolume import sys, winreg, pyuac from PyQt5.QtWidgets import QDialog, QApplication from balancer_ui import * adminError = False def check_admin(): if not pyuac.isUserAdmin(): try: pyuac.runAsAdmin() sys.exit(0) except Exception as e: print(f"提权失败: {e}") sys.exit(1) class Window(QDialog): def __init__(self): super().__init__() self.ui = Ui_Dialog() self.ui.setupUi(self) self.ui.balance.valueChanged.connect(self.main) self.ui.volume.valueChanged.connect(self.main) self.ui.wireless.released.connect(self.wltoggle) self.audio_devices = None self.audio_interface = None self.volume_ctrl = None self.init_audio_devices() errorRead, keyReadValue, adminError = self.wlstatus() if errorRead: self.ui.wireless.setEnabled(False) elif keyReadValue[0] == 1: self.ui.wireless.setChecked(True) mastervolume = round(self.volume_ctrl.GetMasterVolumeLevelScalar()*100) self.ui.volumeval.setText(str(mastervolume)) self.ui.volume.setValue(mastervolume) self.show() def init_audio_devices(self): self.audio_devices = AudioUtilities.GetSpeakers() self.audio_interface = self.audio_devices.Activate(IAudioEndpointVolume._iid_, CLSCTX_ALL, None) self.volume_ctrl = cast(self.audio_interface, POINTER(IAudioEndpointVolume)) def release_audio_resources(self): if self.volume_ctrl: self.volume_ctrl.Release() if self.audio_interface: self.audio_interface.Release() if self.audio_devices: self.audio_devices.Release() def setKeyValue(self, value): global adminError adminError = False try: keySet = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SYSTEM\CurrentControlSet\Control\Bluetooth\Audio\AVRCP\CT", 0, winreg.KEY_SET_VALUE) winreg.SetValueEx(keySet, 'DisableAbsoluteVolume', 0, winreg.REG_DWORD, value) winreg.CloseKey(keySet) except Exception as e: adminError = True self.ui.wlErrorPopup("写入注册表错误: ", e) return adminError def wlstatus(self): global adminError adminError = False try: keyRead = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SYSTEM\CurrentControlSet\Control\Bluetooth\Audio\AVRCP\CT", 0, winreg.KEY_QUERY_VALUE) keyReadValue = winreg.QueryValueEx(keyRead, 'DisableAbsoluteVolume') errorRead = False winreg.CloseKey(keyRead) except Exception as e: errorRead = True keyReadValue = (0,) self.ui.wlErrorPopup("读取注册表错误,无线音频平衡功能将无法使用。程序会尝试自动修复,若失败请手动修改注册表。错误信息: ", e) try: adminError = self.setKeyValue(0) except Exception as e: print(e) self.ui.wlErrorPopup("修复失败: ", e) return errorRead, keyReadValue, adminError def wltoggle(self): try: value = 1 if self.ui.wireless.isChecked() else 0 adminError = self.setKeyValue(value) if adminError: return errorRead, keyReadValue, _ = self.wlstatus() if not errorRead and keyReadValue[0] != value: self.ui.wlErrorPopup("修改失败,注册表值未更新") except Exception as e: print(e) self.ui.wlErrorPopup("操作出错: ", e) def main(self): if not self.volume_ctrl: self.init_audio_devices() target = self.ui.balance.value() mastervolume = self.ui.volume.value() / 100 self.volume_ctrl.SetMasterVolumeLevelScalar(mastervolume, None) self.ui.balanceval.setText(str(target - 5)) if target != 5: targetL = (mastervolume / 10) * (10 - target) targetR = (mastervolume / 10) * target else: targetL = mastervolume targetR = mastervolume self.volume_ctrl.SetChannelVolumeLevelScalar(0, targetL, None) self.volume_ctrl.SetChannelVolumeLevelScalar(1, targetR, None) def closeEvent(self, event): self.release_audio_resources() event.accept() if __name__=="__main__": check_admin() app = QApplication(sys.argv) w = Window() w.show() sys.exit(app.exec())
内容的提问来源于stack exchange,提问作者Vincent Cerowski
相关产品推荐
相关产品推荐

