自定义python框架_使用Python的自定义COM接口

{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}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值