Building a ctypes-“based” C library with distutils

后端 未结 3 442
南旧
南旧 2020-12-29 03:45

Following this recommendation, I have written a native C extension library to optimise part of a Python module via ctypes. I chose ctypes over writing a CPython-native libra

相关标签:
3条回答
  • 2020-12-29 04:31

    Some clarifications here:

    1. It's not a "ctypes based" library. It's just a standard C library, and you want to install it with distutils. If you use a C-extension, ctypes or cython to wrap that library is irrelevant for the question.

    2. Since the library apparently isn't generic, but just contains optimizations for your application, the recommendation you link to doesn't apply to you, in your case it is probably easier to write a C-extension or to use Cython, in which case your problem is avoided.

    For the actual question, you can always use your own custom distutils command, and in fact one of the discussions linked to just such a command, the OOF2 build_shlib command, that does what you want. In this case though you want to install a custom library that really isn't shared, and then I think you don't need to install it in /usr/lib/yourproject, but you can install it into the package directory in /usr/lib/python-x.x/site-packages/yourmodule, together with your python files. But I'm not 100% sure of that so you'll have to try.

    0 讨论(0)
  • 2020-12-29 04:39

    I have setup a minimal working python package with ctypes extension here: https://github.com/himbeles/ctypes-example which works on Windows, Mac, Linux.

    • It takes the approach of memeplex above of overwriting build_ext.get_export_symbols() and forcing the library extension to be the same (.so) for all operating systems.
    • Additionally, a compiler directive in the c / c++ source code ensures proper export of the shared library symbols in case of Windows vs. Unix.
    • As a bonus, the binary wheels are automatically compiled by a GitHub Action for all operating systems :-)
    0 讨论(0)
  • 2020-12-29 04:41

    The distutils documentation here states that:

    A C extension for CPython is a shared library (e.g. a .so file on Linux, .pyd on Windows), which exports an initialization function.

    So the only difference regarding a plain shared library seems to be the initialization function (besides a sensible file naming convention I don't think you have any problem with). Now, if you take a look at distutils.command.build_ext you will see it defines a get_export_symbols() method that:

    Return the list of symbols that a shared extension has to export. This either uses 'ext.export_symbols' or, if it's not provided, "PyInit_" + module_name. Only relevant on Windows, where the .pyd file (DLL) must export the module "PyInit_" function.

    So using it for plain shared libraries should work out-of-the-box except in Windows. But it's easy to also fix that. The return value of get_export_symbols() is passed to distutils.ccompiler.CCompiler.link(), which documentation states:

    'export_symbols' is a list of symbols that the shared library will export. (This appears to be relevant only on Windows.)

    So not adding the initialization function to the export symbols will do the trick. For that you just need to trivially override build_ext.get_export_symbols().

    Also, you might want to simplify the module name. Here is a complete example of a build_ext subclass that can build ctypes modules as well as extension modules:

    from distutils.core import setup, Extension
    from distutils.command.build_ext import build_ext
    
    
    class build_ext(build_ext):
    
        def build_extension(self, ext):
            self._ctypes = isinstance(ext, CTypes)
            return super().build_extension(ext)
    
        def get_export_symbols(self, ext):
            if self._ctypes:
                return ext.export_symbols
            return super().get_export_symbols(ext)
    
        def get_ext_filename(self, ext_name):
            if self._ctypes:
                return ext_name + '.so'
            return super().get_ext_filename(ext_name)
    
    
    class CTypes(Extension): pass
    
    
    setup(name='testct', version='1.0',
          ext_modules=[CTypes('ct', sources=['testct/ct.c']),
                       Extension('ext', sources=['testct/ext.c'])],
          cmdclass={'build_ext': build_ext})
    
    0 讨论(0)
提交回复
热议问题