|
|||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Contents
IntroductionThere are a lot of tutorials on Python and COM over the Internet, but in real practice, you might quickly be confused just going beyond standard First, I tried to use the standard pythoncom module, but it turned out that it didn't support custom COM interfaces. Then, I downloaded the comtypes package and started playing with it. Due to a lack of documentation, it took me about one night to write a simple example. So, here is a step-by-step guide on how to begin using comtypes. Writing a COM objectWe will write a COM component where we'll use the basic techniques for Python-COM interoperability and some thread related tricks. The component we are creating in this tutorial exposes the interface 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); }; First, create a Visual Studio project. Select the 'ATL Project' template, give a name to your project, and click OK. On the 'Application Settings' page, set the server type to 'Executable (EXE)' and click Finish.
Switch to class view, select your newly created project, and in the context menu, select Add -> Class... Select 'ATL Simple Object' and click 'Add'. Give a short name 'TaskLauncher' and leave all the other fields, click 'Next'. On the 'Options' page, set the threading model to 'Free', set the interface to 'Custom', and check the 'Automation compatible' checkbox. Also check the 'Connection points' to add events support to your class. Click 'Finish' to create the class.
Important note: When creating a custom interface object, you should check the 'Automation compatible' check box. Otherwise, script languages won't access your interface. However, you can always set this attribute named In the class view, locate the Locate Now, our source interface that declares the events is ready, but we need Visual Studio to implement the functions that actually fire the events. To do this, locate the
Build the project to ensure that there are no errors at this stage. Now, we are ready to actually implement the component methods. Open the TaskLauncher.h file and add the following definition at the end: struct TaskInfo
{
BSTR name;
TaskInfo(BSTR taskName)
{
//copy taskName to name
UINT len = ::SysStringLen(taskName);
name = ::SysAllocStringLen(taskName, len);
}
~TaskInfo()
{
::SysFreeString(name);
}
};
Locate the 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;
}
Now, it seems a bit complicated, but we will need the Writing a COM client in PythonFirst, we need to know the GUID of our type library. Open the Visual Studio generated TaskServer.idl, and locate the block of code shown on the picture below. Copy the contents of the
Open the PythonWin IDE, create a new Python script, replacing 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
Here, we generate the Run this script. Your output should be like this: # 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
Inter-thread interface marshallingNow, it's time to modify our COM server to make it more asynchronous. The So, in the class view, find the Modify the struct TaskInfo
{
BSTR name;
LPSTREAM marshalledInterface;
TaskInfo(BSTR taskName)
{
//copy taskName to name
UINT len = ::SysStringLen(taskName);
name = ::SysAllocStringLen(taskName, len);
}
~TaskInfo()
{
::SysFreeString(name);
}
};
Locate the 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;
}
Insert the 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;
}
Finally, locate the STDMETHODIMP CTaskLauncher::Fire_TaskCompletedInternal(BSTR name)
{
Fire_TaskCompleted(name);
return S_OK;
}
Rebuild the solution and try to run the Python client again. The output should look like this: # 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
Checklist
Download code for this article
Solution, project, and source files for Visual C++ 2005 Express Edition. Python script source. References
|
||||||||||||||||||||||||||||||||||||||||