Integration of 1C with DLL using Python
Hi Habr! I recently developed an algorithm for logistics, and I needed to attach it somewhere. In addition to the web service, it was decided to implement this module in 1C, and then quite a few pitfalls appeared.
To begin with, the algorithm itself is presented in the form of a dll library, which has one entry point that takes a JSON string as a parameter and gives 2 callbacks. The first to display the status of execution, the other to get the result. With the web service, everything is quite simple, python has a wonderful ctypes package, it is enough to load the necessary library and specify the entry point.
It looks something like this:
As you can see, the entry point is not entirely readable. To find this line in the compiled data, you need to open the corresponding file with the .lib extension and use the objdump utility with the -D parameter, in the output you can easily find the desired method by name.
This distortion of the method is due to the fact that the compiler manglit ("mangle" - cripple) the name of all entry points, and different compilers "cripple" in different ways. The example shows the method obtained by MinGW
In 1C, everything turned out to be much less trivial. To connect the dll, it needs to have a special Native API that allows you to register the External Component. He wrote everything by example, but nothing took off. I thought this was due to gcc. All my attempts to install Visual Studio failed, then nothing was installed, then there were not enough standard libraries.
Already falling asleep, a brilliant hypothesis came to my mind. Probably the pythonists could not leave this problem, because on Python everything that is possible is developed at all. A la internet rule 34, only in relation to wonderful Python. And I turned out to be right!
For python, there is a win32com package that allows you to register Python objects as COM objects. For me it was some kind of magic, because I don’t really understand what a COM object is, but I know that it can do it in 1C.
The pypiwin32 package does not need to be installed using pip, but download its installer, because for some reason, the objects were not registered after installation by pip.
Having dealt with a small example, I took up the development. First you need to create an Object with an interface identifying a COM Object in the system
and of course we will describe his registration
Now, when this script is run, the GtAlgoWrapper object will appear in the system. His call from 1C will look like this:
Thus, all data falling into the callbacks can be processed. The only thing that may still remain incomprehensible is how to transfer data from dll to 1C:
For successful work, first of all, you need to call a python script to register the GtAlgoWrapper class, and then you can safely start the 1C configuration.
This is how simple it is to link a dll library and 1C using python without crawling into strong wilds.
All Magic!
To begin with, the algorithm itself is presented in the form of a dll library, which has one entry point that takes a JSON string as a parameter and gives 2 callbacks. The first to display the status of execution, the other to get the result. With the web service, everything is quite simple, python has a wonderful ctypes package, it is enough to load the necessary library and specify the entry point.
It looks something like this:
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)
As you can see, the entry point is not entirely readable. To find this line in the compiled data, you need to open the corresponding file with the .lib extension and use the objdump utility with the -D parameter, in the output you can easily find the desired method by name.
This distortion of the method is due to the fact that the compiler manglit ("mangle" - cripple) the name of all entry points, and different compilers "cripple" in different ways. The example shows the method obtained by MinGW
In 1C, everything turned out to be much less trivial. To connect the dll, it needs to have a special Native API that allows you to register the External Component. He wrote everything by example, but nothing took off. I thought this was due to gcc. All my attempts to install Visual Studio failed, then nothing was installed, then there were not enough standard libraries.
Already falling asleep, a brilliant hypothesis came to my mind. Probably the pythonists could not leave this problem, because on Python everything that is possible is developed at all. A la internet rule 34, only in relation to wonderful Python. And I turned out to be right!
For python, there is a win32com package that allows you to register Python objects as COM objects. For me it was some kind of magic, because I don’t really understand what a COM object is, but I know that it can do it in 1C.
The pypiwin32 package does not need to be installed using pip, but download its installer, because for some reason, the objects were not registered after installation by pip.
Having dealt with a small example, I took up the development. First you need to create an Object with an interface identifying a COM Object in the system
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)) == "":
com_obj = win32com.client.Dispatch(obj)
try:
# сохраним функцию из 1С (progressCallback) в отдельную переменную
self.progressOuterCb = com_obj.progressCallback1C;
except AttributeError:
raise Exception('"progressCallback" не найден в переданном объекте')
return obj
and of course we will describe his registration
def main():
import win32com.server.register
win32com.server.register.UseCommandLine(GtAlgoWrapper)
print('registred')
if __name__ == '__main__':
main()
Now, when this script is run, the GtAlgoWrapper object will appear in the system. His call from 1C will look like this:
Функция progressCallback1C(знач, тип) Экспорт
Сообщить("значение = " + знач);
Сообщить("тип = " + тип);
КонецФункции
//...
Процедура Кнопка1Нажатие(Элемент)
//Создадим объект
ГТАлго = Новый COMОбъект("GtAlgoWrapper");
//Установим колбэки
ГТАлго.progressCalback(ЭтотОбъект);
//...
Данные = ...; // JSON строка
ГТАлго.solve(Данные);
КонецПроцедуры
Thus, all data falling into the callbacks can be processed. The only thing that may still remain incomprehensible is how to transfer data from dll to 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)
For successful work, first of all, you need to call a python script to register the GtAlgoWrapper class, and then you can safely start the 1C configuration.
This is how simple it is to link a dll library and 1C using python without crawling into strong wilds.
All Magic!
useful links
docs.python.org/3/library/ctypes.html - Package ctypes
citforum.ru/book/cook/dll0.shtml - Dynamic library for dummies
habrahabr.ru/post/191014 - NativeAPI
infostart.ru/public/115486 - the COM object in C ++
infostart.ru/public/190166 - COM object in Python
pastebin.com/EFLnnrfp - Full Python script code from the article
citforum.ru/book/cook/dll0.shtml - Dynamic library for dummies
habrahabr.ru/post/191014 - NativeAPI
infostart.ru/public/115486 - the COM object in C ++
infostart.ru/public/190166 - COM object in Python
pastebin.com/EFLnnrfp - Full Python script code from the article