I want to create a COM server to expose scipy to VBA code, as performing integration is kinda difficult and I don't know of any good VBA/COM libraries for this. My objectives are:
- As portable as possible
- Fast
- Maintainable
Here's the python:
# Requires C:\Users\...\anaconda3\Library\bin on the PATH
from scipy import integrate
from win32com.client import Dispatch
from typing import Callable
import comtypes
class ScipyWrapper:
# pythoncom.CreateGuid()
_reg_clsid_ = "{A621AB64-9378-4B54-A7D3-20711C0B72DB}"
_reg_desc_ = "Python Scipy COM Server"
_reg_progid_ = "PythonCOM.ScipyWrapper"
_reg_clsctx_ = comtypes.CLSCTX_LOCAL_SERVER # don't want inproc because conda not supported on 64 bit
# see https://stackoverflow.com/q/67462529/6609896
_public_methods_ = ["quad",]
# _public_attrs_ = []
# _readonly_attrs_ = []
def quad(self, vbFunc, a: float, b: float) -> float:
vb_dispatch = Dispatch(vbFunc)
func: Callable[[float], float] = vb_dispatch.Evaluate # Callable[..., float]?
return integrate.quad(func, a, b)[0] # only return val, not uncertainty
if __name__ == "__main__":
import win32com.server.register
win32com.server.register.UseCommandLine(ScipyWrapper)
Called from VBA (twinBasic) like:
Module Main
Public Sub doStuff()
Dim integrator as Object = CreateObject("PythonCOM.ScipyWrapper")
Dim func as IOneDimensionalFunction = New XSquared 'this is what we'll integrate
Debug.Print integrator.quad(func, 0., 1.) 'integrate x^2 from 0->1, returns 0.333333...
End Sub
End Module
'@Description("Interface for a f(x) -> y function, with optional parameters")
Class IOneDimensionalFunction
Public Function Evaluate(ByVal x As Double, ParamArray parameters() as Variant) As Double
End Function
End Class
Class XSquared
Implements IOneDimensionalFunction
Private Function IOneDimensionalFunction_Evaluate(ByVal x As Double, ParamArray parameters() as Variant) As Double
Return x^2
End Function
End Class
Questions:
- Not sure how to Type the python,
vbFuncis a PyIDispatch instance but not sure what that means for typing/mypy. - I'd like to use strong typing to protect my python, right now it's duck typed and I could easily pass an Interface without the Evaluate method, or anything else for that matter.
- No idea how to handle python errors and turn them into COM errors.
- Is this ref-count safe?
OFC any other tips on general style, use of comments and naming etc. are welcome :)