I have a nested C++ struct that I'm trying to send over to Python using UDP and get the values there.
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#pragma comment(lib, "Ws2_32.lib")
#include <iostream>
#include <winsock2.h>
struct MyStruct {
int intValue;
float floatValue;
// Add more members as needed
struct MyInnerStruct {
int intValue;
float floatValue;
};
MyInnerStruct *InnerStruct;
};
int main() {
MyStruct testStruct;
testStruct.floatValue = 3.1;
testStruct.intValue = 4;
testStruct.InnerStruct = new MyStruct::MyInnerStruct[4];
for (int i = 0; i < 4; i++) {
testStruct.InnerStruct[i].intValue = i + 23;
testStruct.InnerStruct[i].floatValue = (i/10) + 23;
}
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
std::cerr << "Failed to initialize winsock." << std::endl;
return 1;
}
char hostname[256];
char ip[100];
// Get the hostname
if (gethostname(hostname, sizeof(hostname)) == 0) {
std::cout << "Hostname: " << hostname << std::endl;
// Get the IP address
struct hostent* host_info = gethostbyname(hostname);
if (host_info != nullptr) {
struct in_addr* address = reinterpret_cast<struct in_addr*>(host_info->h_addr);
strcpy(ip, inet_ntoa(*address));
std::cout << "IP Address: " << ip << std::endl;
}
else {
std::cerr << "Error getting host information." << std::endl;
}
}
else {
std::cerr << "Error getting hostname." << std::endl;
}
int udpSocket = socket(AF_INET, SOCK_DGRAM, 0);
if (udpSocket == -1) {
perror("Error creating socket");
exit(EXIT_FAILURE);
}
// Server address
sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(12345); // Choose a port
serverAddr.sin_addr.s_addr = inet_addr(ip); // Replace with the server's IP
// Serialize the struct into a byte stream
char buffer[sizeof(MyStruct)];
memcpy(buffer, &testStruct, sizeof(MyStruct));
// Send the serialized struct over the UDP socket
if (sendto(udpSocket, buffer, sizeof(MyStruct), 0, reinterpret_cast<sockaddr*>(&serverAddr), sizeof(serverAddr)) == -1) {
perror("Error sending data");
closesocket(udpSocket);
exit(EXIT_FAILURE);
}
std::cout << "Struct sent successfully." << std::endl;
// Clean up
closesocket(udpSocket);
WSACleanup();
return 0;
}
But in Python I can't seem to get the values of the nested C++ struct.
import socket
import struct
from ctypes import *
# IP address and port number for server
HOST = '127.0.0.1'
PORT = 12345
class MyInnerStruct(Structure):
_fields_ = [
('field4', c_int),
('field5', c_float),
]
class MyStruct(Structure):
_fields_ = [
('field1', c_int),
('field2', c_float),
('field3', POINTER(MyInnerStruct)),
]
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((HOST, PORT))
while True:
data, addr = sock.recvfrom(sizeof(MyStruct))
received_struct = cast(data, POINTER(MyStruct)).contents
# Access the fields of the received struct
field1 = received_struct.field1
field2 = received_struct.field2
field3 = received_struct.field3
received_inner_struct = cast(field3, POINTER(MyInnerStruct)).contents
for i in range(field1):
array[i].field4 = field3[i].field4
array[i].field5 = field3[i].field5 //these lines are not working
# Close the socket
sock.close()
I tried accessing the values of the inner nested struct (in the for loop) by trying to de-reference but I couldn't. How can I access field4
and field5
in the inner struct?
In C, send the equivalent field1-3 followed by field4-5 for each array element. Here's the Python equivalent I used for testing:
import struct
import socket
# send field1=4, field2, and 4x the two fields of each array element.
data = struct.pack('<ififififif', 4, 3.5, 1, 1.25, 2, 2.5, 3, 2.75, 4, 3.00)
with socket.socket(type=socket.SOCK_DGRAM) as s:
s.sendto(data, ('localhost', 5000))
One way to unpack, not overly efficient. To receive, read the first two fields and initialize field1-2 of MyStruct
. Use field1
to allocate an array of MyInnerStruct
. Then read the pairs of field4-5 from the buffer and initialize the array. Finally, assign the array to MyStruct.field3
:
import socket
import struct
import ctypes as ct
class MyInnerStruct(ct.Structure):
_fields_ = (('field4', ct.c_int),
('field5', ct.c_float))
def __repr__(self): # for display
return f'({self.field4}, {self.field5})'
class MyStruct(ct.Structure):
_fields_ = (('field1', ct.c_int),
('field2', ct.c_float),
('field3', ct.POINTER(MyInnerStruct)))
def __repr__(self): # for display
return f'[{self.field1}, {self.field2}, {list(self.field3[:self.field1])})]'
sock = socket.socket(type=socket.SOCK_DGRAM)
sock.bind(('', 5000))
data, addr = sock.recvfrom(40960)
field1, field2 = struct.unpack_from('<if', data)
received_struct = MyStruct(field1, field2) # pointer is null at this point
inner_array = (MyInnerStruct * field1)() # allocate inner array
start_of_inner = struct.calcsize('if') # index data after MyStruct fields
size_of_inner = struct.calcsize('if') # size of inner array element
index = start_of_inner
for i in range(field1):
field4, field5 = struct.unpack_from('<if', data[index:]) # read fields of an array element
inner_array[i] = MyInnerStruct(field4, field5) # assign to the array element
index += size_of_inner
received_struct.field3 = inner_array # pointer to the initialized inner array
print(received_struct) # now print complete received item.
Run the receiver code to wait for a packet, then run the sender code to transmit the packet:
Output from receiver:
[4, 3.5, [(1, 1.25), (2, 2.5), (3, 2.75), (4, 3.0)])]
In fact, dropping ctypes
may be faster and more simple by building Python class instances parsed from the data stream. No need to deal with Python/C marshaling:
import socket
import struct
class MyInnerStruct:
_format = '<if' # format and size of serialized data
_size = struct.calcsize(_format)
def __init__(self, f4, f5):
self.field4 = f4
self.field5 = f5
@classmethod
def from_data(cls, data):
return cls(*struct.unpack_from(cls._format, data))
@classmethod
def from_data_array(cls, data, count):
# construct a list of InnerStruct from data of known size
return [cls(*struct.unpack_from(cls._format, data[n*cls._size:(n+1)*cls._size])) for n in range(count)]
def __repr__(self):
return f'[{self.field4}, {self.field5}]'
class MyStruct:
_format = '<if' # format and size of serialized data
_size = struct.calcsize(_format)
def __init__(self, f1, f2):
self.field1 = f1
self.field2 = f2
self.field3 = MyInnerStruct.from_data_array(data[self._size:], f1)
@classmethod
def from_data(cls, data):
return cls(*struct.unpack_from(cls._format, data))
def __repr__(self):
return f'[{self.field1}, {self.field2}, {self.field3}]'
sock = socket.socket(type=socket.SOCK_DGRAM)
sock.bind(('', 5000))
data, addr = sock.recvfrom(40960)
received_struct = MyStruct.from_data(data)
print(received_struct) # now print complete received item.
(same output)