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

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再作为参数传给子进程。
落地步骤:

  1. 主进程只负责启动SAP程序、完成账号登录、创建需要数量的会话窗口
  2. 给子进程只传可序列化的简单参数(比如会话索引、事务码字符串),不要传任何COM对象
  3. 每个子进程启动后,自行通过GetObject('SAPGUI')拿到脚本引擎,遍历所有连接下的会话,通过传入的索引匹配到自己要操作的目标会话
  4. 子进程内部基于自己获取到的会话对象执行事务操作,完全不依赖主进程的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 17:01:27