Capture

DLL Integration in Python: A Practical Guide

0 Comments

Introduction

DLL (Dynamic Link Library) integration in Python is required sometimes, especially when you’re dealing with specialized functionality or need to interact with lower-level system resources.

DLLs are used to expose functionality by many operating systems (such as Windows) and third-party software packages. You can utilize pre-existing code, frequently written in languages like C or C++, by integrating DLLs with Python. This eliminates the need to rebuild that functionality in Python.

I recently developed a Python script that integrated with a.dll file. In this blog post, I’ll walk you through the step-by-step instructions and troubleshooting procedure.

What are DLL files?

A DLL is a file with the ‘.dll’ extension that contains functions and routines that other programs can call and use in runtime. DLL files give programmers the ability to modularize their work. By loading and executing these files when needed, the application’s memory footprint is decreased.

Main Functions of DLL Files:

Code Reusability: This reduces redundancy by allowing applications to access a shared DLL rather than including the same code in each one. For instance, Windows offers DLLs that are utilized by all programs and are system-wide, such as kernel32.dll, which manages low-level system functions.


Efficient Memory Usage: DLLs provide efficient memory usage because they can be loaded into memory as needed. Instead of loading a separate copy of the DLL for every program, multiple programs can share a single instance of the file in memory.

Application Modularity: Software developers can divide their programs into discrete modules using DLLs, which enables bug fixes or updates to be applied to specific areas without impacting the system. For example, it is possible to update a DLL that manages graphics rendering without changing the program.


Third-Party Libraries: A lot of third-party libraries are available as DLLs, which let programmers incorporate sophisticated features (including networking, encryption, and image processing) into their apps without having to start from scratch.

Problems with DLL Versioning: DLL Hell:

Developers had to deal with DLL versioning problems in previous Windows versions, when an application may replace a system-wide DLL with an incompatible version, resulting in failures for other programs. The problem has been significantly reduced in contemporary Windows versions because of technologies like side-by-side (SxS) assembly and Windows File Protection.

Why is Python DLL integration necessary?

Some key reasons:

Enhancement of Performance: As compiled binary libraries, DLLs execute substantially more quickly than interpreted Python code. When it comes to real-time activities (such video processing or encryption) or high-performance computations, a DLL can greatly increase speed by executing the performance-critical components natively.

Utilizing Platform-Specific Features: Particularly on Windows, DLLs frequently grant access to platform-specific capabilities. DLLs, which offer an interface via which Python scripts can communicate with the operating system, are necessary for certain activities, such as system-level operations, hardware access, or interaction with other Windows applications.

Compatibility with Different Software: DLLs are used by many enterprise programs to expose APIs. These DLLs can be integrated by Python scripts to communicate with hardware, databases, and proprietary software that might not have direct Python bindings.

Integration of Legacy Code: It’s possible that you have DLLs that were created in the past in languages other than C or C++, such as legacy systems. Since it might not be possible to rewrite the code in Python, you can integrate the DLLs to keep utilizing the old code in Python applications.

Reusability of Code: Reusing an existing DLL library instead of writing the same logic in Python is more efficient when it’s already created (for instance, in a project whose components are shared across various programming languages).

DLL integration makes it possible for Python to work more effectively with native code libraries and systems, increase performance, and expand its capabilities.

Some Methods to load DLL in Python Code

  • Ctypes: A built in library that permits calling DLL or shared library functions.
  • cffi: A library that offers a more streamlined API than ctypes for loading DLLs and calling C code.
  • Pywin32: For DLL interactions unique to Windows.
  • cython: for creating bindings for C/C++ libraries.

I’ll walk you through using the ‘ctype’ library to integrate DLLs with Python in this blog post.

Python ‘ctype Module

The ctypes module in Python is a built-in library that allows you to interact with C-compatible shared libraries, such as DLLs on Windows or .so files on Unix-based systems. It offers tools for transferring data between Python and C and and directly calling methods in these libraries from Python. The ctypes module is a powerful tool for integrating Python with native libraries, enabling direct access to C functions, and optimizing performance when needed.

Key Functionality of ctypes:

  • Loading any external shared library or DLL and call its functions from Python.
  • Using ctypes, you can call C functions and pass parameters and get return values just as in a native C program.
  • To convert Python objects to C data types, ctypes offers a collection of C-compatible data types (c_int, c_char_p, c_double, etc.).
  • Complex data structures in shared libraries can be handled by using ctypes like C structs and unions.
  • Calling conventions such as cdecl (the default calling convention in C) and stdcall (used in Windows DLLs) are supported by ctypes.
  • It offers resources for working with arrays, pointers, and memory addresses, enabling in-depth engagement with native memory.

Benefits of ctype module:

  • No compilation is necessary: Ctypes does not require the compilation of extensions, in contrast to SWIG or Cython. DLLs can be used and loaded directly.
  • Cross-platform: With a few platform-specific adjustments, it functions on Windows, Linux, and macOS.

Limitation of ctype module:

  • Manual type declarations: function return and parameter types must be manually specified.
  • Lower level than Python: Working with structs, memory pointers, and C types necessitates a deeper comprehension of C programming fundamentals.

Environment Setup:

  • Download and install Python as per your system’s operating system if Python is not available on your system.  Here is the link: https://www.python.org/downloads/ Please follow the steps that are coming during the installation of the Python application.
  • There are multiple editor and IDE supported for python code development like PyCharm, Visual Studio Code, and Eclipse. Please use any one for your development.
  • Install ctype framework from command line
                # pip install ctype
  • DLL which you need to load should be available.
  • The header file with all prototypes of the exposed functions in the DLL
  • If you have the source code for a DLL that was written using Visual Studio editor, you can install the editor. Visual Studio Installation Link: https://visualstudio.microsoft.com/downloads/. It is necessary to rebuild the DLL in accordance with Python architecture if the.dll load fails. In that instance, you should ask the development team to rebuild the DLL in accordance with your Python interpreter architecture if you don’t have access to the source code.

Sample Use Case:

The DLL contains an exposed function for creating hashes. The function takes the client device serial number (CDSN), current time, and user defined structure to save the generated hash value as an input parameter. The function returns success if the hash value is generated successfully and failure if the hash is not generated successfully. The Python code will load the.dll and generate a hash as per the current time and CDSN.

The use case may be of interest to you as it is an extension of another use case that was discussed in https://dasfascination.com/adb-shell-process-psutil/.

Steps to follow:

  • Determine whether your Python interpreter is running on a 32-bit or 64-bit architecture. You need to request a DLL from the developer according to your Python interpreter version, or you can create the DLL if you have the source code.
Picture1 2

The DLL should be compiled as a release version does not a debug version. Debug versions of DLLs often depend on debug versions of runtime libraries. This can create compatibility issues if you’re trying to use them from a Python interpreter that’s compiled with release settings (which is usually the case for standard Python distributions).

  • Next you must check the actual functions exported by a .dll file. There are several methods as per your OS. I used ‘dumpbin’ utility which is a tool in Microsoft Visual Studio. Open the Developer Command Prompt for Visual Studio and execute below command.
DLL
  • Sometime the name of the exposed function mentioned in the header file is not recognized by ‘ctype’ module. Then you must use the mangled name of the function which allow you to successfully call function from the DLL in your Python code. You can get the mangled name of the .dll using the dumpbin tool as above.
  • Sample code to verify the DLL received is loaded successfully, otherwise you need to rebuild the .dll.
import ctype
if not os.path.isfile(dll_path):
    print(f"File not found: {dll_path}")
else:
    try:
        # Load the DLL
        mylib = ctypes.CDLL(dll_path)
        #print("DLL loaded successfully")
    except OSError as e:
        print(f"Error loading DLL: {e}")
  • Check the mangled name of exposed function which you want to call from Python and define a variable in your python code with that name.
mangled_name = '?UniversalClient_GetHashCode@@YA?AW4uc_result@@IPAEPAU_uc_buffer_st@@@Z'
  • In the sample code the input parameters for the DLL exposed functions are below type and output parameter is an ‘enum’ variable with Success and Failure.
typedef unsigned int    uc_uint32;
typedef unsigned char*	uc_byte*
typedef struct _uc_buffer_st
{
    /**
     * Pointer to a valid region of memory. Depending on the operation, this
     * may be a block of memory to write into or to read from.
     */
    uc_byte *bytes;

    /**
     * Length of the buffer. Depending on the operation, the number of bytes to 
     * read to or write from the \a bytes member.
     */
    uc_uint32 length;
} uc_buffer_st;
  • The same input output parameters we need to declare in Python code using ‘ctype’ module.
class UcBufferSt(ctypes.Structure):
    _fields_ = [("data", ctypes.POINTER(ctypes.c_ubyte)),
            ("size", ctypes.c_size_t)]

mylib[mangled_name].restype = ctypes.c_int
mylib[mangled_name].argtypes= [ctypes.c_uint32,
                          ctypes.POINTER(ctypes.c_ubyte),
  • Now the sample python code to pass parameter and calling of the DLL exposed function.
cssn1 = ctypes.c_uint32(cssn) 

# Convert string to a ctypes array of bytes
#timestr1 = (ctypes.c_ubyte * len(b"2024-07-23 06:17:15"))(*b"2024-07-23 06:17:15")
my_bytes = timestr.encode('utf-8')
timestr1 = (ctypes.c_ubyte * len(my_bytes))(*my_bytes)

some_uint = ctypes.c_uint(42)  # Replace with your actual value

# Prepare the output buffer
outPutHashCode1 = UcBufferSt()
outPutHashCode1.size = ctypes.c_size_t(0)  # Initialize size
outPutHashCode1.data = ctypes.cast(None, ctypes.POINTER(ctypes.c_ubyte))  # Initially NULL

# Call the function
result = mylib[mangled_name](cssn1, timestr1, ctypes.byref(outPutHashCode1))

# Output the result
print("Result:", result)
print("Output Hash Code Buffer Size:", outPutHashCode1.size)

# Access the data if necessary
if outPutHashCode1.data:
    hash_code_bytes = ctypes.string_at(outPutHashCode1.data, outPutHashCode1.size)
    print("Hash Code Data:", hash_code_bytes)

Leave a Reply

Your email address will not be published. Required fields are marked *

Related Posts