Próbuję napisać otokę Pythona przy użyciu Ctypes dla wstępnie napisanego pliku DLL, ale wciąż zmagam się z problemem wskaźnika.

W szczególności uproszczony przykład mojej funkcji C ++, która została skompilowana do biblioteki DLL, to:

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);
}

Aby zawinąć to przy użyciu ctypes, próbowałem zdefiniować odpowiedni typ restype i argtype w następujący sposób. Kod C ++ nie określa rozmiaru wskaźnika komunikatu wyjściowego, więc zakładam, że nie muszę używać ctypów.

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)]

Następnie próbuję wywołać mój kod w Pythonie za pomocą:

# 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)))

Niestety, po uruchomieniu wyświetlany jest błąd okna dialogowego, który mówi:

"Debug Assertion Failed!"

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

Expression: (string != NULL)

Rozumiem, że oznacza to błąd w moich wskaźnikach, w których sprintf ma również pisać komunikat wyjściowy, ale nie widzę dokładnie, co jest nie tak. Czy jest jakiś sposób, aby to naprawić? A może niepoprawnie obsługuję wskaźniki? Dzięki!

2
SLhark 19 listopad 2019, 13:46
Jaka jest twoja wersja Pythona (pełna)?
 – 
CristiFati
20 listopad 2019, 13:21
Python 3.7.4 - wersja 32-bitowa, ponieważ biblioteka DLL jest 32-bitowa.
 – 
SLhark
20 listopad 2019, 14:00
A co z ctypes.CDLL("MyDLL.dll")?
 – 
CristiFati
20 listopad 2019, 14:55
Dzięki za sugestię, ale komunikat o błędzie używający CDLL zamiast WinDLL jest taki sam.
 – 
SLhark
20 listopad 2019, 16:26

1 odpowiedź

Listing [Python 3.docs]: CTTYPES - Biblioteka funkcji zagranicznych dla Pythona.

buf1 i buf2 (w bazowym C) są postrzegane jako tablice, więc są już adresami.

Pozbądź się addressof, ponieważ wywoła on niezdefiniowane zachowanie (sprawdź [SO]: funkcja C wywołana z Pythona przez ctypes zwraca niepoprawną wartość (@odpowiedź CristiFati)).

Próbować:

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

Aby uzyskać bardziej zaawansowane przykłady, sprawdź [SO]: Jak mogę rzutować ctype z podwójnym wskaźnikiem na tablicę numpy? (@odpowiedź CristiFati).

Edytuj #0

Dodawanie próbek kodu.

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.")

Wyjście :

[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.

Warto zauważyć, że działa to również wtedy, gdy plik .dll jest kompilowany do debugowania.

1
CristiFati 28 listopad 2019, 01:29
Dzięki za tę sugestię i linki. Niestety, nawet po usunięciu addressof, błąd pozostaje z tym samym komunikatem o błędzie. Próbowałem również zastąpić POINTER (c_char) z c_char_p, ale nie wprowadza to żadnych zmian. Czy są jakieś inne oczywiste błędy / sposoby na debugowanie tego? Dziękuję
 – 
SLhark
20 listopad 2019, 12:30