Search code examples
windowswinapicythonlinker-errors

Cython DLL extension compilation using WINAPI fails during linking with error LNK2001


I am trying to write a Cython interface to a (Photron) camera driver DLL. This library offers an SDK with header files and Windows libs for 32 and 64 bits. I already successfully compiled a C++ example using this library in VisualStudio. The Cython compilation on the other hand fails at linking.

The custom Cython code consists of a typedefs.pxd file containing the types, a photron.pxd file containing the constants and function declarations from the SDK header and a wrapper cdef class in a camera.pyx file.

In the setup.py file, in the Extension (imported from setuptools) section I have included the sections below:

include_dirs=[s.path.abspath(os.path.normpath('./include'))],
libraries=[os.path.join(os.path.abspath(os.curdir), 'Lib', '64bit(x64)', 'PDCLIB')],
library_dirs=[os.path.abspath(os.path.normpath('./Lib/64bit(x64)'))],

I also tried the flags from the command line of the successful C++ compilation. To allow the initial cythonization step to work (generating the cpp file) I also had to add this to the header of the SDK:

    #include <windows.h>

Below is a relevant excerpt from the pxd file:

from typedefs cimport ulong, uint

cdef extern from "PDCFUNC.h":
    ulong PDC_CloseDevice(ulong nDeviceNo, ulong *pErrorCode)

When I try to compile the code, I get the cpp file but when the linking is attempted, I get a series of lines like the following for each of the functions I declare in the pxd file:

camera.obj : error LNK2001: unresolved external symbol "unsigned long __cdecl PDC_CloseDevice(unsigned long,unsigned long *)" (?PDC_CloseDevice@@YAKKPEAK@Z)

I suppose this is because the linker tries to find a __cdecl function in the specified headers although the functions are declared this way in the header file:

unsigned long WINAPI PDC_CloseDevice(unsigned long nDeviceNo, unsigned long *pErrorCode);

Since the Cython documentation clearly states that calling conventions are supported, I thus tried to:

  1. Add the WINAPI calling convention to the pxd file but I get a Syntax error in C variable declaration during cythonization of the file.
  2. Directly add the __stdcall calling convention as the program should compile in 64 bits anyway. But I still get the LNK2001 line looking for __cdecl as if Cython was bypassing the calling convention. Indeed, here is the line from the generated cpp file:
    __pyx_v_ret = PDC_CloseDevice(__pyx_v_self->device_nb, (&__pyx_v_error_code));

The __cdecl warnings might also be a red herring but I have no other lead and I am normally not compiling under Windows so I have a limited knowledge of the tools.


Solution

  • After trying everything else, including all the options from the visual studio project that compiled correctly, I restarted with a minimal example with only the following in the pxd file

    cdef extern from "PDCLIB.h"
        ulong __stdcall PDC_Init(ulong *pErrorCode)
        ulong __stdcall PDC_CloseDevice(ulong nDeviceNo, ulong *pErrorCode)
    

    and a simpler pyx file with the same name apart from the extension.

    from pyphotron_pdclib cimport *
    
    from typedefs cimport ulong, uint
    
    cpdef init_pdc_lib():
        cdef ulong error_code = 0
        success = PDC_Init(&error_code)
        assert success != 0
        
    cpdef close_cam(ulong dev_num):
        cdef ulong error_code = 0
        success = PDC_CloseDevice(dev_num, &error_code)
        assert success != 0
    

    The main differences I see in the compilation is that

    1. it now compiles in c because there is no more extension type (cdef class)
    2. The pyx file has the same name as the pxd file

    The compilations and linking options in the setup file are as follows:

    extra_compile_args=['/Gz', '/fp:precise', '/Zc:wchar_t', '/Zc:forScope', '/Zc:inline'],
    extra_link_args=['/DEBUG', '/MACHINE:X64'],