Search code examples
pythonc++csetuptoolspybind11

Python Compile Mixed C and C++ Extension


I'm attempting to compile libspng with PyBind11 so I can easily convert image data into a numpy array.

The compilation process requires compiling a few C files and then linking them to C++. However, I'm not sure how to do this with Python setuptools. I've so far been compiling all C++ or all C modules in my practice, but on clang, this results in problems when I mix the pybind11 file with the C files.

Is there a simple way that I'm missing to compile and link the C files to C++ within setup.py?

I tried building an extension for libspng, but I wasn't sure how to reference the compiled shared object file which was in the build folder.

Thanks!


Solution

  • In case it's helpful (and because I am procrastinating) I built a small demo based on the spng example

    // pywrappers.cpp
    #include <pybind11/pybind11.h>
    #include <utility>
    #include <cstdio>
    extern "C" {
        #include <spng/spng.h>
    }
    
    namespace py = pybind11;
    
    std::pair<size_t, size_t> get_size(const std::string& filename)
    {
        FILE* fp = fopen(filename.c_str(), "rb");
    
        if (fp == nullptr)
        {
            fclose(fp);
            throw std::runtime_error("File not found");
        }
    
        spng_ctx* ctx = spng_ctx_new(0);
    
        spng_set_crc_action(ctx, SPNG_CRC_USE, SPNG_CRC_USE);
    
        size_t limit = 1024 * 1024 * 64;
        spng_set_chunk_limits(ctx, limit, limit);
    
        spng_set_png_file(ctx, fp);
    
        struct spng_ihdr ihdr;
        spng_get_ihdr(ctx, &ihdr);
    
        spng_ctx_free(ctx);
        fclose(fp);
    
        return std::make_pair<size_t, size_t>(ihdr.width, ihdr.height);
    }
    
    
    PYBIND11_MODULE(foo, m)
    {
        m.def("get_size", &get_size);
    }
    
    # setup.py
    from setuptools import setup, Extension
    import pybind11
    
    SPNG_SOURCE = './libspng-0.7.1' # Wherever you put the spng headers and .a
    
    libfoo = Extension(
        'foo',
        sources=['pywrappers.cpp'],
        extra_compile_args = ['-std=c++14'],
        include_dirs=[SPNG_SOURCE, pybind11.get_include()],
        extra_link_args=['-lspng_static','-lz'],
        library_dirs=[SPNG_SOURCE]
    )
    
    if __name__ == "__main__":
        setup(
            name = 'foo',
            ext_modules=[libfoo],
        )
    

    Build the extension as usual with e.g. python setup.py build_ext --inplace. Then for example

    import foo
    foo.get_size('stuff.png')