首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >将复杂数据结构从C#传递到本机dll

将复杂数据结构从C#传递到本机dll
EN

Stack Overflow用户
提问于 2019-01-16 05:34:58
回答 1查看 152关注 0票数 2

我正在从一个NetCore应用程序调用一个用C编写的第三方库。问题是,为了使用这个库,我首先需要进行一个调用并配置一个复杂的结构,该结构必须在以后传递给所有后续调用。

代码语言:javascript
复制
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#忽略里面的内容。

这就是我想出来的

代码语言:javascript
复制
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结构相当复杂

代码语言:javascript
复制
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结构,它似乎可以工作,但是假设结构中包含的指针引用的内存在两次调用之间不会损坏真的安全吗?感觉不太对劲。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2019-01-16 06:13:12

只要您不想修改数据,就可以安全地将数据包装为空*或IntPtr。您通过AllocHGlobal分配数据,它通过最终调用RtlAllocateHeap的LocalAlloc从本地进程堆返回数据。对于C#来说,该指针是一个黑盒,永远不会写入或修改它。只要你不提前释放数据,一切都会好起来的。

C编程规则适用:您需要手动管理内存,并注意谁拥有数据以及谁负责删除数据。

只有当您尝试将该指针映射到部分尝试提供对某些字段的访问的托管类时,才会出现问题。然后,您需要注意结构成员对齐方式与C头文件中的相同,并且需要为要跳过的数据获得正确的偏移量。然后,您可以将IntPtr转换为带有不安全代码的指针形式的C#结构,如果您获得了正确的偏移量和调整,则应该可以正常工作。

如果C++类是包含STL数据类型的头文件的一部分,情况就完全不同了。这些东西根本不是可包装的,因为成员对齐依赖于当前编译器附带的STL版本,这会在私有成员字段之间强加一个紧密的协定,该协定可能会在C++/STL版本之间发生变化。为此,您需要一个C包装器,它将助手方法包装为具有普通结构的普通C方法,然后在内部调用C++方法。托管C++是一项相当过时的技术,不应该再使用了。

总而言之:您目前的方法很好,并且将会起作用。如果你想访问修改字节blob中的数据,这将会变得更多的工作,但是一旦你知道如何在C#中声明只包含原始类型的包装器结构(不包含字符串、字典或指向托管堆结构的指针),这也是可行的。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/54207177

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档