簡體   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