{A}目录{A2}{A3}{A4}{A5}{A6}{A7}{A8}{A9}简介
有很多关于Python和COM在互联网上的教程,但在现实实践中,你可能很快被混淆只是超出标准的IDispatch的东西。同样发生在我当我决定写单元测试,我们的COM组件。组件是相当简单的,他们实现一个自定义接口(从IUnknown派生)和事件传出IDispatch接口。
首先,我尝试使用标准的pythoncom模块,但事实证明,它不支持自定义COM接口。然后,我下载{A10}包,并开始与它玩耍。由于缺乏文件,花了大约一个晚上,我写一个简单的例子。所以,这里是一个关于如何开始使用comtypes一步一步的指导。{A11}编写一个COM对象
我们将写一个COM组件,我们将使用Python的COM互操作性和一些线程相关的技巧的基本技术。
我们在本教程中创建的组件公开的接口ITaskLauncher,并支持即将离任的接口_ITaskLauncherEvents。以下是从IDL文件的摘录:interface ITaskLauncher : IUnknown{
[id(1), helpstring("method StartTask")] HRESULT StartTask([in] BSTR name);
};
dispinterface _ITaskLauncherEvents
{
methods:
[id(1), helpstring("method TaskQueued")] HRESULT TaskQueued([in] BSTR name);
[id(2), helpstring("method TaskCompleted")] HRESULT TaskCompleted([in] BSTR name);
};
首先,创建一个Visual Studio项目。选择"ATL项目"模板,您的项目名称,并单击确定。在"应用程序设置"页,设置服务器类型"可执行(EXE)",并单击Finish。
{S0}
切换到类视图,选择新创建的项目,在上下文菜单中,选择Add - GT;类...选择"ATL简单对象",然后点击"添加"。给一个简短的名称"TaskLauncher,"离开所有其他领域,点击"下一步"。在"选项"页面中,设置的"自由"的线程模型,"自定义"设置界面,并检查"自动化兼容"复选框。另外,请检查"连接点"添加到您的类的事件支持。点击"完成"以创建类。
重要说明:创建一个自定义的界面对象时,你应该检查"自动化兼容"复选框。否则,脚本语言将不能访问你的界面。然而,你总是可以通过直接修改。idl文件名为oleautomation后来这个属性设置。
在类视图,找到ITaskLauncher接口,并选择Add - GT,添加方法...在上下文菜单中。这将打开"添加方法向导"。设置的方法的名称为"StartTask',检查在"属性",选择BSTR参数类型,设置参数的名称为"名",并单击"添加"。单击"完成"。在这一步,向导将创建一个StartTask方法,使一个空的函数体,在我们的CTaskLauncher类实现这个方法。
找到TaskServerLib类视图,展开它,并找到_ITaskLauncherEvents接口。在上下文菜单中,选择"添加"- GT; Add方法...离开返回类型的HRESULT,设置方法的名称为"TaskQueued",并添加"名"参数,像我们前面所做的那样。点击"完成"。重复这个"TaskCompleted"具有相同参数的方法。
现在,我们的源接口声明的事件已准备就绪,但我们需要Visual Studio实现的功能,实际火灾事件。要做到这一点,在类视图中找到CTaskLauncher类并选择Add - GT;添加连接点...在上下文菜单中。在出现的对话框中,双击"_ITaskLauncherEvents接口并单击Finish。
{S2}
生成项目,以确保在这个阶段,有没有错误。现在,我们已经准备好,实际执行的组件方法。
打开TaskLauncher.h文件,并在末尾添加下面的定义:{C}
找到TaskLauncher.cpp StartTask功能,并添加以下实施:STDMETHODIMP CTaskLauncher::StartTask(BSTR name)
{
TaskInfo* pTaskInfo = new TaskInfo(name);
BSTR taskName = ::SysAllocStringLen(pTaskInfo->name,
::SysStringLen(pTaskInfo->name));
Fire_TaskQueued(taskName);
delete pTaskInfo;
return S_OK;
}
现在,似乎有点复杂,但我们会需要TaskInfo结构。现在,它的时间来建立我们的组成部分,并开始编写客户端代码。{A12}在Python编写COM客户端
首先,我们需要知道我们的类型库的GUID。打开Visual Studio生成TaskServer.idl的,并找到下面的图片上显示的代码块。复制uuid属性的内容。
{S3}
打开的PythonWin的IDE,创建一个新的Python脚本,取代由Visual Studio为您生成的GUID comtypes.GUID(... ...)。import comtypes.client as cc
import comtypes
tlb_id = comtypes.GUID("{3DED0EFB-21ED-4337-B098-1B8316952FFA}")
cc.GetModule((tlb_id, 1, 0))
import comtypes.gen.TaskServerLib as TaskLib
class Sink:
def TaskQueued(self, this, name):
print "TaskQueued event. name = %s" % name
def TaskCompleted(self, this, name):
print "TaskCompleted event. name = %s" % name
task_launcher = cc.CreateObject("TaskServer.TaskLauncher",
None, None, TaskLib.ITaskLauncher)
sink = Sink()
advise = cc.GetEvents(task_launcher, sink)
task_launcher.StartTask("first task")
cc.PumpEvents(5)
advise = None
task_launcher = None
在这里,我们呼吁GetModule产生TaskServerLib模块,类型库的GUID,主要版本的库(1),和次要版本(0)作为参数传递。下一步,我们声明类的接收器将接收从我们的对象的事件。 cc.CreateObject创建一个COM对象,并从它获得ITaskLauncher的接口。在这一点上,我们可以调用对象的方法,但接收事件,我们需要一些额外的设置。创建一个接收器类的实例,并调用cc.GetEvents绑定task_launcher源接口往下沉。 GetEvents返回的建议的连接,这是一个好主意,让参考。否则,建议连接可以被垃圾收集和事件将停止工作。接下来,我们呼吁我们的方法StartTask,并等待5秒在PumpEvents循环的事件。
运行此脚本。您的输出应该是这样的:# Generating comtypes.gen._3DED0EFB_21ED_4337_B098_1B8316952FFA_0_1_0
# Generating comtypes.gen._00020430_0000_0000_C000_000000000046_0_2_0
# Generating comtypes.gen.stdole
# Generating comtypes.gen.TaskServerLib
TaskQueued event. name = first task{A13}线程间的接口编组
现在,它的时间来修改我们的COM服务器,以使更多的异步。 StartTask方法火灾后,立即被称为TaskQueued事件。让我们添加一个工作线程将等待几秒钟的时间和火灾TaskCompleted事件。 Visual Studio中生成我们Fire_TaskCompleted的代理功能,但它是相当无用的,可以直接从我们的工作者线程调用,因为它不从辅助线程,主线程接口编组。我想有没有优雅的解决方案,以克服在ATL这个问题。我们可以修改的CProxy_ITaskLauncherEvents:Fire_TaskCompleted功能,并通过自己做所有编组,但在这种情况下,我们将无法生成此文件,如果我们的界面的变化。另一种方法是引入Fire_TaskCompletedInternal的方法我们ITaskLauncher接口,并传递给辅助线程封送处理的接口指针的ITaskLauncher接口。作为Fire_TaskCompletedInternal方法是不应该被称为COM客户端直接的,我们将使它隐藏的,但它仍将ITaskLauncher虚函数表。
因此,在类视图,找到ITaskLauncher接口,添加- GT;添加方法...在上下文菜单中。作为Fire_TaskCompletedInternal填充方法的名称,添加"名"参数,[]和BSTR类型的方向。点击"下一步"按钮或"IDL属性"页。检查"隐藏"复选框,然后点击"完成"。
修改TaskInfo结构,并添加LPSTREAM marshalledInterface成员变量。struct TaskInfo
{
BSTR name;
LPSTREAM marshalledInterface;
TaskInfo(BSTR taskName)
{
//copy taskName to name
UINT len = ::SysStringLen(taskName);
name = ::SysAllocStringLen(taskName, len);
}
TaskInfo()
{
::SysFreeString(name);
}
};
找到的CTaskLauncher:StartTask方法,并用下面的代码替换它:STDMETHODIMP CTaskLauncher::StartTask(BSTR name)
{
TaskInfo* pTaskInfo = new TaskInfo(name);
BSTR taskName = ::SysAllocStringLen(pTaskInfo->name, ::SysStringLen(pTaskInfo->name));
Fire_TaskQueued(taskName);
CoMarshalInterThreadInterfaceInStream(IID_ITaskLauncher, (ITaskLauncher*)this,
&pTaskInfo->marshalledInterface);
if (_beginthreadex(NULL, 0, &threadFunc, (LPVOID)pTaskInfo, 0, NULL) == 0)
{
//clean up if we couldn't start the thread
pTaskInfo->marshalledInterface->Release();
delete pTaskInfo;
};
return S_OK;
}
前插入StartTask方法threadFunc函数定义:unsigned int __stdcall threadFunc(void* p)
{
CoInitializeEx(NULL, COINIT_MULTITHREADED);
Sleep(2000);
TaskInfo* pTaskInfo = (TaskInfo*)p;
ITaskLauncher* pTaskLauncher;
CoGetInterfaceAndReleaseStream(pTaskInfo->marshalledInterface,
IID_ITaskLauncher, (LPVOID*)&pTaskLauncher);
BSTR taskName = ::SysAllocStringLen(pTaskInfo->name, ::SysStringLen(pTaskInfo->name));
HRESULT hr = pTaskLauncher->Fire_TaskCompletedInternal(taskName);
delete pTaskInfo;
CoUninitialize();
return 0;
}
最后,找到了CTaskLauncher:Fire_TaskCompleteInternal方法的定义,使它看起来像这样:STDMETHODIMP CTaskLauncher::Fire_TaskCompletedInternal(BSTR name)
{
Fire_TaskCompleted(name);
return S_OK;
}
重新生成解决方案,并再次尝试运行的Python客户端。输出应该看起来像这样:# comtypes.gen._3DED0EFB_21ED_4337_B098_1B8316952FFA_0_1_0 must be regenerated
# Generating comtypes.gen._3DED0EFB_21ED_4337_B098_1B8316952FFA_0_1_0
# Generating comtypes.gen.TaskServerLib
TaskQueued event. name = first task
TaskCompleted event. name = first task{A14}核对表设置自定义接口的oleautomation属性。这是必要的脚本语言如Python中,VBA等,支持通过typelibs晚期绑定。之前曾经在每一个线程调用任何COM相关的功能或接口功能调用CoInitializeEx。不要忘记调用CoUninitialize线程结束前。你应该元帅线程之间的接口指针。见{A15}更多细节的文章。本文的下载代码{A16}{A17}
解决方案,项目,并为Visual C 2005 Express Edition的源文件。
{A18}
Python脚本源。{A19}参考{A20}{A21}{A22}