Python多进程操作SAP GUI报PyIDispatch序列化、权限拒绝错误
问题根因
两类报错本质是同一个核心问题触发:你把pywin32获取到的SAP COM会话对象(PyIDispatch类型)作为参数传递给了multiprocessing创建的子进程。
- Windows下Python多进程默认通过pickle序列化参数传递给子进程,但COM代理指针是进程独占的内存资源,完全不支持序列化,直接触发
TypeError: cannot pickle 'PyIDispatch' object错误。 - 就算绕开序列化限制,SAP GUI Scripting的COM对象是单线程单元(STA)模型,跨进程直接访问没有对应上下文权限,就会抛出
PermissionError: [WinError 5] Access denied。
另外现有代码还有两处显性bug:
main.py最后进程等待逻辑写了两次p1.join(),漏写p2.join(),第二个进程不会被正确等待回收open_new_windows方法连续创建2个新会话后,仅取了索引为1的会话,实际初始会话+2个新会话总共有3个会话对象,漏了第二个新会话的引用,也没加会话创建完成的等待逻辑,很容易拿到空对象。
正确实现逻辑
核心原则:COM对象不能跨进程、跨线程传递,每个子进程必须独立获取自己要操作的SAP会话对象,绝对不要在主进程初始化好session再作为参数传给子进程。
落地步骤:
- 主进程只负责启动SAP程序、完成账号登录、创建需要数量的会话窗口
- 给子进程只传可序列化的简单参数(比如会话索引、事务码字符串),不要传任何COM对象
- 每个子进程启动后,自行通过
GetObject('SAPGUI')拿到脚本引擎,遍历所有连接下的会话,通过传入的索引匹配到自己要操作的目标会话 - 子进程内部基于自己获取到的会话对象执行事务操作,完全不依赖主进程的COM对象上下文
修正后代码
1. 修正manipulatesap.py的会话创建逻辑
from createconnection import Sap import time class QuerySap(Sap): def __init__(self, sap_env, user_id, user_password, language): super().__init__(sap_env, user_id, user_password, language=language) self.connection = self.get_sap_connection() self.session = self.get_sap_session(self.connection) def open_new_windows(self): init_session_count = self.connection.Sessions.Count # 创建2个新会话 self.connection.Sessions(0).createsession() self.connection.Sessions(0).createsession() # 等待会话全部创建完成,加超时避免死循环 timeout = 10 while self.connection.Sessions.Count < init_session_count + 2 and timeout>0: time.sleep(0.5) timeout -=1 if timeout <=0: raise Exception("创建新SAP会话超时") @property def sess1(self): return self.connection.Sessions(0) @property def sess2(self): return self.connection.Sessions(1)
2. 重写main.py的多进程逻辑
from manipulatesap import QuerySap from multiprocessing import Process from time import sleep from win32com.client import GetObject import pythoncom def get_target_session(session_index): # 子进程内部自行获取SAP会话,不依赖主进程传COM对象 sap_gui = GetObject('SAPGUI').GetScriptingEngine # 默认取第一个SAP连接,多连接场景可以加连接匹配逻辑 conn = sap_gui.Connections(0) return conn.Sessions(session_index) def goto_transaction(session_index, transaction_code): # 初始化当前进程的COM环境,避免权限报错 pythoncom.CoInitialize() session = get_target_session(session_index) session.findById("wnd[0]/tbar[0]/okcd").text = transaction_code session.findById("wnd[0]").sendVKey(0) sleep(5) def sap_interface_multi_process(usr, pw, env): sap_nav = QuerySap(sap_env=env, user_id=usr,user_password=pw, language="PT") sap_nav.open_new_windows() # 只传整数类型的会话索引、字符串类型的事务码,都是可正常序列化的简单类型 # 会话索引0是初始主会话,1、2是新建的两个会话 p1 = Process(target=goto_transaction, args=(0, "TRANSACTION A")) p2 = Process(target=goto_transaction, args=(1, "TRANSACTION B")) p1.start() p2.start() p1.join() p2.join() def main(): print(">>> Start") sap_env = "string_for_connection" sap_interface_multi_process("usr_id", "usr_pw", sap_env) print(">>> Finish") if __name__ == "__main__": main()
createconnection.py不需要修改,保持原有逻辑即可。
额外配置注意事项
- 提前打开SAP GUI的脚本权限:打开SAP GUI选项->「辅助功能和脚本」->「脚本」,勾选「启用脚本」,同时关闭「脚本打开时通知」「脚本附加时通知」,避免弹窗阻断自动化操作
- 如果需要操作超过2个会话,只要在
open_new_windows里增加创建会话的次数,子进程传入对应索引即可 - 不要尝试用多线程替代多进程操作SAP GUI,STA模型下多线程调用依然存在COM对象跨线程封送的问题,稳定性远低于多进程方案。
内容的提问来源于stack exchange,提问作者Rafa_izu
相关产品推荐
相关产品推荐

