我正在从NetCore应用程序调用用C编写的第三方库。 问题在于,为了使用该库,我首先需要进行调用并配置一个复杂的结构,该结构随后必须传递给所有后续调用。
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);
//....
}
我的方法是在调用方应用程序(C#)上创建modbus_t对象,通过一次调用createCtx对其进行配置,然后定期将其传递给pollData。 我已经读过有关StructLayout的文章,但是由于我不需要访问modbusContext对象中的数据,所以我只想为上下文保留一块内存,并让C#忽略其中的内容。
这就是我想出的
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);
}
}
}
所有这些似乎都有效,但是感觉并不正确,尤其是因为modbus_t结构非常复杂
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;
所以我的问题是,我的方法正确吗? 具体来说,modbus_t包含指针。我设法在C#中保留了modbus_t结构,它似乎可以正常工作,但是假设结构中包含的指针引用的内存在两次调用之间不会被破坏,这真的很安全吗?感觉不对。
答案 0 :(得分:1)
只要您不想修改数据,就可以安全地将数据包装为void *或IntPtr。您通过AllocHGlobal分配数据,该数据通过LocalAlloc从本地进程堆返回数据,而该本地处理堆最终调用RtlAllocateHeap。对于C#,该指针是一个黑匣子,并且永远不会对其进行写入或修改。只要您不尽早释放数据,一切都会好起来的。
C编程规则适用:您需要手动管理内存,并注意谁拥有数据以及谁负责删除数据。
仅当您尝试将指针映射到托管类时,才会出现问题,该托管类部分地尝试提供对某些字段的访问权限。然后,需要注意结构成员对齐方式与C头文件中的对齐方式相同,并且需要正确获取要跳过的数据的偏移量。然后,您可以将IntPtr转换为带有不安全代码的C#结构作为指针,如果您正确地获得了偏移量和倾斜度,则该代码才可以工作。
如果C ++类是包含STL数据类型的头文件的一部分,则情况完全不同。这些内容根本无法包装,因为成员对齐取决于当前编译器附带的STL版本,这在私有成员字段之间施加了紧密的约定,可以在C ++ / STL版本之间进行更改。为此,您将需要一个C包装器,该包装器将帮助程序方法包装为具有普通结构的普通C方法,该结构在内部调用C ++方法。托管C ++是一种过时的技术,不应再使用。
总结:您当前的方法很好,并且可以工作。如果您想访问修改字节blob中的数据,它将变得更加繁琐,但是一旦您知道如何在C#中声明仅包含基本类型(不包含字符串,字典或托管堆结构的指针)的包装器结构,这也是可行的。 / p>