Привет Хабр! Недавно я разработал алгоритм для логистики, и нужно было его куда-то пристроить. Помимо веб-сервиса решено было внедрить данный модуль в 1С, и тут появилось довольно много подводных камней.
Начнем с того, что сам алгоритм представлен в виде dll библиотеки, у которой одна точка входа, принимающая JSON строку как параметр, и отдающая 2 колбэка. Первый для отображения статуса выполнения, другой для получения результата. С web-сервисом все довольно просто, у питона есть замечательный пакет ctypes, достаточно подгрузить нужную библиотеку и указать точку входа.
Выглядит это примерно так:
Как можно заметить, точка входа не совсем читабельная. Чтобы найти данную строчку в скомпилировнанных данных, нужно открыть соответствующий файл с расширением .lib и применить утилиту objdump с параметром -D, в выводе легко можно найти нужный метод по названию.
Данное коверканье метода происходит из-за того, что компилятор манглит («mangle» — калечить) название всех точек входа, причем разные компиляторы «калечат» по разному. В примере указан метод полученный MinGW
В 1С все оказалось гораздо менее тривиально. Для подключения dll нужно, чтобы у нее был специальный интерфейс Native API, позволяющий зарегестрировать Внешнюю Компоненту. Все написал по примеру, но ничего не взлетало. Я подумал, что это из-за gcc. Все мои попытки поставить Visual Studio были провальны, то ничего не устанавливалось, то не хватало стандартных библиотек.
Уже засыпая мне в голову пришла гениальная гипотеза. Наверное данную проблему не могли не оставить питонисты, ведь на Питон разработно все, что вообще возможно. А-ля правило интернета 34, только по отношению к чудесному Python. И ведь я оказался прав!
Для python существует пакет win32com который позволяет регестрировать Python объекты, как COM объекты. Для меня это было какой то магией, ведь я даже не очень понимаю что такое COM объект, но знаю что он умеет в 1С.
Пакет pypiwin32 не нужно ставить с помощью pip, а скачать его установщик, т.к. почему-то объекты не регестрировались после установки pip'ом.
Разобравшись с небольшим примером, я взялся за разработку. Для начала нужно создать Объект с интерфейсом идентифицирующим COM-Объект в системе
и конечно опишем его регистрацию
Теперь при запуске данного скрипта в системе появится объект GtAlgoWrapper. Его вызов из 1С будет выглядеть вот так:
Таким образом, все попадающие в колбэки даные можно будет обработать. Единственное, что может еще остаться непонятным — как передать данные из dll в 1C:
Для успешной работы, в первую очередь требуется вызов python-скрипта, чтобы зарегистрировать класс GtAlgoWrapper, а затем уже можно смело запускать конфигурацию 1С.
Вот так просто можно связать dll библиотеку и 1C с помощью питона, не уползая в сильные дебри.
Всем Магии!
Начнем с того, что сам алгоритм представлен в виде dll библиотеки, у которой одна точка входа, принимающая JSON строку как параметр, и отдающая 2 колбэка. Первый для отображения статуса выполнения, другой для получения результата. С web-сервисом все довольно просто, у питона есть замечательный пакет ctypes, достаточно подгрузить нужную библиотеку и указать точку входа.
Выглядит это примерно так:
import ctypes def callback_recv(*args): print(args) lib = ctypes.cdll.LoadLibrary('test.dll') Callback = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p) my_func = getattr(lib, '_ZN7GtTools4testEPKcPFviS1_E') cb_func = Callback(callback_recv) my_func(ctypes.c_char_p('some data'), cb_func)
Как можно заметить, точка входа не совсем читабельная. Чтобы найти данную строчку в скомпилировнанных данных, нужно открыть соответствующий файл с расширением .lib и применить утилиту objdump с параметром -D, в выводе легко можно найти нужный метод по названию.
Данное коверканье метода происходит из-за того, что компилятор манглит («mangle» — калечить) название всех точек входа, причем разные компиляторы «калечат» по разному. В примере указан метод полученный MinGW
В 1С все оказалось гораздо менее тривиально. Для подключения dll нужно, чтобы у нее был специальный интерфейс Native API, позволяющий зарегестрировать Внешнюю Компоненту. Все написал по примеру, но ничего не взлетало. Я подумал, что это из-за gcc. Все мои попытки поставить Visual Studio были провальны, то ничего не устанавливалось, то не хватало стандартных библиотек.
Уже засыпая мне в голову пришла гениальная гипотеза. Наверное данную проблему не могли не оставить питонисты, ведь на Питон разработно все, что вообще возможно. А-ля правило интернета 34, только по отношению к чудесному Python. И ведь я оказался прав!
Для python существует пакет win32com который позволяет регестрировать Python объекты, как COM объекты. Для меня это было какой то магией, ведь я даже не очень понимаю что такое COM объект, но знаю что он умеет в 1С.
Пакет pypiwin32 не нужно ставить с помощью pip, а скачать его установщик, т.к. почему-то объекты не регестрировались после установки pip'ом.
Разобравшись с небольшим примером, я взялся за разработку. Для начала нужно создать Объект с интерфейсом идентифицирующим COM-Объект в системе
class GtAlgoWrapper(): # com spec _public_methods_ = ['solve','resultCallback', 'progressCallback',] # методы объекта _public_attrs_ = ['version',] # атрибуты объекта _readonly_attr_ = [] _reg_clsid_ = '{2234314F-F3F1-2341-5BA9-5FD1E58F1526}' # uuid объекта _reg_progid_= 'GtAlgoWrapper' # id объекта _reg_desc_ = 'COM Wrapper For GTAlgo' # описание объекта def __init__(self): self.version = '0.0.1' self.progressOuterCb = None # ... def solve(self, data): # ... return '' def resultCallback(self, obj): # ... return obj def progressCallback(self, obj): # в колбэк необходимо передавать 1С объект, в котором идет подключение # например ЭтотОбъект или ЭтаФорма if str(type(obj)) == "<type 'PyIDispatch'>": com_obj = win32com.client.Dispatch(obj) try: # сохраним функцию из 1С (progressCallback) в отдельную переменную self.progressOuterCb = com_obj.progressCallback1C; except AttributeError: raise Exception('"progressCallback" не найден в переданном объекте') return obj
и конечно опишем его регистрацию
def main(): import win32com.server.register win32com.server.register.UseCommandLine(GtAlgoWrapper) print('registred') if __name__ == '__main__': main()
Теперь при запуске данного скрипта в системе появится объект GtAlgoWrapper. Его вызов из 1С будет выглядеть вот так:
Функция progressCallback1C(знач, тип) Экспорт Сообщить("значение = " + знач); Сообщить("тип = " + тип); КонецФункции //... Процедура Кнопка1Нажатие(Элемент) //Создадим объект ГТАлго = Новый COMОбъект("GtAlgoWrapper"); //Установим колбэки ГТАлго.progressCalback(ЭтотОбъект); //... Данные = ...; // JSON строка ГТАлго.solve(Данные); КонецПроцедуры
Таким образом, все попадающие в колбэки даные можно будет обработать. Единственное, что может еще остаться непонятным — как передать данные из dll в 1C:
_dependencies = ['libwinpthread-1.dll', 'libgcc_s_dw2-1.dll', # ..., 'GtRouting0-0-1.dll'] def solve(self, data): prefix_path = 'C:/release' # должны быть подключены все зависимые библиотеки try: for dep in self._dependencies: ctypes.cdll.LoadLibrary(os.path.join(prefix_path, dep)) # запоминаем библиотеку с нужной нам точкой входа lib = ctypes.cdll.LoadLibrary(os.path.join(prefix_path, 'GtAlgo0-0-1.dll')) except WindowsError: raise Exception('cant load' + dep) solve_func = getattr(lib, '_ZN6GtAlgo5solveEPKcPFviS1_ES3_') # создаем колбэки StatusCallback = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p) ResultCallback = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p) scb_func = StatusCallback(self.progressOuterCb) rcb_func = ResultCallback(self.resultOuterCb) # колбэки 1C превратились в функции которые мы передадим в DLL. Magic! if self.resultOuterCb is None: raise Exception('resultCallback function is not Set') if self.progressOuterCb is None: raise Exception('progressCallback function is not Set') # запустим алгоритм solve_func(ctypes.c_char_p(data), scb_func, rcb_func)
Для успешной работы, в первую очередь требуется вызов python-скрипта, чтобы зарегистрировать класс GtAlgoWrapper, а затем уже можно смело запускать конфигурацию 1С.
Вот так просто можно связать dll библиотеку и 1C с помощью питона, не уползая в сильные дебри.
Всем Магии!
Полезные ссылки
docs.python.org/3/library/ctypes.html — Пакет ctypes
citforum.ru/book/cook/dll0.shtml — Динамические библиотеки для чайников
habrahabr.ru/post/191014 — NativeAPI
infostart.ru/public/115486 — COM объект на C++
infostart.ru/public/190166 — COM объект на Python
pastebin.com/EFLnnrfp — Полный код скрипта на Python из статьи
citforum.ru/book/cook/dll0.shtml — Динамические библиотеки для чайников
habrahabr.ru/post/191014 — NativeAPI
infostart.ru/public/115486 — COM объект на C++
infostart.ru/public/190166 — COM объект на Python
pastebin.com/EFLnnrfp — Полный код скрипта на Python из статьи
