python ctypes exploration ---- the interaction between python and c

Reprint: Click to open the link

In recent days, I have used python to interact with c/c++ programs. There are recommended swigs on the Internet, but the results are not satisfactory, so I pondered the ctypes module of python. At the same time, although there is content in this regard on the Internet, it still feels unclear. It is recorded here as a backup, and it is also convenient for the vast number of python with c/c++ dispatches. If you think my writing is not good, you can refer to the introduction of ctypes in the official documentation , there may not be what you want.


1. Introduction

The main purpose of the interaction between python and c/c++ is for speed, and the second is probably for scripting.

It is said that python interacts with c/c++, but it is actually python interacting with c, because python itself only supports C API. But we can achieve the purpose of python and c++ project collaboration through adjustment. The following mainly describes the main points and difficulties of python using the ctypes module to interact with c.

 

 

2. What can be done using ctypes?

Python can call c functions by using the ctypes module, which must include variable types (including structure types, pointer types) that can define c.

The official definition is " ctypes is a foreign function library for Python. It provides C compatible data types, and allows calling functions in DLLs or shared libraries. It can be used to wrap these libraries in pure Python." - Quoted from Python 3.5 chm documentation. The gist of it is - ctypes is an external function library for Python. It provides C-compatible data types and allows calling functions in a DLL or shared library. Through it, these function libraries can be wrapped in pure Python (so you can directly import xxx to use these function libraries).

 

Word of mouth is useless, we need a concrete example, let's introduce a cpp file to illustrate all the following issues:

The existing test.cpp file is as follows:

copy code
#if 1
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif

#include <stdio.h>
#include <stdlib.h>

// Point structure 
struct Point
{
    float x, y;
};

static Point* position = NULL;

extern "C" {

    DLL_API int add(int a, int b)
    {
        return a + b;
    }

    DLL_API float addf(float a, float b)
    {
        return a + b;
    }

    DLL_API void print_point(Point* p)
    {
        if (p)
            printf("position x %f y %f", p->x, p->y);
    }
}
copy code

You can see that there are three functions, including a function with a pointer to the formal parameter. Learning to successfully call the above three functions in Python is the goal of my article. For the windows platform, just generate it as a dll file (other platforms are .so). Below we write the Python code for testing in the interpreter.

If you don't understand the above cpp file, then take a look at other articles about dll first:

1.  Analysis and compilation of Dll (1) http://www.cnblogs.com/hicjiajia/archive/2010/08/27/1809997.html

2.  Analysis of the usage of extern "C" http://www.cnblogs.com/rollenholt/archive/2012/03/20/2409046.html

 

 

3. How does ctypes call the function library of c?

First, ctypes is required to load the library of functions that need to be called (nonsense).

Use ctypes.CDLL, which is defined as follows (quoted from the Python 3.5 chm documentation)

ctypes.CDLL(namemode=DEFAULT_MODEhandle=Noneuse_errno=Falseuse_last_error=False)

Also, the modes parameter is ignored on Windows platforms. For the windows platform, you can also call ctypes.WinDLL, which is almost the same as the above CDLL, the only difference is that it assumes that the functions in the library follow the Windows stdcall calling convention, and the meaning of other parameters is shown in the official documentation.

If you want to call the add function in test.dll, you can write:

>>> from ctypes import *
>>> dll = CDLL("test.dll")   #call test.dll 
>>>dll.add(10, 30) #call    add function 
40

It can be seen that 40 is returned, is it very simple? . This is what we expected. Next, we call addf again, which is the float version of add. Some people may ask why not write DLL_API float add(float a, float b) directly? Just use function overloading, why not? Note that we use extern "C" to declare the function, so overloading of functions is not supported.

Next we call addf and guess what?

>>> dll.addf(10, 30)
9108284

Oh, isn't this a bit of a surprise to you? Why is this happening?

 

 

Fourth, c type and Python type, parameter type, return type

The reason why the call to the addf function "fails" is not a problem with Python. The reason is that you didn't "tell" Python what the function "looks like" (more formally, the "description")—the function's parameter types and return type. So why did our call to add succeed? Because the parameter type and return type of Python default function are int. Of course Python thinks that addf returns a value of type int.

That is to say, when ctypes reads the dll, it only knows that the function exists, but it does not know the parameter type and return value type of the function. You might be wondering why Python is so troublesome to tell it the "look" of the functions in the shared library. This can't blame it, in fact, the C# language developed by Microsoft needs to tell C# what this function looks like when calling the dll. This is a bit tedious to explain, so let's focus on our study of ctypes usage.

So, what are the types of c for Python? The following is a table of types in Python corresponding to c types (screenshot from Python 3.5 chm documentation)

c_ype-to-python_type

Then, how do you tell Python the type of the parameters of a foreign function and the type of the returned value?

This requires assigning values ​​to the two attributes restype and argtypes of the function. They correspond to the return type and parameter type, respectively. For addf, its return value type is float, which corresponds to c_float in Python. Next we assign it:

>>> dll.addf.restype = c_float # The type of the return value of addf is flare

If the return value of the function is void then you can assign it to None. Also, in versions that are not too old, Python built-in types (the rightmost column in the table above) can be used to "describe" the return types of library functions, however, Python built-in types cannot be used to describe the parameters of library functions.

Since the parameters of the function are not a fixed number, you need to use a list or a tuple to explain:

>>> dll.addf.argtypes = (c_float, c_float) # addf has two parameters, both of type float 
or something like this, but, you know, it's slightly more efficient to find tuples :)
 >>> dll .addf.argtypes = [c_float, c_float] # addf has two parameters, both of type float

All that needs to be done, now call addf again:

>>> dll.addf(8, 3)
11.0
>>> dll.addf(8.3, 3.1)
11.399999618530273

This is the result we want.

 

 

5. More about the creation and use of ctypes types

We can also create a ctypes type (c_int, c_float, c_char...) and assign it a value, for example:

copy code
>>> i = c_int(45) #Define                         an int variable with a value of 45 
>>> i.value #Print                                the value of the variable
45
>>> i.value = 56 #Change                          the value of the variable to 56 
>>> i.value #Print                                the new value of the variable 
56
copy code

That's right, you assign a value to a ctypes type through the ctypes  value attribute -- assigning the value of a Python built-in type.

Other ctypes functions, such as sizeof(i) (doesn't feel very intimate like c), will not be introduced one by one. Please refer to the third article of the document and the official document.

 

 

Six, structure, common body

This is one of the necessary components to call the print_point library function.

If you want to define a c-type structure in Python, you need to define a class, such as Structu Point:

>>> class Point(Structure):
...     _fields_ = [("x", c_float), ("y", c_float)]
...
>>>

This is defined. There are two main points:

1. The class must inherit from ctypes.Structure

2. Describe the "look" of the structure

The first point is simple, class XXX(Structure) is OK.

To do the second point, you must define a property named _fields_ in the custom c structure class and assign it to a list as above.

Then it can be used like this:

>>> p = Point(2,5) #Define           a variable of type Point, the initial value is x=2, y=5 You can also write p = Point() 
>>> py = 3 #Modify                  the value 
>>> print (px, py)         # print variable 
2 3

As for the union, as long as the class inherits from ctypes.Union, the others are the same as above.

 

 

7. Pointer

This is the last section, although it is a pointer, but don't be nervous, and listen to me.

How to create a ctypes pointer? Here are three functions in ctypes that have the same pointer as a pointer, and you will know them naturally if you master them (maybe pointer POINTER will be a bit confusing, just take a closer look).

function illustrate
byref(x [, offset]) Returns the address of x, which must be an instance of a ctypes type. Equivalent to &x of c. offset represents the offset.
pointer(x) Creates and returns a pointer instance to x, where x is an instance object.
POINTER(type) Returns a type that is a pointer type to type type, which is a type of ctypes.

Byref is well understood. This is used when passing parameters. It is also possible to create a pointer variable with pointer, but byref is faster.

The difference between pointer and POINTER is that pointer returns an instance and POINTER returns a type. Even you can use POINTER to do pointer work:

copy code
>>> a = c_int(66)          #Create an instance of c_int 
>>> b = pointer(a)         #Create a pointer 
>>> c = POINTER(c_int)(a) #Create a pointer >>> 
b <
 __main__ .LP_c_long object at 0x00E12AD0>
>>> c
<__main__.LP_c_long object at 0x00E12B20>
>>> b.contents #Output             the value of a 
c_long(66 )
 >>> c.contents #Output             the value of a 
c_long(66)
copy code

The pointer created by pointer does not seem to have a way to modify the value of the ctypes type it points to.

All that has to be said, the next step is to call the print_point function:

copy code
>>> dll.print_point.argtypes = (POINTER(Point),)    #Specify the parameter type of the function 
>>> dll.print_point.restype = None #Specify                  the return type of the function
>>>
>>> p = Point(32.4, -92.1) #Instantiate       a Point 
>>> dll.print_point(byref(p))    #Call the function 
position x 32.400002 y -92.099998>>>
copy code

Of course you have to use a slower pointer:

>>> dll.print_point(pointer(p))   #call function 
position x 32.400002 y -92.099998>>>
The result is what we want :)

As for why there is a malformed "y -92.099998>>>" in the back of the output, go to the above c code and you will know.

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325730767&siteId=291194637