0

I have a c-function with the following signature

__declspec( dllexport ) void* setup(int c_force, int c_stepping, int c_iteration, int c_roots,
                                    struct para* c_userdata, double* c_y0,
                                    double c_reltol, double c_abstol)
{
    ....
    return "a pointer";
}

where para is defined as below. I want to build a dll with this function (and others) and access it from Python. The important Python lines are

import ctypes as ct
lib = ct.cdll.LoadLibrary('lib_path.dll')
getattr(lib, 'setup')
lib.setup.restype = ct.c_void_p
# pystruct as defined below to avoid clogging code
lib.setup.argtypes = [ct.c_int, ct.c_int, ct.c_int, ct.c_int,
                      ct.POINTER(para), ct.POINTER(ct.c_double),
                      ct.c_double, ct.c_double]
ptr = lib.setup(cf, cs, ci, cr, ct.byref(para), c_y0, c_reltol, c_abstol) 
myobj = ct.c_void_p(ptr)

where cf, cs, ci, cr are (python) ints, para is of struct-type as defined below, c_y0=(ct_c_double * 2)() (should be length 2, both from the python side and on the c-side) and c_reltol and c_abstol are cast to ct.c_double as c_reltol = ct.c_double(reltol).

When I try to run my main application I get WindowsError: exception: access violation writing xxx at the lib.setup-function call and I cant see why... Printing just before calling lib.setup gives the following output for the values and types of the parameters passed to the function (in order)

1 2 2 1 <cparam 'P' (0000000003C84EB0)> <cvode_library.c_double_Array_2 object at 0x0000000003F95BC8> c_double(1e-06) c_double(1e-08)

<type 'int'> <type 'int'> <type 'int'> <type 'int'> <type 'CArgObject'> <class 'cvode_library.c_double_Array_2'> <class 'ctypes.c_double'> <class 'ctypes.c_double'>

I have been trying to debug with this and this question, but without success. Since the call signature of the c-function is fairly simple I cant see why it should break.

P.S. It runs perfectly on Ubuntu, with __declspec... replaced by extern

c-struct defined as

typedef struct para PARA;
struct para
{
    double a;
    double b;
    double c;
};

and corresponding pystruct as

class para(ct.Structure):
    _fields_ = [('a', ct.c_double),
               ('b', ct.c_double),
               ('c', ct.c_double)]

EDIT c_y0 is defined as

y0 = np.array([0., 0.])
c_y0 = (ct.c_double * 2)()
c_y0[0] = y0[0]
c_y0[1] = y0[1]

All "cvode"-functions and the N_Vector are part of Sundials suite for solving nonlinear equations

__declspec( dllexport ) void* setup(int c_force, int c_stepping, int c_iteration, int c_roots,
                                    struct para* c_userdata, double* c_y0,
                                    double c_reltol, double c_abstol)
{
    int flag;
    N_Vector y;
    void* cvode_mem;
    PARA* ptr_para;
    ptr_para = c_userdata;

    // ****** Set up vector with initial conditions ******
    y = N_VNew_Serial(2);
    NV_Ith_S(y,0) = c_y0[0];
    NV_Ith_S(y,1) = c_y0[1];

    // ****** Create cvode object with stepping and iteration method ******
    if(c_iteration==CV_FUNCTIONAL)
        cvode_mem = CVodeCreate(c_stepping, 1);    // Functional iteration
    else
        cvode_mem = CVodeCreate(c_stepping, 2);    // Newton interation
    if(check_flag((void *)cvode_mem, "CVodeCreate", 0)) return(NULL);

    flag = CVodeInit(cvode_mem, ode, 0, y);

    if(check_flag(&flag, "CVodeInit", 1)) return(NULL);

    // ****** Specify integration tolerances ******
    flag = CVodeSStolerances(cvode_mem, c_reltol, c_abstol);
    if(check_flag(&flag, "CVodeSStolerances", 1)) return(NULL);

    // ****** Set up linear solver module if required ******
    if(c_iteration==CV_DENSE_USER)
    {
        printf("Dense user supplied Jacobian\n");
        // Dense user-supplied Jacobian
        flag = CVDense(cvode_mem, 2);
        if(check_flag(&flag, "CVDense", 1)) return(NULL);

        flag = CVDlsSetDenseJacFn(cvode_mem, jac);

        if(check_flag(&flag, "CVDlsSetDenseJacFn", 1)) return(NULL);
    }
    else if(c_iteration==CV_DENSE_DQ)
    {
        // Dense difference quotient Jacobian
        flag = CVDlsSetDenseJacFn(cvode_mem, NULL);
        if(check_flag(&flag, "CVDlsSetDenseJacFn", 1)) return(NULL);
    }


    // Set optional inputs
    flag = CVodeSetUserData(cvode_mem, c_userdata);
    if(check_flag(&flag, "CVodeSetUserData", 1)) return(NULL);

    // Attach linear solver module

    // Specify rootfinding problem
    if(c_roots!=ROOTS_OFF)
    {
         flag = CVodeRootInit(cvode_mem, 1, root_func);
    }

    return cvode_mem;
}
pathoren
  • 1,634
  • 2
  • 14
  • 22
  • Could you share the whole code (especially `setup` func), and let us decide what's important? or better:try to reduce your code to [\[SO\]: mcve](https://stackoverflow.com/help/mcve). I'm concerned about `c_y0`. – CristiFati Jul 20 '17 at 21:03
  • @CristiFati I know a mcve would be optimal, but the code is very large, so it will be a bit tricky to isolate it. If it is absolutely necessary I can try to do that tomorrow. However see the edit for a nicely formatted `setup`-function. – pathoren Jul 20 '17 at 21:17
  • I'm sorry but there are too many variables that could go wrong and too much extra info that I don't have. So you might consider _mcve_. Btw: `NV_Ith_S` is a macro right? _Access Violation_ occurs when you try to access memory that's "not yours". – CristiFati Jul 20 '17 at 21:48
  • I fully understand you, so thank you for your time. I have been wrapping my head around this the whole day today. The strange thing is that the same code runs flawlessly on Ubuntu. Im not 100% sure what `NV_Ith_S` really is, but it is part of the Sundials-library. However thanks again! – pathoren Jul 20 '17 at 21:57
  • I'm sure that what we're talking about, is _UB_ (undefined behavior). It works on certain compilers on certain circumstances or it fails. The fact that it works in certain situations should be considered (dumb) luck. Recently, I encountered a situation, when an array out of bounds `someArray[-1]` was __always working perfectly__ on many _Ux_ (although it should have *segfault*ed), but on _Win_ __always threw__ _Access Violation_. – CristiFati Jul 20 '17 at 22:16
  • FYI, you can use `ndpointer` to create a type for `argtypes` that checks the type and size of a NumPy array before passing it directly as the argument to a ctypes function pointer, e.g. `np.ctypeslib.ndpointer('d', 1, (2,))` to declare that an argument is a C double array with 1 dimension and length 2. Also you don't need to manually wrap a float as a `c_double` if it's declared in `argtypes`. Thus with `argtypes` set properly, you can pass the last three arguments directly, e.g. `lib.setup(cf, cs, ci, cr, ct.byref(para), y0, reltol, abstol)`. – Eryk Sun Jul 21 '17 at 21:35

1 Answers1

1

Here's an MCVE. It show's your declarations are correct so the problem is likely in the function implementation. If below doesn't work for you, update your question with a similar MCVE that reproduces your failure.

test.c

#include <stdio.h>

typedef struct para PARA;
struct para
{
    double a;
    double b;
    double c;
};

__declspec(dllexport) void* setup(int c_force, int c_stepping, int c_iteration, int c_roots,
                                  struct para* c_userdata, double* c_y0,
                                  double c_reltol, double c_abstol)
{
    printf("%d %d %d %d %lf %lf %lf %lf %lf %lf %lf\n",c_force,c_stepping,c_iteration,c_roots,c_userdata->a,c_userdata->b,c_userda
ta->c,c_y0[0],c_y0[1],c_reltol,c_abstol);
    return NULL;
}

test.py

import ctypes as ct

class para(ct.Structure):
    _fields_ = [('a', ct.c_double),
               ('b', ct.c_double),
               ('c', ct.c_double)]

lib = ct.CDLL('test')
lib.setup.restype = ct.c_void_p
# pystruct as defined below to avoid clogging code
lib.setup.argtypes = [ct.c_int, ct.c_int, ct.c_int, ct.c_int,
                      ct.POINTER(para), ct.POINTER(ct.c_double),
                      ct.c_double, ct.c_double]
p = para(1.5,2.5,3.5)
c_y0 = (ct.c_double * 2)(4.5,5.5)
ptr = lib.setup(1,2,3,4,p, c_y0,6.5,7.5) 
myobj = ct.c_void_p(ptr)

Output

1 2 3 4 1.500000 2.500000 3.500000 4.500000 5.500000 6.500000 7.500000
Mark Tolonen
  • 166,664
  • 26
  • 169
  • 251
  • Thank you Mark for your clear example, and it works. After some more debugging I found that it is not the function call itself which breaks down (as your example demonstrated) but rather the call to the cvode-function `NV_Ith_S(y,i) = c_y0[i];`. If I comment out those lines, the setup works, but with them it does not. My suspicion is that something is incompatible with my library, and it feels out of scope to debug that at the moment. However thank you again for your example! – pathoren Jul 22 '17 at 22:48