Details of the methods that python and C call each other

  • 2020-06-12 09:49:49
  • OfStack

preface

Recently, due to the need of work, I was considering making a data synchronization protocol based on udp for online game combat. In order to test the data in the early stage, I decided to make an external agent tunnel first. The principle is to establish network forwarding proxy on server and client respectively, that is, the original C/S connection is changed to the fast data transmission between two proxy. Because udp library is the code written by C++, when testing the data, it needs to constantly modify parameters, recompile, modify output statistics tabulations, etc., which is very annoying, and finally decides that the export interface is called logically by python script. Without further ado, let's start with a detailed introduction:

C/C++ There are several ways to export to python. According to different requirements, the following ways can be used:

1. ctypes binding. ctypes is included in the universal python Standard library module, which can load dynamic link libraries (dll, so) at runtime and is supported on both CPython 2.ES28en / 3.x and PyPy. The advantage of this approach is that instead of writing export functions specifically to python api, the symbol table of the dynamic link library can be loaded directly, which can be called directly in python.

2. python binding of the third party. Examples are ES39en-ES40en, the implementation is the tool automation using Python/C api to generate 1 series C++ wrapper functions. Especially suitable for large libraries or engines exported to python.

3. Manually write the python binding function. If you are familiar with Python C api, this approach should be the most flexible and can be used after reading the API document once. In theory, efficiency should be the best, but for me as a beginner to python, it can take quite a bit of time.

Based on the previous experience of exporting C function to Lua script, I thought I had to study 1 python c api first, and then work on it for half a day. Later it was found that ctypes of python standard library module was already very powerful. Although the performance should be the worst among the three ways, the loss of C/Python interface boundary call was ignored in the tunnel of the highest 60fps. Unlike the other two approaches, ctypes USES a non-invasive method to call the interface, without modifying the original C interface or writing binding code, and calls directly to the compiled dynamic library. ctypes is also very pleasant to use.

The following is the use of ctypes:

1. Load DLL dynamic link library

Note that the dynamic link library functions are loaded using cdecl or windll, respectively, using the calling convention of cdecl or windll.

Such as:


#  loading udp Library function  
udp_server = cdll.LoadLibrary("./udp_server.so") 
init_udp_server = udp_server.init_udp_server 
destroy_udp_server = udp_server.destroy_udp_server 
update_udp_server = udp_server.update_udp_server 
SendMsg = udp_server.SendMsg 

SetConnectCallback = udp_server.SetConnectCallback 
SetDisconnectCallback = udp_server.SetDisconnectCallback 
SetTimeoutCallback = udp_server.SetTimeoutCallback 
SetRecvCallback = udp_server.SetRecvCallback

2. Data type mapping

In addition to the basic data types defined by ctypes (c_char, c_int, c_double, etc.), you can also use the pointer function to convert to pointer types. For the network library to be exported, it is necessary to set the callback function. In C++ library, the callback function is completed by setting 1 function pointer. ctypes also supports the declaration of function pointer. Such as: recv_cb = CFUNCTYPE( None, c_char_p, c_int ) , represents a callback function of type char* and int with a return value of void.


def __init__(self, port, ip="127.0.0.1"): 
  self._port = port 
  self._ip = ip 

  self._clients = {} 

  self.c_connect_cb = connect_cb(self.server_connect) 
  self.c_disconnect_cb = disconnect_cb(self.server_disconnect) 
  self.c_timeout_cb = timeout_cb(self.server_timeout) 
  self.c_recv_cb = recv_cb(self.server_recv) 

def create(self): 
  if self._port: 
   if init_udp_server(self._ip, self._port) == 0: 
    print "server listen %s:%d" % (self._ip, self._port) 
    SetConnectCallback( self.c_connect_cb ) 
    SetDisconnectCallback( self.c_disconnect_cb ) 
    SetTimeoutCallback( self.c_timeout_cb ) 
    SetRecvCallback( self.c_recv_cb ) 
    return True 
  print "[error] init_udp_server error", self._ip, self._port 
  return False

Binding callback parameters Note that the binding callback function needs to be saved as a member variable (as described above) to avoid python garbage collection causing the callback function to become a wild pointer. It's a little bit of a hole. Basically, a small library USES these functions.

conclusion


Related articles: