I'm calling a third party library written in C from a NetCore application. The problem is that in order to use this library, I first need to make a call and configure a complex struct that must later be passed to all subsequent calls.
void createCtx(modbus_t ** ctx)
{
*ctx = modbus_new_tcp("192.168.1.175", 502);
//configure the context here ....
int res = modbus_connect(*ctx);
}
int pollData(modbus_t * ctx)
{
//....
modbus_read_bits(ctx, addr, 1, tab_rp_bits);
//....
}
My approach would be to create the modbus_t object on the caller application (C#), configure it by calling createCtx once and then pass it to pollData at regular intervals. I've read about StructLayout but since I don't need to access the data in the modbusContext object I would just like to reserve a chunk of memory for the context and let C# be oblivious of what's inside.
This is what I've come up with
static IntPtr modbusContext;
static class ModbusDriver
{
[DllImport("modbusdriver",EntryPoint = "createCtx")]
public static extern void CreateCtx(ref IntPtr modbusContext);
[DllImport("modbusdriver",EntryPoint = "pollData")]
public static extern uint PollData(IntPtr modbusContext)
}
static void Main(string[] args)
{
int ctxSize = ModbusDriver.GetCtxSize();
modbusContext = Marshal.AllocHGlobal(80 * Marshal.SizeOf(typeof(byte))); //<--- 80 is the result of sizeof(modbus_t)
ModbusDriver.CreateCtx(ref modbusContext);
while(true)
{
ModbusDriver.PollData(modbusContext);
Thread.Sleep(1000);
}
}
}
All this seems to work, but it doesn't really feel right, especially because the modbus_t structure is quite complex
struct modbus_t {
/* Slave address */
int slave;
/* Socket or file descriptor */
int s;
int debug;
int error_recovery;
struct timeval response_timeout;
struct timeval byte_timeout;
struct timeval indication_timeout;
const modbus_backend_t *backend;
void *backend_data;
};
typedef struct _modbus_backend {
unsigned int backend_type;
unsigned int header_length;
unsigned int checksum_length;
unsigned int max_adu_length;
int (*set_slave) (modbus_t *ctx, int slave);
int (*build_request_basis) (modbus_t *ctx, int function, int addr,
int nb, uint8_t *req);
int (*build_response_basis) (sft_t *sft, uint8_t *rsp);
int (*prepare_response_tid) (const uint8_t *req, int *req_length);
int (*send_msg_pre) (uint8_t *req, int req_length);
ssize_t (*send) (modbus_t *ctx, const uint8_t *req, int req_length);
int (*receive) (modbus_t *ctx, uint8_t *req);
ssize_t (*recv) (modbus_t *ctx, uint8_t *rsp, int rsp_length);
int (*check_integrity) (modbus_t *ctx, uint8_t *msg,
const int msg_length);
int (*pre_check_confirmation) (modbus_t *ctx, const uint8_t *req,
const uint8_t *rsp, int rsp_length);
int (*connect) (modbus_t *ctx);
void (*close) (modbus_t *ctx);
int (*flush) (modbus_t *ctx);
int (*select) (modbus_t *ctx, fd_set *rset, struct timeval *tv, int msg_length);
void (*free) (modbus_t *ctx);
} modbus_backend_t;
So my question is, is my approach correct? Specifically, modbus_t contains pointers. I managed to retain the modbus_t struct in C# and it seems to work, but is it really safe to assume that the memory referenced by the pointers contained in the struct will not be corrupted between calls? It doesn't feel right.