在脚本语言中使用 COM 对象 - 第二部分(Python)
本文演示了如何在 Python 中实例化 COM 对象并使用其方法和属性。
引言
在本系列的*第一部分*文章 TkCOMApplication 中,我们已经了解了如何在 Tcl/Tk 中实例化和使用 COM 对象。在本文中,我想向您展示如何在强大的面向对象脚本语言 Python 中使用它。本文假定读者已经对 COM 有了深刻的理解。本文也不能完全用于理解 Python。期望读者能通过其他可用资源掌握 Python。本文仅用于演示在 Python 中使用 COM 对象。在展示如何在 Python 中实例化 COM 组件并使用接口之前,我想对 Python 本身做一个简要介绍。
背景
当您需要花几分钟时间构建一个操作一些文件的简短脚本时,Python 是一个非常方便的工具。此外,它对于更大的项目也很有用,因为您可以获得数据结构、模块化、面向对象、单元测试、性能分析以及海量 API 的全部功能。
Python 几乎与一切事物都有连接。您拥有非常高级的string
和正则表达式处理能力,拥有线程、网络(内置许多协议)、压缩、加密,您可以使用 Tcl/Tk 构建 GUI,而这仅仅是内置功能中的一小部分。
如果您环顾四周,您会惊讶地发现互联网上有多少应用程序和库提供了 Python 绑定:MySQL、ImageMagick、SVN、Qt、libXML 等等。
有一些应用程序通过 Python 提供插件接口,例如 Blender 和 GIMP。
更进一步,您甚至可以使用 Python/C API 使用 C 或 C++ 为 Python 编写扩展模块,或者反过来,您可以将 Python 解释器用作您原生应用程序的一个模块,也就是说,您可以将 Python 嵌入到您的软件中。我的文章 Python_Embedded_Dialog 展示了如何使用 Python 解释器解析数学表达式,并在 C/C++ 的 GUI 编程中利用其结果。
然而,与 Tcl/Tk 不同,Python 并没有内置 GUI 支持。它通过为现有工具包编写的扩展来支持 GUI 编程,例如 Tk (TkInter)、Qt (PyQt)、GTK+ (PyGtk)、Fltk (PyFltk)、wxWidgets (wxPython) —— 这些是一些流行的工具包。TkInter 是 Python 默认打包的实现。
Python 可用于大多数平台:例如 Linux、Solaris、Windows、Mac、AIX、BeOS、OS/2、DOS、QNX 或 PlayStation。
设置您的编程环境
要在 Windows 上使用 Python,请从 http://www.activestate.com 下载安装程序。
安装程序将安装一个 Python 解释器 shell 和一个名为 PythonWin editor 的实用编辑器。
我们还需要安装 comtypes 扩展,它将为 Python 添加 COM 支持。此扩展仅适用于 Windows 平台。您可以从 http://sourceforge.net/projects/comtypes/files/ 下载相应版本的 comtypes。
入门
由于本文无法过多解释 Python 的理论、语法等内容,让我们立即开始实践。让我们来看一个典型的 Python "Hello World" 程序:
启动 Python 解释器
输入以下行,然后按回车键。
print "Hello World!"
解释器将输出语句 "Hello world!
"
现在让我们看看 Python 解释器作为数学计算器的强大功能。
输入以下各行,每行后按回车键,然后查看结果。
from math import*
x = sin(pi/4)
print x
x = 10.0/(12.5*1.25)*log(2)
print x
pow(12, 2)
现在我们来看一个小类。注意,Python 是一个高度依赖缩进的语言。正确的缩进是分隔代码块的唯一方法。
输入以下各行,每行后按回车键。为了清晰起见,我将“__”符号显示为一个缩进级别。
class Point:
__x = 100
__y = 200
__def setCoord(self, x, y):
____self.x = x
____self.y = y
__def printCoord(self)
____print "X %0.3f Y %0.3f" % (float(self.x), float(self.y))
类的定义到此结束。最后再按一次回车键以恢复提示符。
现在让我们实例化一个类的对象并使用其方法,输入以下各行,每行后按回车键,然后查看结果。
pnt = Point()
pnt.printCoord()
pnt.setCoord(10, 20)
pnt.printCoord()
所有这些都可以放入一个扩展名为.py
的文件中,并通过在资源管理器中双击来执行。对于 GUI 应用程序,使用扩展名.pyw
将只显示 GUI 窗口,而不会显示控制台窗口。希望您现在已经对 Python 语言有了一些了解。让我们看看如何使用 TkInter 创建一个类似于本系列第一部分中看到的 GUI,然后在SimpleCOM
库中实例化一个 COM 对象并使用其方法。
在本文中,我们有一个名为SimpleCOM
的 COM 库(源代码和 VS2008 项目包含在下载文件中),它有一个名为GraphicPoint
的对象。GraphicPoint
实现了三个接口,所有接口都派生自IDispatch
以支持脚本......
IPoint
- 方法 --
SetCoord
、GetCoord
和Distance
- 属性 --
X
、Y
和Z
IGraphicPoint
- 方法 --
Draw
IColor
- 方法 --
SetColor
和GetColor
(OLE_COLOR
)
我们将实例化 2 个GraphicPoint
对象,获取它们的IPoint
接口,并使用SetCoord
方法设置Points
的坐标。坐标将通过我们使用 Python TkInter 开发的 GUI 从用户输入中获取。我们还将通过获取IColor
接口来设置这两个点的颜色。在这里,我们将看到如何将从颜色对话框获得的 RGB 分量转换为OLE_COLOR
。然后,我们将通过调用IPoint
接口的Distance
方法来计算两个点之间的距离。我们还将通过弹出显示每个点坐标和颜色的消息框来模拟点的绘制。为此,我们将调用IGraphicPoint
接口的Draw
方法。当从 Python 代码实例化点时,我们还将弹出一个messagebox
显示我们设置的坐标。为此,我们将调用点对象的X
、Y
和Z
属性。所有这些都将涵盖实例化 COM 对象、查询相应接口以及使用方法和属性的活动。
脚本以必要的系统相关、TkInter 和 COM 相关扩展导入开始。
import sys
# for TkInter GUI support
from Tkinter import *
import tkMessageBox
import tkColorChooser
# for COM support
import comtypes.client as cc
import comtypes
# Load the typelibrary registered with the Windows registry
tlb_id = comtypes.GUID("{FA3BF2A2-7220-47ED-8F07-D154B65AA031}")
cc.GetModule((tlb_id, 1, 0))
# Alternately you can use this method also by direct loading the dll file
#cc.GetModule("SimpleCOM.dll")
# Import the SimpleCOMLib library from comtypes
import comtypes.gen.SimpleCOMLib as SimpleCOMLib
现在是应用程序 GUI 的类定义
# Application class for the GUI
class AppDlg:
# member variables
X1 = 0.0
Y1 = 0.0
Z1 = 0.0
X2 = 0.0
Y2 = 0.0
Z2 = 0.0
distVal = 0.0
# methods
# constructor
def __init__(self, master):
master.title("Test COM InterOp in Python/Tkinter")
master.maxsize(400, 210)
master.minsize(400, 210)
frame1 = Frame(master, padx=5, pady=5)
frame1.pack(anchor=N, side="top", fill=X, expand=Y)
# Point 1 Data
self.labelframe1 = LabelFrame
(frame1, padx=5, pady=5, relief="groove", text="Point 1 Data")
self.labelframe1.pack(side=LEFT, fill=BOTH, expand=Y)
self.frameX1 = Frame(self.labelframe1)
self.frameY1 = Frame(self.labelframe1)
self.frameZ1 = Frame(self.labelframe1)
self.frameX1.pack()
self.frameY1.pack()
self.frameZ1.pack()
self.labelX1 = Label(self.frameX1, text="X")
self.labelX1.pack(side=LEFT, padx=2, pady=2)
self.entryX1 = Entry(self.frameX1)
self.entryX1.insert(0, self.X1)
self.entryX1.pack()
...
...
<code skipped for brevity>
...
# variable to store colors
self.colorTuple1 = ((255, 255, 255), '#ffffff')
self.colorTuple2 = ((255, 255, 255), '#ffffff')
# Apply button callback
def onApply(self):
self.X1 = self.entryX1.get()
self.Y1 = self.entryY1.get()
self.Z1 = self.entryZ1.get()
self.X2 = self.entryX2.get()
self.Y2 = self.entryY2.get()
self.Z2 = self.entryZ2.get()
#print self.colorTuple1
#print self.colorTuple2
# Check if the user selects cancel on the color chooser
# in that case the tuple will contain None, None values
if self.colorTuple1[0] is None:
r = 255
g = 255
b = 255
else:
r = self.colorTuple1[0][0]
g = self.colorTuple1[0][1]
b = self.colorTuple1[0][2]
self.color1 = (((0xff & b) << 16) | ((0xff & g) << 8) | (0xff & r))
#print "Color Point1 is %d\n" % self.color1
if self.colorTuple2[0] is None:
r = 255
g = 255
b = 255
else:
r = self.colorTuple2[0][0]
g = self.colorTuple2[0][1]
b = self.colorTuple2[0][2]
self.color2 = (((0xff & b) << 16) | ((0xff & g) << 8) | (0xff & r))
#print "Color Point2 is %d\n" % self.color2
# Create COM Point1
self.aGrPoint = cc.CreateObject
("SimpleCOM.GraphicPoint", None, None, SimpleCOMLib.IGraphicPoint)
self.aPoint = self.aGrPoint.QueryInterface(SimpleCOMLib.IPoint)
#help(self.aPoint)
self.aPoint.SetCoord(float(self.X1), float(self.Y1), float(self.Z1))
tkMessageBox.showinfo("From Python-Tkinter App",
"Point 2 Created At X%0.3f, Y%0.3f, Z%0.3f"\
% (float(self.aPoint.X), float(self.aPoint.Y), float(self.aPoint.Z)))
self.aColor = self.aGrPoint.QueryInterface(SimpleCOMLib.IColor)
if self.colorTuple1:
self.aColor.SetColor(self.color1)
self.aGrPoint.Draw()
# Create COM Point2
self.aGrPoint2 = cc.CreateObject("SimpleCOM.GraphicPoint",
None, None, SimpleCOMLib.IGraphicPoint)
self.aPoint2 = self.aGrPoint2.QueryInterface(SimpleCOMLib.IPoint)
#help(self.aPoint2)
self.aPoint2.SetCoord(float(self.X2), float(self.Y2), float(self.Z2))
tkMessageBox.showinfo("From Python-Tkinter App",
"Point 2 Created At X%0.3f, Y%0.3f, Z%0.3f"\
% (float(self.aPoint2.X), float(self.aPoint2.Y), float(self.aPoint2.Z)))
self.aColor2 = self.aGrPoint2.QueryInterface(SimpleCOMLib.IColor)
if self.colorTuple2:
self.aColor2.SetColor(self.color2)
self.aGrPoint2.Draw()
self.distVal = self.aPoint.Distance(self.aPoint2)
self.entryDist.delete(0, END)
self.entryDist.insert(0, self.distVal)
# Color selection button callbacks
def onSelectColor1(self):
# tkColorChooser returns a tuple containing the RGB tuple
and the html color value
self.colorTuple1 = tkColorChooser.askcolor()
if self.colorTuple1:
self.colorbtn1.configure(bg=self.colorTuple1[1])
def onSelectColor2(self):
self.colorTuple2 = tkColorChooser.askcolor()
if self.colorTuple2:
self.colorbtn2.configure(bg=self.colorTuple2[1])
# Start the TkInter root window
rootwin = Tk()
# Instantiate the GUI class object
AppDlg(rootwin)
# Run the TkInter main event loop
rootwin.mainloop()
颜色选择按钮的回调函数仅显示一个颜色选择器对话框,并将选定的颜色存储在一个全局变量中,该变量由“应用”按钮的回调过程访问。颜色存储为 RGB 的 3 个值的列表。我们获取每个元素,并使用以下代码将其转换为OLE_COLOR
等效项。
# Check if the user selects cancel on the color chooser
# in that case the tuple will contain None, None values
if self.colorTuple1[0] is None:
r = 255
g = 255
b = 255
else:
r = self.colorTuple1[0][0]
g = self.colorTuple1[0][1]
b = self.colorTuple1[0][2]
# code for converting RGB values into OLE_COLOR
self.color1 = (((0xff & b) << 16) | ((0xff & g) << 8) | (0xff & r))
要创建 COM 类的对象,请使用comtypes.client
方法CreateObject
,要访问接口,请在接口变量上调用QueryInterface
方法。例如:
self.aGrPoint = cc.CreateObject("SimpleCOM.GraphicPoint",
None, None, SimpleCOMLib.IGraphicPoint)
self.aPoint = self.aGrPoint.QueryInterface(SimpleCOMLib.IPoint)
self.aPoint.SetCoord(float(self.X1), float(self.Y1), float(self.Z1))
tkMessageBox.showinfo("From Python-Tkinter App",
"Point 2 Created At X%0.3f, Y%0.3f, Z%0.3f"\
% (float(self.aPoint.X), float(self.aPoint.Y), float(self.aPoint.Z)))
self.aColor = self.aGrPoint.QueryInterface(SimpleCOMLib.IColor)
if self.colorTuple1:
self.aColor.SetColor(self.color1)
self.aGrPoint.Draw()
希望您喜欢这篇文章。本演示的 Python 脚本文件和 COM VS2008 项目文件包含在可下载的 zip 文件中。Windows 7 用户需要以管理员模式运行 Visual Studio 来构建项目并注册 COM DLL。
关注点
COM 的强大功能和IDispatch
再次得到证明。它为从 RAD 工具和脚本访问组件提供了巨大的灵活性。Python 拥有许多用于开发稳健灵活应用程序的扩展,例如数值和科学应用程序、游戏、CAD 和图形。
参考文献
- https://docs.pythonlang.cn/tutorial/index.html
- http://en.wikipedia.org/wiki/Python_(programming_language)
- https://zetcode.cn/tutorials/pyqt4/firstprograms/
- http://tkinter.unpythonic.net/wiki/Examples
- http://www.pygtk.org/tutorial.html
- http://starship.python.net/crew/theller/comtypes/
历史
- 2010 年 4 月 18 日:首次发布