📜 ⬆️ ⬇️

Integration of 1C with DLL using Python

Hi Habr! Recently, I developed an algorithm for logistics, and I had to attach it somewhere. In addition to the web service, it was decided to implement this module in 1C, and there appeared quite a few pitfalls.

Let's start with the fact that the algorithm itself is represented as a dll of the library, which has one entry point that accepts a JSON string as a parameter and gives 2 callbacks. The first to display the status of execution, the other to get the result. Everything is quite simple with the web service, python has a wonderful ctypes package, just load the necessary library and specify the entry point.

It looks 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 quite 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; you can easily find the desired method by its name in the output.

This method of distorting is due to the fact that the mangliter compiler (“mangle” - cripple) the name of all entry points, and the different compilers “cripple” in different ways. In the example, the method obtained is the MinGW

In 1C, everything turned out to be much less trivial. To connect the dll, you need to have a special Native API that allows you to register the External Component. I wrote everything by example, but nothing took off. I thought it was because of 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, this problem could not but be left by Pythonists, because everything that is possible at all is developed on Python. A la rule of the Internet 34, only in relation to wonderful Python. And because I was 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 even really understand what a COM object is, but I know that it can do 1C.

The pypiwin32 package does not need to be installed using pip, but rather to download its installer, since 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 that identifies 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)) == "<type 'PyIDispatch'>": 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 you run this script, 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 the data entering the callbacks can be processed. The only thing that can 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, you first need to call the python script to register the GtAlgoWrapper class, and then you can safely run the 1C configuration.

So just you can link the dll library and 1C using a python, without crawling into the wilds.
All Magic!

useful links
docs.python.org/3/library/ctypes.html - ctypes package
citforum.ru/book/cook/dll0.shtml - Dynamic Libraries for Dummies
habrahabr.ru/post/191014 - NativeAPI
infostart.ru/public/115486 - COM object in C ++
infostart.ru/public/190166 - COM object on Python
pastebin.com/EFLnnrfp - Full Python Script Code from Article

Source: https://habr.com/ru/post/332082/


All Articles