Problems with wrapping C++ class to python - python

For my project (ia for a game) I need a C network interface. I want to code my project in Python so I created a C++ wrapper from C TCP-Socket functions, to use it with c-types in my Python code:
#include <iostream>
#include <string>
#include "Socket.h"
#include "stdio.h"
void Socket::s_connect(char *host, int port){
const char *str;
pe = getprotobyname("tcp");
if (!pe){
perror("Error get protocol");
exit(1);
}
f = socket(AF_INET, SOCK_STREAM, pe->p_proto);
if (f == -1){
perror("Error create socker");
exit(1);
}
s_in.sin_family = AF_INET;
s_in.sin_port = htons(port);
s_in.sin_addr.s_addr = inet_addr(host);
if (connect(f, (const struct sockaddr *&)s_in, sizeof(s_in)) == -1){
perror("Error on connect");
close(f);
}
}
void Socket::s_send(char *msg){
if (write(f, msg, strlen(msg)) == -1){
printf("error write\n");
perror("write");
exit(1);
}
}
char *Socket::s_recv(){
char *buff;
int ret;
buff = (char *)malloc(4096);
ret = read(f, buff, 4096);
if (ret < 0){
if (close(f))
exit(1);
}
return buff;
}
extern "C" {
Socket *Socket_new()
{
return (new Socket);
}
void Socket_connect(Socket *sock, char *host, int port)
{
sock->s_connect(host, port);
}
void Socket_send(Socket *sock, char *msg)
{
sock->s_send(msg);
}
}
Socket.h :
#ifndef SOCKET_HPP_
# define SOCKET_HPP_
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
class Socket {
int f, port;
struct protoent *pe;
struct sockaddr_in s_in;
char *ip;
public:
void s_connect(char *host, int port);
void s_send(char * msg);
char *s_recv();
};
#endif
And there is my Python class:
#!/usr/bin/python
import sys
from ctypes import cdll
lib = cdll.LoadLibrary('./libsocket.so')
class Socket(object):
def __init__(self):
self.obj = lib.Socket_new()
def s_connect(self, host, port):
print(host, port)
lib.Socket_connect(self.obj, host, int(port))
def s_send(self, msg):
lib.Socket_send(self.obj, msg)
def s_recv(self):
lib.Socket_recv(self.obj)
def main(arg):
sock = Socket()
sock.s_connect(arg[1], arg[2])
sock.s_send("coucou")
if __name__ == "__main__":
main(sys.argv)
And My extern C func can't read my string sent from Python I can send a port number but my C++ function can't read the host's string value.
If I change char * to std::string I get the same problem.
Did I do something wrong?
Thanks

As you say you use a Python 3 version, the documentation for the ctypes module states that Python unicode strings (str) correspond to wide character arrays in C (wchar_t *), and that Python byte strings (bytes) correspond to narrow character arrays (char *).
So depending if you want to actually process unicode characters, you should:
either use bytes Python side (trivial):
sock.s_connect(arg[1].encode('latin1'), arg[2])
or use wchar_t C side (probably harder...)

Related

Translate C header file in Python

I have this C-Headerfile:
#pragma pack (push,1)
typedef struct {
uint16_t DLLFailureCode;
uint8_t ConnectionStatus;
uint32_t SystemFailureCode;
} TConnectionResult;
typedef void (__cdecl *TOnConnectionEvent)(uint32_t Handle,
TConnectionResult ConnectionStatus);
DLLIMPORT uint16_t __cdecl OpenConnection(uint8_t PortType,
char * PortName,
TOnConnectionEvent OnConnectSuccess,
uint32_t * Handle);
And I implemented it as below, but for some reason I can't get any connection. Did I make a mistake or is it the DLL?
class Program:
def __init__(self):
self.DLL = CDLL(os.path.join(r"C:\Users\...\PycharmProjects\Program\epMCOMLib.dll"))
self.Handle = c_uint32(0)
self.Handle_p = pointer(self.Handle)
def OPEN(self):
PortType = c_uint8(0)
PortName = c_char_p(b'COM1')
OnConnectSuccess = c_uint32(0)
self.DLL.OpenConnection(PortType, PortName, OnConnectSuccess, self.Handle_p)
Here's a working example given your C declarations. I recommend using .argtypes and .restype to declare your function parameters and return value so ctypes can do argument checking. Passing a c_uint32(0) for Handle is incorrect on a 64-bit system but might work on a 32-bit system. Use None for a NULL pointer or you can make a callback as demonstrated below using CFUNCTYPE as the callback type.
test.c (MSVC compile: cl /LD /W4 test.c)
#include <stdint.h>
#pragma pack(push,1)
typedef struct {
uint16_t DLLFailureCode;
uint8_t ConnectionStatus;
uint32_t SystemFailureCode;
} TConnectionResult;
#pragma pack(pop)
typedef void (__cdecl *TOnConnectionEvent)(uint32_t Handle,
TConnectionResult ConnectionStatus);
__declspec(dllexport)
uint16_t __cdecl OpenConnection(uint8_t PortType,
char * PortName,
TOnConnectionEvent OnConnectSuccess,
uint32_t * Handle)
{
TConnectionResult result = {1, 2, 3};
*Handle = 123;
OnConnectSuccess(*Handle, result);
return 1;
}
test.py
import ctypes as ct
class TConnectionResult(ct.Structure):
_pack_ = 1
_fields_ = (('DLLFailureCode', ct.c_uint16),
('ConnectionStatus', ct.c_uint8),
('SystemFailureCode', ct.c_uint32))
def __repr__(self):
return f'TConnectionResult(DLLFailureCode={self.DLLFailureCode}, ' \
f'ConnectionStatus={self.ConnectionStatus}, ' \
f'SystemFailureCode={self.SystemFailureCode})'
TOnConnectionEvent = ct.CFUNCTYPE(None, ct.c_uint32, TConnectionResult)
#TOnConnectionEvent
def callback(handle, status):
print(f'{handle=}, {status=}')
dll = ct.CDLL('./test')
dll.OpenConnection.argtypes = ct.c_uint8, ct.c_char_p, TOnConnectionEvent, ct.POINTER(ct.c_uint32)
dll.OpenConnection.restype = ct.c_uint16
port_type = 0
port_name = b'COM1'
handle = ct.c_uint32()
result = dll.OpenConnection(port_type, port_name, callback, ct.byref(handle))
print(f'{result=} handle={handle.value}')
Output:
handle=123, status=TConnectionResult(DLLFailureCode=1, ConnectionStatus=2, SystemFailureCode=3)
result=1 handle=123

Embedding Python in C Application

From the official docs ;
#define PY_SSIZE_T_CLEAN
#include <Python.h>
int main(int argc, char *argv[])
{
wchar_t *program = Py_DecodeLocale(argv[0], NULL);
if (program == NULL) {
fprintf(stderr, "Fatal error: cannot decode argv[0]\n");
exit(1);
}
Py_SetProgramName(program); /* optional but recommended */
Py_Initialize();
PyRun_SimpleString("from time import time,ctime\n"
"print('Today is', ctime(time()))\n");
if (Py_FinalizeEx() < 0) {
exit(120);
}
PyMem_RawFree(program);
return 0;
}
I successfully ran this piece. I am trying to get output of PyRun_SimpleString to a string variable, let's say
char string[50];
I went through the documentation but couldn't come up with a result. As far as I can tell there are multiple ways to achieve this tiny task and is in the orders of a couple of additional lines. I would appreciate a guidence or a workaround regarding this manner.
I wish I had found a better way, but this way seems to work, (will update if I find a better solution):
If you defined a class in Python to catch sys.stdout writes:
import sys
class CatchOutErr:
def __init__(self):
self.value = ''
def write(self, txt):
self.value += txt
catchOutErr = CatchOutErr()
sys.stdout = catchOutErr
sys.stderr = catchOutErr
And you passed the value received from this handler to C++, converted to string, etc...
#include <Python.h>
#include <iostream>
#include <stdio.h>
int main(int argc, char *argv[])
{
Py_Initialize();
PyObject *pModule = PyImport_AddModule("__main__"); //create main module
std::string stdOutErr = "import sys\nclass CatchOutErr:\n\tdef __init__(self):\n\t\tself.value = ''\n\tdef write(self, txt):\n\t\tself.value += txt\ncatchOutErr = CatchOutErr()\nsys.stdout = catchOutErr\nsys.stderr = catchOutErr\n";
PyRun_SimpleString(stdOutErr.c_str()); //invoke code to redirect
PyRun_SimpleString("from time import time,ctime\n"
"print('Today is', ctime(time()))\n");
PyObject *catcher = PyObject_GetAttrString(pModule, "catchOutErr"); //get our catchOutErr created above
PyObject *output = PyObject_GetAttrString(catcher,"value"); //get the stdout and stderr from our catchOutErr object
std::string s = PyString_AsString(output);
Py_DECREF(catcher);
Py_DECREF(output);
Py_DECREF(s);
std::cout << s;
return 0;
}
('Today is', 'Thu Jul 30 09:02:55 2020')
For Python3:
...
PyObject *output = PyObject_GetAttrString(catcher,"value"); //get the stdout and stderr from our catchOutErr object
PyObject *encodedData = PyUnicode_AsEncodedString(output, "ascii", NULL);
Py_DECREF(output);
Py_DECREF(encodedData);
char* buf;
Py_ssize_t len;
PyBytes_AsStringAndSize(encodedData, &buf, &len);
std::cout << std::string(buf) << std::endl;
Reference:
https://stackoverflow.com/a/4307737/9238288
https://cpp.hotexamples.com/examples/-/-/PyUnicode_AsEncodedString/cpp-pyunicode_asencodedstring-function-examples.html

ctypes, problem with output format function c++

I created a ctype function that calls a C ++ library that connects to a socket and sends it a 5 bytes hex packet. The function connects send the hex code I write I input the peripheral responds (I checked with Wireshark) in a correct way but the python code with ctype fails to interopet well the packet it receives as a response (always 5 bytes). Can anyone give me any suggestions? Where am I wrong? Attached I enclose the C ++ code with the code or python and the answer of the function.
I have already tried c_char_p, ctypes.POINTER (LP_c_char), ctypes.POINTER (LP_c_char), but nothing the return of the function is always 0
C++ Code connect_pe_func.cpp
#include <stdio.h>
#include <errno.h>
#include <string>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <cstring>
#include <unistd.h>
#include <iostream>
#include <unistd.h>
#include <sstream>
extern "C" char * connect_pe_func(int argc, char *argv[])
{
int sockfd, n;
int connected = 0;
struct sockaddr_in servaddr;
std::string serveraddr = argv[0];
sockfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr(serveraddr.c_str());
servaddr.sin_port = htons(9761);
connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
std::string pref_hex;
std::string hex("0x");
std::string test = argv[1];
size_t numbytes = test.size() / 2;
uint8_t command[numbytes];
for (size_t w = 0, x = 0; w < numbytes; ++w, x += 2)
{
pref_hex = hex + test.substr(x, 2);
//cout << pref_hex;
command[w] = stoi(pref_hex, nullptr, 16);
}
int bytes_to_send = sizeof(command);
send(sockfd, command, bytes_to_send, 0);
uint8_t output_command[numbytes];
recv(sockfd, output_command, bytes_to_send, 0);
close(sockfd);
char test_out[10];
for (size_t w = 0, x = 0; w < numbytes; ++w, x += 2)
{
test_out[x] = (char)output_command[w];
}
return test_out;
}
python code
import sys
import ctypes
lib = ctypes.CDLL('./connect_pe_func.so')
LP_c_char = ctypes.POINTER(ctypes.c_char)
LP_LP_c_char = ctypes.POINTER(LP_c_char)
lib.connect_pe_func.argtypes = (ctypes.c_int, LP_LP_c_char)
lib.connect_pe_func.restypes = ctypes.c_char_p
argv = ["192.168.2.170","2600000026"]
argc = len(argv)
p = (LP_c_char*len(argv))()
for i, arg in enumerate(argv): # not sys.argv, but argv!!!
enc_arg = arg.encode('utf-8')
p[i] = ctypes.create_string_buffer(enc_arg)
na = ctypes.cast(p, LP_LP_c_char)
lib.connect_pe_func(argc, na)
I expected the output of python code like 26810000a7, but the actual output is always 0

python ctypes use pointers to get results from a function

I am trying to call a C function form my python code, the C function is the following:
#include <stdio.h>
#include <stddef.h>
#include "picohttpparser.c"
#include "picohttpparser.h"
const char *path;
char buf[4096];
int pret, minor_version, i;
struct phr_header headers[100];
size_t buflen = 0, prevbuflen = 0, method_len, path_len, num_headers;
ssize_t rret;
void parse_http_request(char *request, const char *method) {
/* parse the request */
strncpy(buf, request, 4096);
num_headers = sizeof(headers) / sizeof(headers[0]);
phr_parse_request(buf, sizeof(buf) -1, &method, &method_len, &path, &path_len, &minor_version, headers, &num_headers, 0);
}
In python I am using ctypes:
import ctypes
_cparser = ctypes.CDLL('/tmp/serv.so')
_cparser.parse_http_request.argtypes = (ctypes.c_char_p, ctypes.POINTER(ctypes.c_char_p))
def parse(request):
method = ctypes.c_char_p()
_cparser.parse_http_request(ctypes.c_char_p(request.encode()), ctypes.byref(method))
print(method)
method is always None, but if I compile the C code, it prints out correctly the method variable. What am I missing here?

Passing two parameters (int and array) to embedded Python function

I need to call Python function from my module and set two parameters for it: int and array.
For a now I get segfault during calling this function and I have no idea what I'm doing wrong. Could someone specify where my mistake is?
Function in my Python module app.py. It works if I call it from Python code:
def get_model(rate, signal):
mfcc_train = MFCC().compute(rate, signal)
with open('mfcc_test', 'wb') as f:
pickle.dump(mfcc_train, f)
return clf()._fit(mfcc_train)
My C code that calls the function above. The last pring is "Before calling"
#include <Python.h>
#include <stdio.h>
#include "wav.h"
#include <numpy/arrayobject.h>
int main(int argc, char *argv[])
{
PyObject *pName, *pModule, *pDict, *pFunc, *pValue, *pArgs;
uint8_t *samples = NULL;
wavread("test.wav", &samples);
printf("No. of channels: %d\n", header->num_channels);
printf("Sample rate: %d\n", header->sample_rate);
printf("Bit rate: %dkbps\n", header->byte_rate*8 / 1000);
printf("Bits per sample: %d\n\n", header->bps);
printf("Sample 0: %d\n", samples[0]);
printf("Sample 1: %d\n", samples[1]);
// Initialize the Python Interpreter
printf("Before init\n");
Py_Initialize();
PyObject *sysPath = PySys_GetObject("path");
const char *scriptDirectoryName = ".";
PyObject *path = PyUnicode_FromString(scriptDirectoryName);
int result = PyList_Insert(sysPath, 0, path);
printf("after init\n");
// Build the name object
pName = PyUnicode_DecodeFSDefault(argv[1]);
printf("after pname %s %d\n", argv[1], pName == NULL ? 1 : 0);
// Load the module object
pModule = PyImport_Import(pName);
printf("after pmodule %d\n", pModule == NULL ? 1 : 0);
// pFunc is also a borrowed reference
pFunc = PyObject_GetAttrString(pModule, "get_model");
printf("after pfunc\n");
if (PyCallable_Check(pFunc))
{
pArgs = PyTuple_New(2);
printf("after pytuple\n");
PyTuple_SetItem(pArgs, 0, PyLong_FromLong(header->sample_rate));
printf("after set item\n");
uint8_t* array = malloc(header->datachunk_size);
int dims[1];
dims[0] = header->datachunk_size;
printf("alloc\n");
import_array();
PyObject* pSamples = PyArray_SimpleNewFromData(1, dims, NPY_INT8, (void*)samples);
printf("pSamples\n");
PyArray_ENABLEFLAGS((PyArrayObject*)pSamples, NPY_ARRAY_OWNDATA);
PyTuple_SetItem(pArgs, 1, pSamples);
printf("Before calling\n");
pValue = PyObject_CallObject(pFunc, pArgs);
printf("After calling\n");
} else
{
PyErr_Print();
}
printf("pValue: %d\n", pValue);
// Clean up
Py_DECREF(pModule);
Py_DECREF(pFunc);
Py_DECREF(pName);
// Finish the Python Interpreter
Py_Finalize();
free(header);
free(samples);
}
UPD: updated code where one issue was fixed. But another problem still exists. It's in line PyObject* pSamples = PyArray_SimpleNewFromData(1, dims, NPY_INT8, (void*)samples);. And I can't find out what is wrong with it.
And wav.h just in case:
#include <inttypes.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <err.h>
typedef struct {
char chunk_id[4];
uint32_t chunk_size;
char format[4];
char fmtchunk_id[4];
uint32_t fmtchunk_size;
uint16_t audio_format;
uint16_t num_channels;
uint32_t sample_rate;
uint32_t byte_rate;
uint16_t block_align;
uint16_t bps;
char datachunk_id[4];
uint32_t datachunk_size;
}WavHeader;
WavHeader *header;
void wavread(char *file_name, int16_t **samples)
{
int fd;
if (!file_name)
errx(1, "Filename not specified");
if ((fd = open(file_name, O_RDONLY)) < 1)
errx(1, "Error opening file");
if (!header)
header = (WavHeader*)malloc(sizeof(WavHeader));
if (read(fd, header, sizeof(WavHeader)) < sizeof(WavHeader))
errx(1, "File broken: header");
if (strncmp(header->chunk_id, "RIFF", 4) ||
strncmp(header->format, "WAVE", 4))
errx(1, "Not a wav file");
if (header->audio_format != 1)
errx(1, "Only PCM encoding supported");
if (*samples) free(*samples);
*samples = (int16_t*)malloc(header->datachunk_size);
if (!*samples)
errx(1, "Error allocating memory");
if (read(fd, *samples, header->datachunk_size) < header->datachunk_size)
errx(1, "File broken: samples");
close(fd);
}
It's difficult to tell without the definition of header but I believe the issue is in the line
PyTuple_SetItem(pArgs, 0, header->sample_rate);
PyTuple_SetItem expects a Python object and you're passing it what I think is an integer, which is being misinterpreted as a PyObject*.
I suspect you want
PyTuple_SetItem(pArgs, 0, PyInt_FromLong(header->sample_rate));
(PyLong_FromLong in Python3)
Second issue: you free samples twice. First you pass it to numpy and tell numpy that it owns the data:
PyObject* pSamples = PyArray_SimpleNewFromData(1, dims, NPY_INT8, (void*)samples);
PyArray_ENABLEFLAGS((PyArrayObject*)pSamples, NPY_ARRAY_OWNDATA);
then at the end of your code you free it
free(samples);
I suspect that you meant to pass your newly allocated array to numpy instead of samples. (You still need to copy the data between them too, if this is the case)
UPD: One more right solution from comments is to change type of dims from int to npy_intp

Categories

Resources