<jupyter_start><jupyter_text>Working with Foreign Functions Suppose your Python library needs to load some sensitive binary data from a file into a contiguous block of memory (*e.g.*, in order to use it for some application-specific operation). Furthermore, you have some additional requirements that must be met for security and auditing purposes:* you need to ensure that your code does not inadvertently cause the interpreter to copy any part of the data loaded from the file into some other region of memory,* you need to log the memory address at which the data was stored, and* you need to clear the memory region that held the data by overwriting it with random bytes.One strategy you might employ in order maintain tight control over what your code is doing is to use C functions found in a compiled shared library to read the data from disk, to load that data into a region of memory, and at the end to clear that region. What minimal collection of built-in Python features will you need to invoke functions that are found in a shared library? How can you transform Python values (such as strings representing the location of the file) into an appropriate form on which the function can operate?  Python offers a rich set of capabilities via the built-in [ctypes](https://docs.python.org/3/library/ctypes.html) library that make it possible to invoke (or wrap in a Python function) foreign functions that have been implemented using another language (such as C/C++) and compiled into shared libraries. This article reviews the basics of employing foreign functions by demonstrating how to load and apply to the above use case the instance of the [GNU C Library](https://www.gnu.org/software/libc/) available on most operating systems. The same techniques can be used for any shared library. An alternative approach used by some popular Python packages is briefly reviewed, as well.  Loading a Shared Library To load a shared library file for which you know the relative or absolute path, you can normally use the [`LoadLibrary`](https://docs.python.org/3/library/ctypes.htmlloading-dynamic-link-libraries) method of either the `cdll` or the `windll` instance (depending on your operating system) of the [`LibraryLoader`](https://docs.python.org/3/library/ctypes.htmlctypes.LibraryLoader) class found in [ctypes](https://docs.python.org/3/library/ctypes.html). For the purposes of the use case in this article, it is sufficient to load the GNU C Library. In the example below, the [`system`](https://docs.python.org/3/library/platform.htmlplatform.system) function from the [platform](https://docs.python.org/3/library/platform.html) library is used to distinguish between Windows and Linux/macOS environments. In the Linux/macOS case, the [`find_library`](https://docs.python.org/3/library/ctypes.htmlfinding-shared-libraries) function is used to determine the absolute path of the shared library.<jupyter_code>import ctypes
import platform

if platform.system() == "Windows":
    libc = ctypes.windll.msvcrt
else:
    libc = ctypes.cdll.LoadLibrary(ctypes.util.find_library("c"))<jupyter_output><empty_output><jupyter_text>Invoking Foreign Functions The first portion of your workflow involves loading a file into memory. The Python code below writes a file to disk that contains a sequence of 32 random bytes. The file can be used to test the workflow.<jupyter_code>from secrets import token_bytes
with open("data.txt", "wb") as file:
    file.write(token_bytes(32))<jupyter_output><empty_output><jupyter_text>The C function [`fopen`](https://www.gnu.org/software/libc/manual/html_node/Opening-Streams.html) expects two arguments: a pointer to the first character of a string that represents the path of the file, and a pointer to the first character of the string that represents the mode (*i.e.*, reading or writing) in which the file is opened. You can use the [`c_char_p`](https://docs.python.org/3/library/ctypes.htmlctypes.c_char_p) function to turn Python strings into a representation in memory that can be handled by the C function. Note the use of the [`encode`](https://docs.python.org/3/library/stdtypes.htmlstr.encode) string method to provide an explicit encoding for the string as a byte sequence.<jupyter_code>from ctypes import c_char_p
file = c_char_p("data.txt".encode("ascii"))
mode = c_char_p("rb".encode("ascii"))<jupyter_output><empty_output><jupyter_text>Unfortunately, it is not possible within Python to examine the `libc` object that was created by the [`LibraryLoader`](https://docs.python.org/3/library/ctypes.htmlctypes.LibraryLoader) instance in order to determine what symbols are defined within it. However, in this case we know that the functions `fopen`, `fread`, and `fclose` must exist. For each of these functions, an instance of the [`FuncPtr`](https://docs.python.org/3/library/ctypes.htmlctypes._FuncPtr) class can be found in `libc`.<jupyter_code>fopen = libc.fopen
fread = libc.fread
fclose = libc.fclose<jupyter_output><empty_output><jupyter_text>Before you can safely invoke these functions, you need to [specify their argument types](https://docs.python.org/3/library/ctypes.htmlspecifying-the-required-argument-types-function-prototypes) and their [return types](https://docs.python.org/3/library/ctypes.htmlreturn-types). This can be accomplished by first consulting the [GNU C Library](https://www.gnu.org/software/libc/) documentation to find the signature for each of the C functions you would like to use. Then, the appropriate [data type classes](https://docs.python.org/3/library/ctypes.htmlfundamental-data-types) can be used to assign the correct sequence of argument types and the correct return type to the `argtypes` and `restype` attributes, respectively, of each `FuncPtr` class instance.<jupyter_code>from ctypes import c_int, c_size_t, c_void_p

fopen.argtypes = [c_char_p, c_char_p]
fopen.restype = c_void_p

fread.argtypes = [c_void_p, c_size_t, c_size_t, c_void_p]
fread.restype = c_size_t

fclose.argtypes = [c_void_p]
fclose.restype = c_int<jupyter_output><empty_output><jupyter_text>It is now possible to invoke these functions on some inputs. You can allocate a memory buffer for the 32 bytes of data that you will be loading from the file using the [`create_string_buffer`](https://docs.python.org/3/library/ctypes.htmlctypes.create_string_buffer) function.<jupyter_code>from ctypes import create_string_buffer
data = ctypes.create_string_buffer(32)<jupyter_output><empty_output><jupyter_text>You can now open the file, load the data, and close the file.<jupyter_code>fp = fopen(file, mode)
fread(data, 32, 1, fp)
fclose(fp)
bytes(data).hex()<jupyter_output><empty_output><jupyter_text>You can determine the memory address corresponding to the memory buffer `data` using the [`addressof`](https://docs.python.org/3/library/ctypes.htmlctypes.addressof) function.<jupyter_code>from ctypes import addressof
hex(addressof(data))<jupyter_output><empty_output><jupyter_text>You can now clear the memory region. The example below uses the `memset` C function for this purpose. An example that uses a random sequence generator that is appropriate for cryptographic applications appears in the next section.<jupyter_code>libc.memset.argtypes = [c_void_p, c_int, c_size_t]
libc.memset(data, 0, 32)
bytes(data).hex()<jupyter_output><empty_output><jupyter_text>Alternative Approaches The [C Foreign Function Interface](https://cffi.readthedocs.io/) library is similar to the built-in ctypes module and is used by some popular packages, including the [PyNaCl](https://pypi.org/project/PyNaCl/) library that acts as a Python interface for the cryptographic library [libsodium](https://doc.libsodium.org/). In the example below, the C implementation of the [`randombytes`](https://libsodium.gitbook.io/doc/generating_random_data) function is invoked on a character buffer `bs` and then the contents of that buffer are displayed.<jupyter_code>from nacl import _sodium
lib = _sodium.lib

from cffi import FFI
ffi = FFI()

bs = ffi.new("unsigned char[]", 8)
lib.randombytes(bs, 8)
bytes(bs).hex()<jupyter_output><empty_output><jupyter_text>You might choose to call the C implementation directly for a variety of reasons, including to improve performance. This may be useful to do when a more high-level library method allocates new memory for a byte sequence during every invocation, while your own solution can reuse the same memory over and over to store each new batch of bytes. In the example below, the time to invoke the C function over one million iterations is measured.<jupyter_code>import time
start = time.perf_counter()
for _ in range(10**6):
    lib.randombytes(bs, 8)
time.perf_counter() - start<jupyter_output><empty_output><jupyter_text>The below example measures the amount of time it takes to invoke the Python wrapper in PyNaCl over the same number of iterations. The longer running time may be the result of a number of factors; regardless of the underlying reason that may apply for any particular function, the example demonstrates that direct access to the C method gives you more control over those factors.<jupyter_code>from nacl.bindings import randombytes
start = time.perf_counter()
for _ in range(10**6):
    bs = randombytes(8)
print(time.perf_counter() - start)<jupyter_output>6.109586499980651
{'fraction_non_alphanumeric': 0.08095596942620303, 'fraction_numerical': 0.006028636021100226, 'mean_word_length': 5.5056022408963585, 'pattern_counts': {'":': 1, '<': 48, '<?xml version=': 0, '>': 48, 'https://': 23, 'lorem ipsum': 0, 'www.': 3, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '12', 'dir': 'jupyter-structured-clean-dedup', 'id': '280247', 'license': 'MIT', 'n_tokens_mistral': 2622, 'n_tokens_neox': 2557, 'n_words': 1087, 'path': 'working-with-foreign-functions.ipynb', 'repo_name': 'python-supply/working-with-foreign-functions', 'size': '9289'}