You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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
原因分析
  1. UAC提权时机错误:setKeyValue中在程序运行中途触发提权,会启动新的管理员进程并终止原进程。原进程持有音频设备的COM接口资源,进程终止时COM对象析构函数尝试释放资源,此时进程环境已不稳定,引发访问违例或VTable错误。
  2. 函数作用域错误:wlstatus是__init__内的嵌套函数,wltoggle中调用self.wlstatus()会触发AttributeError,但异常处理掩盖了错误,最终导致COM资源释放崩溃。
  3. COM资源未显式管理:devicesetup每次调用都会创建新的COM接口实例,但未显式释放,进程意外退出时自动释放逻辑会出错。
  4. 注册表值设置错误: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 12:52:12