高级语言调用C接口(三)结构体(1)-Java、C#

上一篇简单说了回调函数,其中回调函数包含更复杂的参数,结构体等,所以,本系列的第三篇先说一下结构体,由于内容会比较多,本篇先说Java(Jna调用、Jni比较复杂,如有需要,后面再单独来将)、C#两种比较常见的开发语言。

先来看C层头文件对结构体的定义。

    typedef struct _stGoodsInfo
    {
        const char* goodsId = NULL; //商品ID
        const char* goodsName = NULL; //商品名称
        const char* quantity = NULL; //数量
        const char* price = NULL; //单价(分)
        const char* goodsCategory = NULL; //商品分类
        const char* body = NULL; //商品说明
        const char* discount = NULL; //折扣
        const char* unit = NULL; //单位
    }stGoodsInfo;

    typedef struct _stOrderInfo
    {
        const char* merOrderId = NULL; //订单ID
        const char* srcReserve = NULL; //请求系统预留字段
        const char* orderDesc = NULL; //订单描述
        const char* totalAmount = NULL; //订单金额
        int goodsNum = 0; //商品数量
        stGoodsInfo* pstGoodsInfo = NULL; //商品信息
        const char* attachedData = NULL; //商户附加数据
    }stOrderInfo;

其中订单信息中还包含了一个结构体指针商品列表。

接口参数定义,这里的接口定义为指针主要是没了节省栈空间,如果结构体比较大,占用的内存会比较多,传参会翻倍,用指针传参固定占用4或8字节。

void Pay(stOrderInfo* pstOrderInfo);

调用也比较简单

    stOrderInfo orderInfo;
    orderInfo.merOrderId = "SD000000000000001";
    orderInfo.srcReserve = "aaaaaaaaaaaaaa";
    orderInfo.orderDesc = "Test";
    orderInfo.totalAmount = "10.00";
    orderInfo.attachedData = "attachedData test";
    orderInfo.goodsNum = 2;
    orderInfo.pstGoodsInfo = new stGoodsInfo[2];
    orderInfo.pstGoodsInfo[0].goodsId = "SDGOOD000001";
    orderInfo.pstGoodsInfo[0].goodsName = "哇哈哈矿泉水";
    orderInfo.pstGoodsInfo[0].quantity = "2";
    orderInfo.pstGoodsInfo[0].price = "2.00";
    orderInfo.pstGoodsInfo[0].goodsCategory = "食品饮料";
    orderInfo.pstGoodsInfo[0].body = "饮料";
    orderInfo.pstGoodsInfo[0].discount = "0";
    orderInfo.pstGoodsInfo[0].unit = "瓶";
    orderInfo.pstGoodsInfo[1].goodsId = "SDGOOD000002";
    orderInfo.pstGoodsInfo[1].goodsName = "百岁山矿泉水";
    orderInfo.pstGoodsInfo[1].quantity = "2";
    orderInfo.pstGoodsInfo[1].price = "3.00";
    orderInfo.pstGoodsInfo[1].goodsCategory = "食品饮料";
    orderInfo.pstGoodsInfo[1].body = "饮料";
    orderInfo.pstGoodsInfo[1].discount = "0";
    orderInfo.pstGoodsInfo[1].unit = "瓶";

    PalmPay(&orderInfo);
    delete[] orderInfo.pstGoodsInfo;

接下来我们来看一下Java的Jna改如何调用。

首先,定义结构体

package com.saintdeem;

import java.util.Arrays;
import java.util.List;

import com.sun.jna.Structure;

public class stGoodsInfo  extends Structure{
	public String goodsId; //商品ID
	public String goodsName; //商品名称
	public String quantity; //数量
	public String price; //单价(分)
	public String goodsCategory; //商品分类
	public String body; //商品说明
	public String discount; //折扣
	public String unit; //单位
	public static class ByReference extends stGoodsInfo implements Structure.ByReference {

	}
	public static class ByValue extends stGoodsInfo implements
	Structure. ByValue {
	}
	

    /**
     * 重写getFieldOrder获取字段列表
     * @return
     */
    @Override
    protected List<String> getFieldOrder() {
        return Arrays.asList(
        		"goodsId"
        		, "goodsName"
        		, "quantity"
        		, "price"
        		, "goodsCategory"
        		, "body"
        		, "discount"
        		, "unit"
        		);
    }

}

 

package com.saintdeem;

import java.util.Arrays;
import java.util.List;

import com.sun.jna.Structure;

public class stOrderInfo extends Structure {
	public String merOrderId; //订单ID
    public String srcReserve; //请求系统预留字段
    public String orderDesc; //订单描述
    public String totalAmount; //订单金额
    public int goodsNum = 0; //商品数量
    public stGoodsInfo.ByReference pstGoodsInfo; //商品信息
    public String attachedData; //商户附加数据
	public static class ByReference extends stOrderInfo implements Structure.ByReference {

	}
	public static class ByValue extends stOrderInfo implements
	Structure. ByValue {
	}

    /**
     * 重写getFieldOrder获取字段列表
     * @return
     */
    @Override
    protected List<String> getFieldOrder() {
        return Arrays.asList(
        		"merOrderId"
        		, "srcReserve"
        		, "orderDesc"
        		, "totalAmount"
        		, "goodsNum"
        		, "pstGoodsInfo"
        		, "attachedData"
        		);
    }

}

这里要说一下,重写getFieldOrder,这里是个很大的坑,Jna要求字段顺序与C结构体完全一致,而默认的方法不能保正这一点,如果不显式重写,运行时会出错。

Jna可以直接利用ByReference来定义结构体指针,还是比较方便的,接下来看接口定义。

void  Pay(stOrderInfo[] pstOrderInfo);

调用示例如下

        stOrderInfo orderInfo[] = new stOrderInfo[1];
        orderInfo[0] = new stOrderInfo();
	    orderInfo[0].merOrderId = "SD000000000000001";
	    orderInfo[0].srcReserve = "aaaaaaaaaaaaaa";
	    orderInfo[0].orderDesc = "Test";
	    orderInfo[0].totalAmount = "10.00";
	    orderInfo[0].attachedData = "attachedData test";
	    orderInfo[0].goodsNum = 2;
	    orderInfo[0].pstGoodsInfo = new stGoodsInfo.ByReference();
	    stGoodsInfo goods[] = (stGoodsInfo[])orderInfo[0].pstGoodsInfo.toArray(orderInfo[0].goodsNum);
	    goods[0].goodsId = "SDGOOD000001";
	    goods[0].goodsName = "哇哈哈矿泉水";
	    goods[0].quantity = "2";
	    goods[0].price = "2.00";
	    goods[0].goodsCategory = "食品饮料";
	    goods[0].body = "饮料";
	    goods[0].discount = "0";
	    goods[0].unit = "瓶";
	    goods[1].goodsId = "SDGOOD000002";
	    goods[1].goodsName = "百岁山矿泉水";
	    goods[1].quantity = "2";
	    goods[1].price = "3.00";
	    goods[1].goodsCategory = "食品饮料";
	    goods[1].body = "饮料";
	    goods[1].discount = "0";
	    goods[1].unit = "瓶";
		Pay(orderInfo);

下面来看C#的定义和调用

    [StructLayout(LayoutKind.Sequential)]
    public struct GoodsInfo
    {
        public string goodsId; //商品ID
        public string goodsName; //商品名称
        public string quantity; //数量
        public string price; //单价(分)
        public string goodsCategory; //商品分类
        public string body; //商品说明
        public string discount; //折扣
        public string unit; //单位
    }
    [StructLayout(LayoutKind.Sequential)]
    public struct OrderInfo
    {
        public string merOrderId; //订单ID
        public string srcReserve; //请求系统预留字段
        public string orderDesc; //订单描述
        public string totalAmount; //订单金额
        public int goodsNum; //商品数量
        public GoodsInfo[] pstGoodsInfo; //商品信息
        public string attachedData; //商户附加数据
    }

接口定义

    [StructLayout(LayoutKind.Sequential)]
    public struct OrderInfoTmp
    {
        public string merOrderId; //订单ID
        public string srcReserve; //请求系统预留字段
        public string orderDesc; //订单描述
        public string totalAmount; //订单金额
        public int goodsNum; //商品数量
        public IntPtr pstGoodsInfo; //商品信息
        public string attachedData; //商户附加数据
    }
    [StructLayout(LayoutKind.Sequential)]
    public struct GoodsInfoTmp
    {
        public IntPtr goodsId; //商品ID
        public IntPtr goodsName; //商品名称
        public IntPtr quantity; //数量
        public IntPtr price; //单价(分)
        public IntPtr goodsCategory; //商品分类
        public IntPtr body; //商品说明
        public IntPtr discount; //折扣
        public IntPtr unit; //单位
    }
    public static extern void Pay(ref OrderInfoTmp pstOrderInfo);

接口定义可以看出,C#有2重定义,C#调用会比Java更复杂,由于C结构体中定义的char*并非真正的字符串,而是指针,C#的string与char*并不能直接转换,加上汉字乱码问题,所以,调用C层接口前,需要做如下封装处理。

        public void Pay(ref OrderInfo pstOrderInfo)
        {
            OrderInfoTmp orderInfoTmp = new OrderInfoTmp();
            orderInfoTmp.attachedData = pstOrderInfo.attachedData;
            orderInfoTmp.goodsNum = pstOrderInfo.goodsNum;
            orderInfoTmp.merOrderId = pstOrderInfo.merOrderId;
            orderInfoTmp.orderDesc = pstOrderInfo.orderDesc;
            orderInfoTmp.srcReserve = pstOrderInfo.srcReserve;
            orderInfoTmp.totalAmount = pstOrderInfo.totalAmount;
            orderInfoTmp.pstGoodsInfo = IntPtr.Zero;
            GoodsInfoTmp[] goodsInfo = null;
            if (orderInfoTmp.goodsNum > 0)
            {
                orderInfoTmp.pstGoodsInfo = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(GoodsInfo)) * orderInfoTmp.goodsNum);
                goodsInfo = new GoodsInfoTmp[orderInfoTmp.goodsNum];
                for(int i = 0;i < orderInfoTmp.goodsNum;i++)
                {
                    goodsInfo[i].goodsId = Utf8StringToPtr(pstOrderInfo.pstGoodsInfo[i].goodsId);
                    goodsInfo[i].goodsName = Utf8StringToPtr(pstOrderInfo.pstGoodsInfo[i].goodsName);
                    goodsInfo[i].quantity = Utf8StringToPtr(pstOrderInfo.pstGoodsInfo[i].quantity);
                    goodsInfo[i].price = Utf8StringToPtr(pstOrderInfo.pstGoodsInfo[i].price);
                    goodsInfo[i].goodsCategory = Utf8StringToPtr(pstOrderInfo.pstGoodsInfo[i].goodsCategory);
                    goodsInfo[i].body = Utf8StringToPtr(pstOrderInfo.pstGoodsInfo[i].body);
                    goodsInfo[i].discount = Utf8StringToPtr(pstOrderInfo.pstGoodsInfo[i].discount);
                    goodsInfo[i].unit = Utf8StringToPtr(pstOrderInfo.pstGoodsInfo[i].unit);
                    Marshal.StructureToPtr(goodsInfo[i], (IntPtr)(orderInfoTmp.pstGoodsInfo.ToInt64() + Marshal.SizeOf(typeof(GoodsInfo)) * i), false);
                }
            }
            PalmPay(handle, place, ref orderInfoTmp);
            if (orderInfoTmp.pstGoodsInfo != IntPtr.Zero)
            {
                for (int i = 0; i < orderInfoTmp.goodsNum; i++)
                {
                    FreeHGlobal(goodsInfo[i].goodsId);
                    FreeHGlobal(goodsInfo[i].goodsName);
                    FreeHGlobal(goodsInfo[i].quantity);
                    FreeHGlobal(goodsInfo[i].price);
                    FreeHGlobal(goodsInfo[i].goodsCategory);
                    FreeHGlobal(goodsInfo[i].body);
                    FreeHGlobal(goodsInfo[i].discount);
                    FreeHGlobal(goodsInfo[i].unit);
                }
                Marshal.FreeHGlobal(orderInfoTmp.pstGoodsInfo);
            }
        }

应用层调用

            OrderInfo orderInfo = new OrderInfo();
            orderInfo.merOrderId = "SD000000000000001";
            orderInfo.srcReserve = "aaaaaaaaaaaaaa";
            orderInfo.orderDesc = "Test";
            orderInfo.totalAmount = "10.00";
            orderInfo.attachedData = "attachedData test";
            orderInfo.goodsNum = 2;
            orderInfo.pstGoodsInfo = new GoodsInfo[orderInfo.goodsNum];
            orderInfo.pstGoodsInfo[0].goodsId = "SDGOOD000001";
            orderInfo.pstGoodsInfo[0].goodsName = "哇哈哈矿泉水";
            orderInfo.pstGoodsInfo[0].quantity = "2";
            orderInfo.pstGoodsInfo[0].price = "2.00";
            orderInfo.pstGoodsInfo[0].goodsCategory = "食品饮料";
            orderInfo.pstGoodsInfo[0].body = "饮料";
            orderInfo.pstGoodsInfo[0].discount = "0";
            orderInfo.pstGoodsInfo[0].unit = "瓶";
            orderInfo.pstGoodsInfo[1].goodsId = "SDGOOD000002";
            orderInfo.pstGoodsInfo[1].goodsName = "百岁山矿泉水";
            orderInfo.pstGoodsInfo[1].quantity = "2";
            orderInfo.pstGoodsInfo[1].price = "3.00";
            orderInfo.pstGoodsInfo[1].goodsCategory = "食品饮料";
            orderInfo.pstGoodsInfo[1].body = "饮料";
            orderInfo.pstGoodsInfo[1].discount = "0";
            orderInfo.pstGoodsInfo[1].unit = "瓶";
            Pay(ref orderInfo);

封装接口的处理过程也可以和最终接口合并为一个,但代码里比较多,不建议接口实现太复杂,分成2个会相对清晰一些,并没有其它意义。

以上是Java(Jna)和C#的调用示例,篇幅有限,示例不能展示所有可能遇到的情况,只能把我个人遇到的问题已经解决方法融入进去。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿捏利

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值