Разработка COM сервера: Python в системном программировании
Создание COM сервера на Python
COM (Component Object Model) технология Microsoft, позволяющая компонентам взаимодействовать независимо от языка программирования. Python может выступать в роли сервера COM, предоставляя свои объекты клиентам (VBScript, C++, .NET, Excel). В статье рассматривается несколько подходов с акцентом на практическую реализацию.
Основное решение: библиотека pywin32 (модуль win32com.server). Это наиболее стабильный и документированный способ. Сервер регистрируется в реестре Windows и может быть вызван из любого COM клиента.
Как создать простой COM сервер с одним методом?
Определяется класс Python с атрибутами _public_methods_ (список имен методов, доступных клиенту), _reg_progid_ (программный идентификатор), _reg_clsid_ (GUID). После этого объект регистрируется функцией win32com.server.register.UseCommandLine или вручную с помощью win32com.server.register.RegisterServer.
import pythoncom
import win32com.server.register
class SimpleCalc:
_public_methods_ = ['Add', 'Multiply']
_reg_progid_ = 'PythonDemo.SimpleCalc'
_reg_clsid_ = '{B8F2B3A0-5C9E-11EC-9A0A-0800200C9A66}' # сгенерировать новый GUID
def Add(self, a, b):
return a + b
def Multiply(self, a, b):
return a * b
if __name__ == '__main__':
win32com.server.register.UseCommandLine(SimpleCalc)Com сервер на python (com сервер на python)
После запуска скрипта с аргументом --register COM сервер регистрируется. Клиент может создать экземпляр через CreateObject("PythonDemo.SimpleCalc").
Типичные проблемы: регистрация требует прав администратора. Ошибка "access denied" при попытке записи в реестр. Решение: запускать скрипт от имени администратора. Еще одна ошибка "CoInitialize has not been called" – вызывается pythoncom.CoInitialize() в главном потоке.
Некорректный GUID – если дважды используется один и тот же CLSID, возникает конфликт. Решение: генерировать новый GUID для каждого сервера (например, с помощью uuid.uuid4()).
Как зарегистрировать сервер в качестве COM DLL (in-process) и EXE (out-of-process)?
По умолчанию pywin32 регистрирует сервер как DLL (in-process). Чтобы создать out-of-process (EXE), требуется дополнительная обёртка. Класс наследуется от win32com.server.util.NewObject или используется win32com.server.dispatcher.Dispatcher. Ниже пример для EXE.
import sys
import pythoncom
import win32com.server.register
class CalcExe:
_public_methods_ = ['Add']
_reg_progid_ = 'PythonDemo.CalcExe'
_reg_clsid_ = '{C1D2E3F4-5A6B-7C8D-9E0F-1A2B3C4D5E6F}'
def Add(self, x, y):
return x + y
if __name__ == '__main__':
import win32com.server.dispatcher
win32com.server.register.RegisterServer(CalcExe)
pythoncom.CoInitialize()
win32com.server.dispatcher.Run()После регистрации запустите скрипт как сервер (он будет висеть в процессах). Клиент подключается через GetObject или CreateObject.
Ошибка "Class not registered" – сервер не зарегистрирован или указан неверный CLSID. Решение: проверить реестр под HKEY_CLASSES_ROOT\CLSID.
При запуске EXE сервера могут возникнуть проблемы с потоковой моделью (STA, MTA). По умолчанию используется pythoncom.COINIT_APARTMENTTHREADED, что требует для каждого потока отдельной инициализации.
Вариант: использование comtypes для создания COM сервера
Библиотека comtypes ориентирована на работу с существующими COM интерфейсами, но также позволяет создавать серверы. Требуется больше ручной работы с указателями и интерфейсами. Подходит для продвинутых разработчиков.
from comtypes.server import comtypes
import comtypes
class MyServer:
@comtypes.server.interface.IUnknown
def QueryInterface(self, riid, ppvObject):
pass
# ...Однако полноценный сервер с IDispatch проще реализовать через pywin32. comtypes чаще используется для создания COM клиентов или для низкоуровневой работы.
Сложность отладки, недостаток примеров – основная проблема.
Вариант: ручная регистрация сервера с помощью regedit и ctypes
Иногда требуется встроить COM сервер в приложение без внешних библиотек (кроме ctypes). Технически возможно, но объём кода велик. Не рекомендуется для реальных проектов, но может быть познавательным.
import ctypes
import uuid
# Определение класса и методов вручную, регистрация в реестре
# Кроме ctypes потребуется манипуляции с GUID и таблицами vtblПодход используется при встраивании Python в C++ приложения.
В каждом варианте важно учитывать потоковую модель, права доступа и корректную обработку ссылок (AddRef/Release). Для большинства сценариев автоматизация через pywin32 является оптимальной.
Расширенные примеры и сценарии
Пример 1: COM сервер с событиями (ConnectionPoints)
События позволяют серверу уведомлять клиента (например, при изменении данных). Реализация требует создания объекта источника событий.
import pythoncom
import win32com.server.util
from win32com.server import connect
class EventSource:
_public_methods_ = ['FireEvent']
_reg_progid_ = 'PythonDemo.EventServer'
_reg_clsid_ = '{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}'
_reg_desc_ = 'Python Event Server'
_connect_to_ = None
def __init__(self):
self.__event_set = set()
def FireEvent(self, value):
# уведомление всех подключённых клиентов
for sink in self.__event_set:
sink.OnChange(value)
def Advise(self, sink):
self.__event_set.add(sink)
return 0
def Unadvise(self, sink):
self.__event_set.discard(sink)
if __name__ == '__main__':
win32com.server.register.UseCommandLine(EventSource)Результат: сервер зарегистрирован. Клиент на VBScript может подписаться на событие OnChange.
Пример 2: COM сервер с двойным интерфейсом (Dual Interface)
Поддержка IDispatch и пользовательского интерфейса одновременно. Требуется описание метода в _public_methods_ и указание _typelib_guid_.
class DualCalc:
_public_methods_ = ['Add', 'Subtract']
_reg_progid_ = 'PythonDemo.DualCalc'
_reg_clsid_ = '{12345678-1234-1234-1234-123456789ABC}'
_typelib_guid_ = '{87654321-4321-4321-4321-CBA987654321}'
_reg_typelib_name_ = 'DualCalcLib'
def Add(self, a, b):
return a + b
def Subtract(self, a, b):
return a - bПосле регистрации создается библиотека типов, позволяющая раннее связывание.
Пример 3: Многопоточный COM сервер (MTA)
По умолчанию Python COM сервер работает в STA (Single-Threaded Apartment). Чтобы обеспечить многопоточность, используется pythoncom.COINIT_MULTITHREADED и специальный диспетчер.
import pythoncom
import win32com.server.dispatcher
class MultiThreadServer:
_public_methods_ = ['LongOp']
_reg_progid_ = 'PythonDemo.MultiThread'
_reg_clsid_ = '{DEADBEEF-0000-0000-0000-000000000001}'
def LongOp(self, n):
import time
time.sleep(n)
return n
if __name__ == '__main__':
pythoncom.CoInitialize(pythoncom.COINIT_MULTITHREADED)
win32com.server.dispatcher.Run()При этом вызовы могут приходить из разных потоков, но сам Python GIL сохраняется.
Пример 4: Использование makepy для создания .py файла из библиотеки типов
Не для сервера, а для клиента. Однако может пригодиться при создании сервера с известным интерфейсом.
# В командной строке:
python -m win32com.client.makepy "PythonDemo.DualCalc"Создаётся файл с именем библиотеки, содержащий классы для раннего связывания.
Пример 5: Отладка COM сервера с использованием print и лог-файла
Поскольку сервер может быть запущен в отдельном процессе, вывод на консоль не виден. Рекомендуется использовать логирование.
import logging
logging.basicConfig(filename='com_server.log', level=logging.DEBUG)
class DebugServer:
_public_methods_ = ['Echo']
def Echo(self, msg):
logging.debug(f'Echo called with {msg}')
return msgПри каждом вызове в лог пишется сообщение.
Данные примеры покрывают основные сценарии использования COM сервера на Python: от простых методов до событий и многопоточности.