繁体   English   中英

VBA 和 Python 之间的 IPC

[英]IPC between VBA and Python

我面临以下问题:在我们公司,我们使用的软件的 GUI 是用 MS Access/VBA 编程的。 现在应该将部分业务逻辑移至 Python,但仍应保留 MS Access 部分。 现在实现并工作了以下场景:用户在 Access 中键入一个字符串,该字符串在 VBA 中读出,并通过命令行调用 Python 脚本并将该字符串作为命令行参数提供给脚本. Python 反过来连接到供应商的数据库,使用传递的字符串作为参数,并将结果存储在我们的 MS SQL 数据库中。 供应商为其数据库提供了 Python API,因此通过 Python 进行了必要的中间步骤。 这种情况每天发生几次,每次启动脚本或解释器大约需要 3 秒。 这需要太长时间。 以下是不需要的:将 Python 脚本转换为 Web 服务器或使用 Python 重新编程 GUI。

Sub CallPython()


Dim PythonExe As String, PythonScript As String, PythonArgs As String, PythonOutput As String
Dim PythonCommand As String
Dim objShell As Object

PythonExe = """C:\Program Files\Python37\python.exe"""
PythonScript = """[path_to_our_script]\insert_article.py"""
PythonArgs = "-id 123456"

Set objShell = VBA.CreateObject("Wscript.Shell")

PythonCommand = PythonExe & " " & PythonScript & " " & PythonArgs 
'MsgBox PythonCommand
objShell.Run PythonCommand


End Sub

我看过以下关于IPC技术的页面,但我在这方面没有太多经验,所以我不能说太多复杂性。 有没有人有上述场景的经验并且可以分享他们对更智能解决方案的知识?

这个主题确实非常广泛和复杂。

我自己通过命名管道使用 R 和 Access 之间的双向直接通信完成了这项工作,这些命名管道的处理方式与 Python(或 R)端的文件非常相似。 但是,Access 端需要许多 API 声明来设置管道,就我而言,查看进度,以便我们可以异步报告进度而不会锁定应用程序。

命名管道的基础知识可以在这里找到:

https://docs.microsoft.com/en-us/windows/win32/ipc/multithreaded-pipe-server

和这里:

https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipe-client

如果只有一个客户端(您的 Python 应用程序),命名管道服务器可以是单线程的,因此您可以忽略大多数多线程内容。

我在 VBA 中需要的声明是:

Private Type SECURITY_ATTRIBUTES
    nLength As Long
    lpSecurityDescriptor As LongPtr
    bInheritHandle As Long
End Type
 
Private Type PROCESS_INFORMATION
    hProcess As LongPtr
    hThread As LongPtr
    dwProcessId As Long
    dwThreadId As Long
End Type
 
Private Type STARTUPINFO
    cb As Long
    lpReserved As LongPtr
    lpDesktop As LongPtr
    lpTitle As LongPtr
    dwX As Long
    dwY As Long
    dwXSize As Long
    dwYSize As Long
    dwXCountChars As Long
    dwYCountChars As Long
    dwFillAttribute As Long
    dwFlags As Long
    wShowWindow As Integer
    cbReserved2 As Integer
    lpReserved2 As LongPtr
    hStdInput As LongPtr
    hStdOutput As LongPtr
    hStdError As LongPtr
End Type
 
Private Const STARTF_USESHOWWINDOW  As Long = &H1
Private Const STARTF_USESTDHANDLES  As Long = &H100
Private Const SW_HIDE               As Long = 0&
Private Const ERROR_SUCCESS As Long = 0
Private Const STILL_ACTIVE As Long = 259
Private Const PIPE_TYPE_BYTE As Long = 0
Private Const PIPE_ACCESS_INBOUND = 1
Private Const PIPE_ACCESS_OUTBOUND = 2
Private Const PIPE_ACCESS_DUPLEX As Long = 3
Private Const PIPE_WAIT As Long = 0
Private Const PIPE_NOWAIT As Long = 1
Private Const PIPE_ACCEPT_REMOTE_CLIENTS As Long = 0
Private Const ERROR_PIPE_CONNECTED = 535
Private Const ERROR_PIPE_LISTENING = 536

Private Declare PtrSafe Function CreatePipe Lib "kernel32" (ByRef hReadPipe As LongPtr, ByRef hWritePipe As LongPtr, ByVal lpPipeAttributes As LongPtr, ByVal nSize As Long) As Long
Private Declare PtrSafe Function CreateProcess Lib "kernel32" Alias "CreateProcessW" (ByVal lpApplicationName As LongPtr, ByVal lpCommandLine As LongPtr, ByVal lpProcessAttributes As LongPtr, ByVal lpThreadAttributes As LongPtr, ByVal bInheritHandles As Long, ByVal dwCreationFlags As Long, ByVal lpEnvironment As LongPtr, ByVal lpCurrentDirectory As LongPtr, lpStartupInfo As STARTUPINFO, lpProcessInformation As PROCESS_INFORMATION) As Long
Private Declare PtrSafe Function ReadFile Lib "kernel32" (ByVal hFile As LongPtr, lpBuffer As Any, ByVal nNumberOfBytesToRead As Long, lpNumberOfBytesRead As Long, ByVal lpOverlapped As LongPtr) As Long
Private Declare PtrSafe Function WriteFile Lib "kernel32" (ByVal hFile As LongPtr, lpBuffer As Any, ByVal nNumberOfBytesToWrite As Long, ByRef nNumberOfBytesWritten As Long, ByVal lpOverlapped As LongPtr) As Long
Private Declare PtrSafe Function FlushFileBuffers Lib "kernel32" (ByVal hFile As LongPtr) As Long
Private Declare PtrSafe Function CloseHandle Lib "kernel32" (ByVal hObject As LongPtr) As Long

Private Declare PtrSafe Function GetExitCodeProcess Lib "kernel32" (ByVal hProcess As LongPtr, ByRef lpExitCode As Long) As Long
Private Declare PtrSafe Function TerminateProcess Lib "kernel32" (ByVal hProcess As LongPtr, ByVal uExitCode As Long) As Long

Private Declare PtrSafe Function CreateNamedPipeW Lib "kernel32" (ByVal lpName As LongPtr, ByVal dwOpenMode As Long, ByVal dwPipeMode As Long, ByVal nMaxInstances As Long, ByVal nOutBufferSize As Long, ByVal nInBufferSize As Long, ByVal nDefaultTimeOut As Long, lpSecurityAttributes As Any) As LongPtr
Private Declare PtrSafe Function ConnectNamedPipe Lib "kernel32" (ByVal hNamedPipe As LongPtr, lpOverlapped As Any) As Long
Private Declare PtrSafe Function DisconnectNamedPipe Lib "kernel32" (ByVal hNamedPipe As LongPtr) As Long
Private Declare PtrSafe Function PeekNamedPipe Lib "kernel32" (ByVal hNamedPipe As LongPtr, lpBuffer As Any, ByVal nBufferSize As Long, ByRef lpBytesRead As Long, ByRef lpTotalBytesAvail As Long, ByRef lpBytesLeftThisMessage As Long) As Long

VBA 部分的基本内容是:

  1. 通过CreateNamedPipeW使用PIPE_ACCESS_OUTBOUND创建一个命名管道(或者两个,如果你想要输入和输出,一进一出)
  2. 通过CreateProcess生成一个侦听器进程(您的 Python 进程),以便您获取其 ID
  3. 发送命令时,通过GetExitCode检查进程是否处于活动状态,通过ConnectNamedPipe连接到管道,使用WriteFile写入管道,然后FlushFileBuffers ,通过CloseHandle释放文件句柄,然后使用DisconnectNamedPipe断开与管道的连接

在 Python 进程中,在一个循环中,通过open管道,读取并处理消息,然后通过open再次打开。 open应该停止,直到发送下一条消息。

如果您想使用返回消息来获取进度,请确保在 Python 运行缓慢或遇到错误时使用PeekNamedPipe不会停止您的 Access 应用程序。

您可能希望将所有这些包装在一个预先声明的自我修复 VBA 类中,以在 VBA 处于活动状态时保持您的 Python 程序处于活动状态,而不必等待 Python 启动/读取您的程序等,所有这些都是不平凡的东西。

最后,只使用本地 http 会简单得多,因为您可以使用预先存在的工具来发送和接收 http 请求。 但是可以在 VBA 和 Python/R/任何可以读取命名管道(= 读取文件)的编程语言之间进行直接 IPC。

不幸的是,这是我所能带给你的。 真的,重新考虑“不是网络服务器”。 如果进程在同一台机器上,则可以对网络服务器进行防火墙保护,使用网络服务器比命名管道有更多开销,但要容易得多。 我有点希望我走那条路。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM