1

I made a CPP DLL and I'm trying to call function inside it from python. I've achieved this multiple times for other functions, but this one, I just can't find my mistake.

dll_name = "..\\src\\x64\\Debug\\2019-3A-IBD-MLDLL.dll"
dllabspath = os.path.dirname(os.path.abspath(__file__)) + os.path.sep + dll_name
myDll = CDLL(dllabspath)

#fit_reg_RBF_naive
myDll.fit_reg_RBF_naive.argtypes = [ct.c_void_p, ct.c_double,  ct.c_void_p, ct.c_int, ct.c_int]
myDll.fit_reg_RBF_naive.restypes = ct.c_void_p

#predict_reg_RBF_naive
myDll.predict_reg_RBF_naive.argtypes = [ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_int, ct.c_double, ct.c_int]
myDll.predict_reg_RBF_naive.restypes = ct.c_double

def fit_reg_RBF_naive(pyXTrain, pyGamma, pyYTrain, pySampleCount, pyInputCountPerSample):
    XTrain = (ct.c_double * len(pyXTrain))(*pyXTrain)
    YTrain = (ct.c_double * len(pyYTrain))(*pyYTrain)
    inputCountPerSample = ct.c_int(pyInputCountPerSample)
    sampleCount = ct.c_int(pySampleCount)
    gamma = ct.c_double(pyGamma)
    return myDll.fit_reg_RBF_naive(XTrain, gamma, YTrain, sampleCount, inputCountPerSample)

def predict_reg_RBF_naive(pyW, pyXTrain, pyXpredict ,pyInputCountPerSample, pyGamma, pySampleCount):
    XTrain = (ct.c_double * len(pyXTrain))(*pyXTrain)
    inputCountPerSample = ct.c_int(pyInputCountPerSample)
    sampleCount = ct.c_int(pySampleCount)
    gamma = ct.c_double(pyGamma)
    Xpredict = (ct.c_double * len(pyXpredict))(*pyXpredict)
    return myDll.predict_reg_RBF_naive(W, XTrain, Xpredict, inputCountPerSample, gamma, sampleCount)

Basically I load my DLL, set Ctypes for arguments and result for both of my fonctions. Then I make a python wrapper so that the user does not have to retype every cast from python to cpp.

My types on the cpp side seems good too:

extern "C" {

    SUPEREXPORT double predict_reg_RBF_naive(double* W, double* X, double* Xpredict, int inputCountPerSample, double gamma, int N);
    SUPEREXPORT double* fit_reg_RBF_naive(double* XTrain, double gamma, double* YTrain, int sampleCount, int inputCountPerSample);
}

I have no warning from the compiler for the cpp part, I've printed the memory adresse before the return inside fit_reg_RBF_naive from cpp and the W in python and they are the same.

000002B358384980 // cpp address of W before return
0x58384980       # Python address of W after function call

For me it seems the same address. Maybe I'm wrong.

So when I try to call my second cpp function it said

myDll.predict_reg_RBF_naive(W, XTrain, Xpredict,inputCountPerSample, gamma, sampleCount) OSError: exception: access violation reading 0x000000007C7380A0

It crashed in the cpp when it tries to read W. They are no free or 'delete' in the cpp and the variable is properly allocated : double* W = new double[2];

Also, when I print W type in python I get <class 'int'>.

How comes my W seems to have the same address regarding the language, but has not the good type? Changing the result type of fit_reg_RBF_naive to POINTER(ct.c_double * 2) makes no change.

EDIT:

Here is how I call my functions:

from dll_load import predict_reg_RBF_naive, fit_reg_RBF_naive

gamma = 50
sampleCount = 2
inputCountPerSample = 3
XTrain = [1.0, 1.0, 1.0, 3.0, 3.0, 3.0]
YTrain = [-1.0, 1.0]
Xpredict = [1.0, 1.0, 1.0]

W = fit_reg_RBF_naive(XTrain, gamma, YTrain, sampleCount, inputCountPerSample)

print(predict_reg_RBF_naive(W, XTrain, Xpredict, inputCountPerSample, gamma, sampleCount))
BeGreen
  • 765
  • 1
  • 13
  • 39
  • `000002B358384980` and `0x58384980` are not the same at all, the first starts with `2B` and then has eight more digits and the second has only the final eight digits. This is symptomatic of 32 vs 64 bit errors, though where those errors are occurring I'm not sure off hand. – torek Jun 10 '19 at 21:41
  • I agree with you on this. But I don't understand why this phenomenon exist while all my other functions seems to be coded the same way... I'll take a loot on your idea. – BeGreen Jun 10 '19 at 21:45
  • Also, that `` thing seems suspicious. You may want `POINTER` applied, it's been a while since I fussed with `ctypes`. – torek Jun 10 '19 at 21:47
  • I agree with you. `W` should be `ct.c_void_p` regarding `myDll.fit_reg_RBF_naive.restypes = ct.c_void_p`?. Edit: I'll add my call file in python – BeGreen Jun 10 '19 at 21:48
  • @torek from an other function that works from my DLL/python I get the same type `` for `W` – BeGreen Jun 10 '19 at 21:55
  • I made a mistake on the `W` conversion. It's already a ctype, so I don't need to cast it in cytpes. Thus I removed `W = (ct.c_double * len(pyW))(*pyW)` – BeGreen Jun 10 '19 at 21:59
  • `myDll.fit_reg_RBF_naive.restypes` (same for the other): it's **`restype`** (no **s** at the end). Also for *argtypes*, use `ct.POINTER(ct.c_double)` when it comes to `double*`. – CristiFati Jun 11 '19 at 06:04

1 Answers1

1

[Python 3.Docs]: ctypes - A foreign function library for Python.

You misspelled restypes (it should be restype). By doing so, restype is not initialized, and defaults to int (this wouldn't be a problem on 32bit), and you ran into:

Besides that, there are several problems in the code:

  • If the C function specifies a pointer (double* in this case), don't use ctypes.c_void_p (in argtypes or restype) to map it, as it might be too wide, use (for this case) ctypes.POINTER(ctypes.c_double) instead
  • For me this doesn't even compile (I wonder how were you able to run that code). I'm going to exemplify on XTrain only, but applies to YTrain and Xpredict as well. ctypes doesn't know to convert a Python list to a ctypes.POINTER(ctypes.c_double) (or ctypes.c_void_p), and the conversion must be made manually (to a ctypes.c_double array):

    XTrain = [1.0, 1.0, 1.0, 3.0, 3.0, 3.0]
    xtrain_ctypes = (ctypes.c_double * len(XTrain))(*XTrain)
    

    and pass xtrain_ctypes to the functions.

CristiFati
  • 38,250
  • 9
  • 50
  • 87
  • I knew it... only a typo... Thank you sir. For the output type, I don't really need to be specific on the type. Some times I pass complex structure from my DLL tp python to DLL again. How comes python does not give me an error or warning on this type of errors? Usually it says something like 'ImportError: No module named restypes' ? – BeGreen Jun 11 '19 at 07:18
  • I do make the conversions for all my python variables to ctypes. I make the conversion for lists already. My code looks like a mess because i use the same names in python and cpp. I agree it is confusing. – BeGreen Jun 11 '19 at 07:26
  • 1
    *ImportError* is a totally different issue. *Python* lets you add members to an instance on the fly (that's why you don't get an error). I didn;t say the code is confusing. Apparently there are some missing parts :). – CristiFati Jun 11 '19 at 07:29
  • Ok thank you for your comment, i'm not used to python, I'm more of a C programmer. Indeed code is not full and a bit heavy to read. – BeGreen Jun 11 '19 at 07:39