2

I'm trying to write a Python wrapper using Ctypes for a pre-written DLL file but keep struggling with a pointer issue.

Specifically, a simplified example of my C++ function that's been compiled into the DLL is:

double compute_result(double var1, double var2, char *output_message1, char *output_message2){
    // Computational steps are all here, e.g.
    double result = var1 + var2;

    // then an information message is passed to the output_message1 & 2 location
    sprintf(output_message1, "useful output message 1");
    sprintf(output_message2, "useful output message 2");

    return(result);
}

To wrap this using ctypes, I've tried to define the appropriate restype and argtype as follows. The C++ code doesn't specify a size for the output message pointer so I assume I don't have to using ctypes.

dll = ctypes.WinDLL("MyDLL.dll")
f = dll.compute_result
f.restype = c_double
f.argtypes = [c_double, c_double, POINTER(c_char), POINTER(c_char)]

I then try calling my code in Python using:

# Imports
import ctypes
from ctypes import c_double, c_char, POINTER, addressof, cast

# Input variables
var1 = 1.1
var2 = 2.2

# Create string buffers to hold output message, then convert address to a pointer to pass to dll function
size = 1024  # we know output messages will be shorter than 1024 characters
buf1 = ctypes.create_string_buffer(size)
buf2 = ctypes.create_string_buffer(size)

f(var1, var2, cast(addressof(buf1), POINTER(c_char)), cast(addressof(buf2), POINTER(c_char)))

Unfortunately, a dialog box error is displayed upon execution, saying:

"Debug Assertion Failed!"

Program: ...somepath_on_my_computer\python.exe
File: ...somepath_on_my_computer\sprintf.c
Line: 110

Expression: (string != NULL)

I understand that this implies some error with my pointers where sprintf is meant to write the output message too, but I can't see what exactly is wrong. Is there a way to fix this please? Or am I handling pointers incorrectly? Thanks!

SLhark
  • 177
  • 10

1 Answers1

1

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

buf1 and buf2 (in underlying C) are seen as arrays, so they are already addresses.

Get rid of addressof, as it will trigger Undefined Behavior (check [SO]: C function called from Python via ctypes returns incorrect value (@CristiFati's answer)).

Try:

f(var1, var2, cast(buf1, POINTER(c_char)), cast(buf2, POINTER(c_char)))

For more advanced example(s), check [SO]: How can i cast a double pointer ctype to numpy array? (@CristiFati's answer).

Edit #0

Adding code samples.

dll00.c:

#include <stdio.h>

#if defined(_WIN32)
#  define DLL00_EXPORT_API __declspec(dllexport)
#else
#  define DLL00_EXPORT_API
#endif


#if defined(__cplusplus)
extern "C" {
#endif

DLL00_EXPORT_API double dll00Func00(double var1, double var2, char *pMsg1, char *pMsg2);

#if defined(__cplusplus)
}
#endif


double dll00Func00(double var1, double var2, char *pMsg1, char *pMsg2) {
    double result = var1 + var2;
    sprintf(pMsg1, "useful output message 1");
    sprintf(pMsg2, "useful output message 2");
    return result;
}

code00.py:

#!/usr/bin/env python3

import sys
import ctypes as ct


DLL_NAME = "./dll00.dll"


def main():
    dll00 = ct.CDLL(DLL_NAME)
    dll00Func00 = dll00.dll00Func00
    dll00Func00.argtypes = [ct.c_double, ct.c_double, ct.POINTER(ct.c_char), ct.POINTER(ct.c_char)]
    dll00Func00.restype = ct.c_double

    v1 = 1.1
    v2 = 2.2
    size = 1024
    buf1 = ct.create_string_buffer(size)
    buf2 = ct.create_string_buffer(size)

    res = dll00Func00(v1, v2, ct.cast(buf1, ct.POINTER(ct.c_char)), ct.cast(buf2, ct.POINTER(ct.c_char)))
    print("{0:s} returned: {1:.3f}".format(dll00Func00.__name__, res))
    print(buf1.value, buf2.value)


if __name__ == "__main__":
    print("Python {0:s} {1:d}bit on {2:s}\n".format(" ".join(item.strip() for item in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform))
    main()
    print("\nDone.")

Output:

[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q058932240]> sopr.bat
*** Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ***

[prompt]> "c:\Install\x86\Microsoft\Visual Studio Community\2017\VC\Auxiliary\Build\vcvarsall.bat" x64
**********************************************************************
** Visual Studio 2017 Developer Command Prompt v15.9.17
** Copyright (c) 2017 Microsoft Corporation
**********************************************************************
[vcvarsall.bat] Environment initialized for: 'x64'

[prompt]> dir /b
code00.py
dll00.c

[prompt]> cl /nologo /MD /DDLL dll00.c  /link /NOLOGO /DLL /OUT:dll00.dll
dll00.c
   Creating library dll00.lib and object dll00.exp

[prompt]> dir /b
code00.py
dll00.c
dll00.dll
dll00.exp
dll00.lib
dll00.obj

[prompt]> "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\Scripts\python.exe" code00.py
Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)] 64bit on win32

dll00Func00 returned: 3.300
b'useful output message 1' b'useful output message 2'

Done.

As a note, it also works when the .dll is build for debug.

CristiFati
  • 38,250
  • 9
  • 50
  • 87
  • Thanks for this suggestion and the links. Unfortunately, even after removing `addressof`, the error remains with the same error message. I tried replacing POINTER(c_char) with c_char_p too but it makes no change. Are there any other obvious error / ways to debug this? Thanks – SLhark Nov 20 '19 at 09:30