杰瑞科技汇

python ctypes linux

Of course! Here is a comprehensive guide to using ctypes on Linux, covering everything from basic concepts to practical examples and best practices.

python ctypes linux-图1
(图片来源网络,侵删)

What is ctypes?

ctypes is a foreign function library for Python. It allows you to call functions in shared libraries (like .so files on Linux) directly from your Python code. This is incredibly powerful for:

  • Interfacing with system libraries (e.g., libc).
  • Using performance-critical C libraries without writing a Python extension.
  • Interacting with hardware via kernel modules or system calls.
  • Wrapping legacy C code.

The Core Concepts on Linux

On Linux, shared libraries are typically named lib<name>.so.<version> (e.g., libc.so.6, libssl.so.1.1). ctypes knows how to find these libraries using standard Linux dynamic linker paths (/lib, /usr/lib, etc.).

a. Loading a Library

You load a library using ctypes.CDLL().

import ctypes
# Load the standard C library (libc.so.6)
# This is the most common library you'll interact with.
libc = ctypes.CDLL("libc.so.6") 

You can also use ctypes.util.find_library() to locate a library automatically.

python ctypes linux-图2
(图片来源网络,侵删)
import ctypes.util
# Find the full path to the OpenSSL library
libssl_path = ctypes.util.find_library("ssl")
if libssl_path:
    print(f"Found OpenSSL at: {libssl_path}")
    libssl = ctypes.CDLL(libssl_path)
else:
    print("Could not find libssl")

b. Calling a Function

Once a library is loaded, you can access its functions as attributes.

# Access the 'strlen' function from libc
strlen_func = libc.strlen
# The function is now a Python object. Let's call it.
# But first, we need to prepare the arguments...

c. Data Types and Arguments

This is the most critical part. You must tell ctypes about the C data types your function expects and returns. ctypes provides a mapping to C types:

C Type ctypes Type Python Representation (usually)
char c_char A single 1-byte character
int c_int A Python int
long c_long A Python int
float c_float A Python float
double c_double A Python float
char * c_char_p A Python bytes or str object
void * c_void_p An integer representing a memory address
void None None

Passing Arguments:

You need to "wrap" your Python arguments in the correct ctypes type.

python ctypes linux-图3
(图片来源网络,侵删)
# Let's call strlen("hello")
# The function expects a 'char *', which in ctypes is c_char_p
my_string = "hello"
# Python strings are Unicode. We need to encode them to bytes for C.
c_string = my_string.encode('utf-8') 
# Now, pass the c_string to the function
length = strlen_func(c_string)
print(f"The length of '{my_string}' is: {length}") # Output: The length of 'hello' is: 5

d. Specifying Function Prototypes (Best Practice)

The above works, but it's not safe. If you pass the wrong type of argument, you'll get a segfault. The correct way is to declare the function's signature before calling it. This tells ctypes what arguments to expect and what it returns.

The argtypes attribute is a list of expected argument types. The restype attribute is the expected return type.

import ctypes
libc = ctypes.CDLL("libc.so.6")
# Declare the prototype for strlen
libc.strlen.argtypes = [ctypes.c_char_p]  # It takes a char *
libc.strlen.restype = ctypes.c_size_t     # It returns a size_t (like an unsigned long)
# Now, call it safely
my_string = "hello world"
c_string = my_string.encode('utf-8')
length = libc.strlen(c_string)
print(f"The length of '{my_string}' is: {length}") # Output: The length of 'hello world' is: 11

If you now try to pass an integer, ctypes will raise a TypeError before the C code is even executed, preventing a crash.


Practical Linux Examples

Example 1: Getting the Hostname

Let's call the gethostname() function from libc. It takes a char * buffer and an int for the buffer's length.

import ctypes
import ctypes.util
# Find and load libc
libc = ctypes.CDLL(ctypes.util.find_library("c"))
# Define the function prototype
libc.gethostname.argtypes = [ctypes.c_char_p, ctypes.c_int]
libc.gethostname.restype = ctypes.c_int  # Returns 0 on success, -1 on error
# Prepare the arguments
buffer_size = 256
# Create a mutable buffer of bytes. c_char_p is immutable.
hostname_buffer = ctypes.create_string_buffer(buffer_size)
# Call the function
result = libc.gethostname(hostname_buffer, buffer_size)
if result == 0:
    # The result is stored in our buffer. Decode it to a Python string.
    hostname = hostname_buffer.value.decode('utf-8')
    print(f"Hostname: {hostname}")
else:
    # An error occurred. We can get the error message from errno
    errno = ctypes.get_errno()
    print(f"Error getting hostname. errno: {errno}")

Example 2: Interacting with /proc (Reading System Info)

This example shows how to call C functions to read from a file, which is a common task on Linux.

import ctypes
import ctypes.util
libc = ctypes.CDLL(ctypes.util.find_library("c"))
# Prototypes for open, read, close
libc.open.argtypes = [ctypes.c_char_p, ctypes.c_int]
libc.open.restype = ctypes.c_int
libc.read.argtypes = [ctypes.c_int, ctypes.c_void_p, ctypes.c_size_t]
libc.read.restype = ctypes.c_ssize_t # read returns ssize_t
libc.close.argtypes = [ctypes.c_int]
libc.close.restype = ctypes.c_int
# --- Read /proc/version ---
file_path = b"/proc/version"
buffer_size = 1024
# Open the file for reading (O_RDONLY is defined in fcntl.h, but we can use the value 0)
O_RDONLY = 0
fd = libc.open(file_path, O_RDONLY)
if fd == -1:
    print("Failed to open /proc/version")
else:
    # Create a buffer to hold the data
    read_buffer = ctypes.create_string_buffer(buffer_size)
    # Read from the file descriptor
    bytes_read = libc.read(fd, read_buffer, buffer_size)
    if bytes_read == -1:
        print("Failed to read from file")
    else:
        # The .value attribute gives the bytes read
        version_info = read_buffer.value.decode('utf-8').strip()
        print(f"Kernel Version Info:\n{version_info}")
    # Close the file descriptor
    libc.close(fd)

Example 3: Calling a Function in a Custom Shared Library

This is a very common use case. Let's say you have a C file mylib.c.

mylib.c

#include <stdio.h>
int add_numbers(int a, int b) {
    return a + b;
}
void greet(const char* name) {
    printf("Hello, %s!\n", name);
}

Compile it into a shared library:

gcc -shared -fPIC -o libmylib.so mylib.c
# -shared: Create a shared library
# -fPIC: Position-Independent Code (required for shared libraries)
# -o libmylib.so: Output file name

Create a Python script to use it: Make sure libmylib.so is in your current directory or in a library path like /usr/local/lib.

use_mylib.py

import ctypes
import os
# Load our custom library
# The 'lib' prefix and '.so' suffix are often optional, but good practice.
try:
    mylib = ctypes.CDLL("./libmylib.so")
except OSError as e:
    print(f"Error loading library: {e}")
    print("Make sure libmylib.so is in the same directory or in LD_LIBRARY_PATH.")
    exit()
# --- Call add_numbers ---
mylib.add_numbers.argtypes = [ctypes.c_int, ctypes.c_int]
mylib.add_numbers.restype = ctypes.c_int
sum_result = mylib.add_numbers(10, 25)
print(f"10 + 25 = {sum_result}")
# --- Call greet ---
mylib.greet.argtypes = [ctypes.c_char_p]
mylib.greet.restype = None # void function
name = "Pythonista"
mylib.greet(name.encode('utf-8'))

Run the Python script:

python3 use_mylib.py

Expected Output:

10 + 25 = 35
Hello, Pythonista!

Important Considerations and Pitfalls

  1. Memory Management is YOUR Responsibility: ctypes will not free memory allocated by C functions (e.g., malloc, strdup). If a C function returns a pointer to allocated memory, you are responsible for calling free() on it, or you will have a memory leak.

  2. Segfaults are Common: A mistake in a function signature (argtypes or restype) or passing a bad pointer is the most common cause of a segmentation fault. Your Python program will crash hard. Always define prototypes!

  3. String Encoding: C functions expect char * (null-terminated strings). Python 3 uses Unicode strings. You must encode them to bytes (e.g., using .encode('utf-8')) before passing them to C.

  4. Calling Conventions: On Linux/x86-64, the default is the System V AMD64 ABI, which is what ctypes uses. On other platforms or for special cases, you might need to specify the calling convention (e.g., libc.my_func = ctypes.CFUNCTYPE(...)), but this is rarely needed for standard libc functions.

  5. Passing Pointers to Modify Data: To pass a buffer for a C function to write into, use ctypes.create_string_buffer() for bytes or ctypes.create_array() for other types. The byref() function is used to get a pointer to your Python object.

    # Example: Passing a pointer to an int
    num = ctypes.c_int()
    pointer_to_num = ctypes.byref(num)
    # A hypothetical C function: void set_to_42(int *p);
    mylib.set_to_42.argtypes = [ctypes.POINTER(ctypes.c_int)]
    mylib.set_to_42(pointer_to_num)
    print(num.value) # Should print 42
分享:
扫描分享到社交APP
上一篇
下一篇