【JNA】JAVA使用JNA调用C++ dll文件(2)JNA 对接代理DLL
上面的文章已经实现了C++代理层,封装了代理层的头文件 CTPProxy.h
,下面就使用JNA对接这个代理DLL文件 CTPProxy.dll
JNA 对接步骤
1、添加 JNA 依赖
在 Maven 项目的 pom.xml 中添加依赖:
<dependency><groupId>net.java.dev.jna</groupId><artifactId>jna</artifactId><version>5.13.0</version> <!-- 最新版本可到 Maven 仓库查询 -->
</dependency>
2、将 CTPProxy.dll
和CTP的原始文件一起放在工程中
3、创建代理DLL接口映射
实现的函数和CTPProxy.dll
暴露的函数要完全匹配
import com.sun.jna.*;
// 代理DLL接口映射
public interface CTPMdApi extends Library {CTPMdApi INSTANCE = Native.load("CTPProxy01", CTPMdApi.class);// 创建行情API实例Pointer CreateMdApi(String pszFlowPath, boolean bIsUsingUdp, boolean bIsMulticast);// 初始化API实例void MdApi_Init(Pointer api);// 释放API实例资源void MdApi_Release(Pointer api);// 注册回调接口实例void MdApi_RegisterSpi(Pointer api, Pointer spi);// 注册前置机地址int MdApi_RegisterFront(Pointer api, String frontAddress);// 注册名字服务器地址void MdApi_RegisterNameServer(Pointer api, String nsAddress);// 发起用户登录请求String MdApi_ReqUserLogin(Pointer api, String brokerId, String userId, String password, int requestId);// 发起用户登出请求int MdApi_ReqUserLogout(Pointer api, String brokerId, String userId, int requestId);// 订阅行情数据int MdApi_SubscribeMarketData(Pointer api, String[] instrumentIds, int count);// 取消订阅行情数据int MdApi_UnSubscribeMarketData(Pointer api, String[] instrumentIds, int count);// 订阅询价数据int MdApi_SubscribeForQuoteRsp(Pointer api, String[] instrumentIds, int count);// 取消订阅询价数据int MdApi_UnSubscribeForQuoteRsp(Pointer api, String[] instrumentIds, int count);// 获取API版本号String MdApi_GetApiVersion();// 创建行情回调实例Pointer CreateMdSpi(CTPMdCallbacks callbacks);// 释放行情回调实例void ReleaseMdSpi(Pointer spi);
}
4、创建回调结构体(对应C的MdSpiCallbacks)
import com.sun.jna.Callback;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;import java.util.Arrays;
import java.util.List;// 回调结构体(对应C的MdSpiCallbacks)
public class CTPMdCallbacks extends Structure {// 回调函数接口定义// 前置机连接成功public interface OnFrontConnectedCallback extends Callback {void invoke(Pointer userData);}// 前置机断开连接public interface OnFrontDisconnectedCallback extends Callback {void invoke(Pointer userData, int nReason);}// 心跳超时警告public interface OnHeartBeatWarningCallback extends Callback {void invoke(Pointer userData, int nTimeLapse);}// 登录响应public interface OnRspUserLoginCallback extends Callback {void invoke(Pointer userData, String errorMsg, int errorId);}public interface OnRspUserLogoutCallback extends Callback {void invoke(Pointer userData, String errorMsg, int errorId);}// 订阅行情响应public interface OnRspSubMarketDataCallback extends Callback {void invoke(Pointer userData, String instrumentId, String errorMsg, int errorId, boolean bIsLast);}// 取消订阅行情响应public interface OnRspUnSubMarketDataCallback extends Callback {void invoke(Pointer userData, String instrumentId, String errorMsg, int errorId, boolean bIsLast);}// 取消订阅询价响应public interface OnRspSubForQuoteRspCallback extends Callback {void invoke(Pointer userData, String instrumentId, String errorMsg, int errorId, boolean bIsLast);}// 取消订阅询价响应public interface OnRspUnSubForQuoteRspCallback extends Callback {void invoke(Pointer userData, String instrumentId, String errorMsg, int errorId, boolean bIsLast);}// 深度行情推送public interface OnRtnDepthMarketDataCallback extends Callback {void invoke(Pointer userData, String instrumentId, double lastPrice, int volume,double askPrice1, int askVolume1, double bidPrice1, int bidVolume1, double settlementPrice,double upperLimitPrice,double lowerLimitPrice,String tradingDay);}// 订阅询价响应public interface OnRtnForQuoteRspCallback extends Callback {void invoke(Pointer userData, String instrumentId, String forQuoteSysId);}// 结构体字段(与C头文件对应)public OnFrontConnectedCallback OnFrontConnected;public OnFrontDisconnectedCallback OnFrontDisconnected;public OnHeartBeatWarningCallback OnHeartBeatWarning;public OnRspUserLoginCallback OnRspUserLogin;public OnRspUserLogoutCallback OnRspUserLogout;public OnRspSubMarketDataCallback OnRspSubMarketData;public OnRspUnSubMarketDataCallback OnRspUnSubMarketData;public OnRspSubForQuoteRspCallback OnRspSubForQuoteRsp;public OnRspUnSubForQuoteRspCallback OnRspUnSubForQuoteRsp;public OnRtnDepthMarketDataCallback OnRtnDepthMarketData;public OnRtnForQuoteRspCallback OnRtnForQuoteRsp;public Pointer userData;// 字段顺序(必须与C结构体一致)@Overrideprotected List<String> getFieldOrder() {return Arrays.asList("OnFrontConnected","OnFrontDisconnected","OnHeartBeatWarning","OnRspUserLogin","OnRspUserLogout","OnRspSubMarketData","OnRspUnSubMarketData","OnRspSubForQuoteRsp","OnRspUnSubForQuoteRsp","OnRtnDepthMarketData","OnRtnForQuoteRsp","userData");}
}
5、初始化并运行
- 创建行情API实例
- 注册回调
- 注册前置机地址
- 登录
- 订阅行情
- 买入报单录入:定时发送
- 行情推送:回调
- 登出
import com.sun.jna.Pointer;
/*** 期货行情订阅接口** 1. 创建行情API实例* 2. 注册回调* 3. 注册前置机地址* 4. 登录* 5. 订阅行情* 6. 买入报单录入:定时发送* 7. 行情推送:回调* 8. 登出**/
public class CTPMdDemo {// 注意:实际使用时需要替换为官方提供的服务器地址、端口和认证信息static String frontAddress = "tcp://180.166.xx.xx:51218";static String brokerid = "xxxx";static String username = "xxxx";static String password = "xxxx";public static void main(String[] args) {// 1. 创建回调结构体CTPMdCallbacks callbacks = new CTPMdCallbacks();initCallbacks(callbacks);Pointer spi = CTPMdApi.INSTANCE.CreateMdSpi(callbacks);// 2. 创建行情API实例(使用默认参数)Pointer api = CTPMdApi.INSTANCE.CreateMdApi("", false, false);// 3. 注册回调CTPMdApi.INSTANCE.MdApi_RegisterSpi(api, spi);// 4. 注册前置机地址int rst = CTPMdApi.INSTANCE.MdApi_RegisterFront(api, frontAddress);System.out.println("注册前置机地址:" + rst);// 5. 初始化APICTPMdApi.INSTANCE.MdApi_Init(api);System.out.println("初始化API成功");// 阻塞主线程,等待链接成功回调通知LockUtil.waitConnect();// 6. 登录(实际使用时替换为真实信息)String loginResult = CTPMdApi.INSTANCE.MdApi_ReqUserLogin(api, brokerid, username, password, 2);System.out.println("登录请求结果:" + loginResult);// 登录成功后查询行情LockUtil.waitLogin();// 7. 订阅行情(示例合约)String[] instruments = {"eb2510"};CTPMdApi.INSTANCE.MdApi_SubscribeMarketData(api, instruments, instruments.length);// 阻塞主线程(实际应用中需合理处理)try {Thread.sleep(600000); // 运行1分钟} catch (InterruptedException e) {e.printStackTrace();}// 8. 登出int logout = CTPMdApi.INSTANCE.MdApi_ReqUserLogout(api, brokerid, username, 2);// 9. 释放资源CTPMdApi.INSTANCE.MdApi_Release(api);CTPMdApi.INSTANCE.ReleaseMdSpi(spi);System.out.println("释放资源成功");}// 初始化回调函数private static void initCallbacks(CTPMdCallbacks callbacks) {callbacks.userData = Pointer.NULL; // 可传递Java对象指针// 前置机连接成功回调callbacks.OnFrontConnected = (userData) -> {// 发送信号通知主线程System.out.println("前置机链接成功");LockUtil.connected();};// 登录响应回调callbacks.OnRspUserLogin = (userData, errorMsg, errorId) -> {if (errorId == 0) {LockUtil.logined();System.out.println("登录成功:" + userData);} else {System.out.println("~~~登录失败:" + errorMsg + "(错误码:" + errorId + ")");}};callbacks.OnRspUserLogout = (userData, errorMsg, errorId) -> {System.out.println("登出:" + errorMsg + "(错误码:" + errorId + ")");};callbacks.OnRspSubMarketData = (userData, instrumentId, errorMsg, errorId, bIsLast) -> {System.out.println("订阅行情响应:" + instrumentId + " " + errorMsg + "(错误码:" + errorId + ")");};// 行情推送回调callbacks.OnRtnDepthMarketData = (userData, instrumentId, lastPrice, volume,askPrice1, askVolume1, bidPrice1, bidVolume1, settlementPrice,upperLimitPrice,lowerLimitPrice,tradingDay) -> {// 打印所有参数System.out.printf("行情推送 - 合约:%s,最新价:%s,成交量:%s,卖一:%s(%d),买一:%s(%d),结算价:%s,涨停价:%s,跌停价:%s,交易日:%s\n",instrumentId, lastPrice, volume, askPrice1, askVolume1, bidPrice1, bidVolume1, settlementPrice,upperLimitPrice,lowerLimitPrice,tradingDay);};callbacks.OnRspSubForQuoteRsp = (userData, instrumentId, errorMsg, errorId, bIsLast) -> {System.out.println("订阅询价响应:" + instrumentId + " " + errorMsg + "(错误码:" + errorId + ")");};// 其他回调按需实现...}
}
LockUtil.java
工具类
public class LockUtil {private static final Object lockConnect = new Object();private static final Object lockLogin = new Object();private static final Object lockAuth = new Object();public static void waitConnect() {synchronized (lockConnect) {try {lockConnect.wait(); //} catch (InterruptedException e) {e.printStackTrace();}}}public static void connected() {synchronized (lockConnect) {lockConnect.notifyAll();}}public static void waitLogin() {synchronized (lockLogin) {try {lockLogin.wait();} catch (InterruptedException e) {e.printStackTrace();}}}public static void logined() {synchronized (lockLogin) {lockLogin.notifyAll();}}
}
注意事项
- 平台依赖:
.dll
是 Windows 特有,Linux 对应.so
,macOS 对应.dylib
,跨平台需分别编译。 - 函数名和参数匹配:Java 映射的函数名和参数类型必须与 DLL 中的函数严格一致,否则会报
UnsatisfiedLinkError
。 - 数据类型映射:C++ 与 Java 的数据类型需严格对应(如char*→String,double→double),避免内存错误。
- Java 中的 int 对应 C 中的 jint
- Java 中的 String 需通过 JNI 函数(如GetStringUTFChars)转换为 C 字符串
- 线程安全:CTP 回调在独立线程中执行,Java 层需处理线程同步(如使用 synchronized)
- 动态库加载:确保 Java 能找到适配层的.dll/.so(通过-Djava.library.path指定路径)。
- DLL 依赖:若 .dll 依赖其他 DLL,需确保这些依赖也能被找到。