1

I have a .dll named my.dll, with 4 functions, called in python 3.5 using:
myDLL = ctypes.cdll.LoadLibrary(Path:\to\my.dll)

My problem is calling a function that has LPSTR:

#include "stdafx.h"
#include "newheader.h"    
double  _stdcall pain_function(double arg1, double arg2, LPSTR arg_string_with_spaces)

the other 3 calls to my.dll work fine.

Below is the code I tried for the pain_function:

import ctypes
from ctypes import wintypes

# Load dll
myDLL = ctypes.WinDLL(Path:\to\my.dll)

# Call function 
pain_function = myDLL.pain_function

# Typecast the arguments
pain_function.argtypes = [ctypes.c_double, ctypes.c_double, wintypes.LPSTR]

# Typecast the return
pain_function.restype = ctypes.c_double


pain_return = pain_function(arg1, arg2, some_string)

pain_return returns some nonsensical number. I tried some variations, mostly along the lines of:

some_string = ctypes.create_string_buffer(b' ' * 200)

I have looked here among other places:

  1. python-ctypes-prototype-with-lpcstr-out-parameter

  2. Using String Parameters in DLL Function Calls

What is this correct way to pass the string of 200 spaces to the dll?

Thank you in advance

Community
  • 1
  • 1
Itay Livni
  • 2,143
  • 24
  • 38
  • `LPSTR` is an alias of `c_char_p`. If you prefer, you can use `from ctypes import wintypes; wintypes.LPSTR`. Since it's not `LPCSTR` (a constant string), we should assume the function modifies the buffer, so you are correct to use `create_string_buffer`. There's no need to `encode` the value. Just use a `bytes` literal, e.g. `e = create_string_buffer(b' ' * 200)`. If you do encode the value, use the `'mbcs'` encoding instead of hard coding codepage 1252. – Eryk Sun Apr 21 '16 at 19:46
  • As to the nonsensical number, you didn't show the complete prototype. If `pain_function` has a `void` return value, set `pain_function.restype = None`. If it has a `double` return value, set `pain_function.restype = c_double`. And so on. – Eryk Sun Apr 21 '16 at 19:53
  • @eryksun Thanks! ... I guess there were two issues with the code (1) `restype` which I fixed (2) this `LPSTR` so now my argtype looks like this: `pain_function=[`ctypes.c_double, ctypes.c_double, wintypes.LPSTR]` however - now it just returning a double, what else is missing? – Itay Livni Apr 21 '16 at 20:28
  • If it's writing to `e`, check `e.value` to get the value as a null-terminated string. To get the whole array, use either `e.raw` or `e[:]`. – Eryk Sun Apr 21 '16 at 20:36
  • @eryksun what do you mean by 'writing to e.value'? I edited the question using your suggestions – Itay Livni Apr 21 '16 at 20:54
  • I didn't say "writing to e.value". You're passing a 200 byte buffer to the function, I assume in order for the function to write a string or other data in the buffer. After you call the function, if it succeeds, the value as a null-terminated string (i.e. the bytes up to the first `NUL` byte) is `some_string.value` and the whole 200-byte array as a string is `some_string[:]` or `some_string.raw`. As to whether the function succeeds, please update your question to include the complete function prototype from the header, including the return type. – Eryk Sun Apr 21 '16 at 21:00
  • @eryksun Thanks. You answered my question: `restype` and `wintype.LPSTR` – Itay Livni Apr 21 '16 at 23:38

1 Answers1

1

You indicated the prototype was:

double  _stdcall pain_function(double arg1, double arg2, LPSTR arg_string_with_spaces)

but using:

myDLL = ctypes.cdll.LoadLibrary(Path:\to\my.dll)

It should be, for __stdcall calling convention:

myDLL = ctypes.WinDLL('Path:\to\my.dll')
Mark Tolonen
  • 166,664
  • 26
  • 169
  • 251
  • Probably Itay is using 64-bit Python, on which it doesn't make a difference. But it's still better to use `WinDLL` to support 32-bit Python. – Eryk Sun Apr 22 '16 at 09:24
  • That is correct I am using 64-bit-Python. And funny enough when I was trying to figure out the issue, I tried both and it did not make a difference. That being said - I am incorporating the suggestions. – Itay Livni Apr 22 '16 at 15:14
  • @ItayLivni, it does matter in 32-bit Python. In x86 `stdcall` (i.e. `WinDLL`), the callee cleans up the stack when returning, which is why you don't see this convention used with C variadic functions such as `printf`. ctypes checks for this in case a `stdcall` function is mistakenly called as `cdecl` (i.e. `CDLL`). In this case an exception should be raised such as "ValueError: Procedure called with not enough arguments (N bytes missing) or wrong calling convention", where N is the number of bytes the callee cleaned from the stack. – Eryk Sun Apr 22 '16 at 18:02