上一篇简单说了回调函数,其中回调函数包含更复杂的参数,结构体等,所以,本系列的第三篇先说一下结构体,由于内容会比较多,本篇先说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#的调用示例,篇幅有限,示例不能展示所有可能遇到的情况,只能把我个人遇到的问题已经解决方法融入进去。