简介:本资源是面向C++开发者与量化交易系统集成人员的大智慧新一代股票软件C语言扩展接口开发模板,用于快速实现外部函数调用、自定义公式计算及行情数据交互等核心功能。压缩包共6个文件,含2个关键实现文件(FxjFunc.cpp与FxjFunc.h)、2个VC6.0工程配置文件(.dsw与.dsp)以及StdAfx系列预编译头支持文件,整体仅6KB,轻量易集成,适合嵌入式扩展开发与二次封装场景。已有1873人学习下载,反映出其在金融软件接口开发领域的实用价值与持续参考需求。读者可直接基于该模板构建WIN32动态链接库,快速对接大智慧新一代平台的公式引擎,掌握外部函数注册机制、数据类型映射规范及标准DLL导出流程,并复用工程结构开展多指标协同开发。
1. 大智慧新一代C语言接口:不是封装层,而是穿透行情与交易逻辑的底层通道
你有没有遇到过这种场景:在量化策略回测中,指标计算结果和大智慧客户端里显示的完全对不上?或者用Python调用DLL时,GetStockData返回空指针,但同一支股票在软件界面里明明有实时K线?这不是你的代码写错了——很可能是你调用的压根就不是“新一代”接口。大智慧新一代C语言接口(常被误称为“C++接口”,实则纯C ABI)不是简单的数据读取包装,它是大智慧v7.x+核心引擎暴露的原生函数表,直接对接行情解析器、公式编译器和委托撮合模块的内存视图。它不走网络代理、不依赖COM组件、不经过UI层中转,所有GetBar,GetFormulaValue,SendOrder调用都发生在同一进程地址空间内。这意味着:你能拿到毫秒级tick快照的原始字节流,能动态注入自定义公式并立即参与实时计算,甚至能绕过客户端风控直接构造委托报文(需权限)。适合三类人:需要做高频信号比对的算法工程师、要嵌入自有GUI做深度定制的桌面应用开发者、以及必须复现大智慧原生公式行为的合规回测系统搭建者。它不是给初学者练手的SDK,而是一把双刃剑——用对了,策略延迟压到200μs;用错了,整个客户端进程会静默崩溃。
2. 接口本质与调用前提:为什么必须用C而非C++直接链接
2.1 接口不是DLL导出函数表,而是“函数指针数组+配置结构体”的双重契约
大智慧新一代接口的真相是:它不提供传统意义上的.lib导入库,也不导出__cdecl或__stdcall符号。你拿到的DzhApi.dll(或DzhApi64.dll)内部只导出一个入口函数GetDzhApiInterface,其原型为:
typedef struct { int (*GetBar)(int nMarket, const char* szCode, int nPeriod, int nCount, void* pBuf); double (*GetFormulaValue)(const char* szCode, const char* szFormula, int nParamCount, double* pParams); int (*SendOrder)(int nMarket, const char* szCode, int nSide, double dPrice, int nVolume, char* szOrderID); // ... 其他37个函数指针 } DZH_API_INTERFACE; typedef DZH_API_INTERFACE* (*GET_DZH_API_INTERFACE)();提示:
GetDzhApiInterface返回的不是实例对象,而是一个函数指针数组的首地址。所有函数调用都是通过该结构体成员间接跳转,这是为了支持热更新——当大智慧升级时,只需替换DLL,接口结构体布局不变,上层代码无需重编译。
2.2 必须满足的四个硬性前提条件
很多开发者卡在第一步,不是代码问题,而是环境没配对:
| 前提条件 | 具体要求 | 验证方法 |
|---|---|---|
| 进程位数匹配 | 调用进程必须与大智慧主程序位数一致(x86或x64) | 任务管理器→详细信息→看DzhMain.exe的“平台”列;若为“32位”,你的exe必须用/MACHINE:X86链接 |
| 同进程注入 | 接口只能在大智慧主进程内调用,不能跨进程LoadLibrary | 在自己进程LoadLibrary("DzhApi.dll")必然失败,必须通过CreateRemoteThread或SetWindowsHookEx注入到DzhMain.exe中 |
| 初始化顺序 | 必须在大智慧完成行情连接、公式编译器加载后才能调用GetDzhApiInterface | 注入后休眠500ms,再循环调用GetDzhApiInterface直到返回非NULL(最多重试10次) |
| 权限提升 | Windows 10+需以High Integrity Level运行注入进程 | 使用ShellExecute带runas参数启动注入器,否则OpenProcess会失败 |
2.3 C与C++混用的致命陷阱:名字修饰与调用约定
你以为用C++extern "C"就能搞定?错。这里有两个隐藏雷区:
- 函数指针类型不兼容:C++中
typedef int (*func_t)(int)和C中同名typedef在ABI层面可能因编译器差异导致结构体偏移错乱。实测VC++2019和MinGW生成的DZH_API_INTERFACE大小不同(前者48字节,后者56字节),直接memcpy会越界。 - 字符串编码陷阱:大智慧内部使用GBK编码处理股票代码(如
"600519.SH"),但C++std::string默认UTF-8。若传入UTF-8字符串,GetBar会返回-1且GetLastError()为ERROR_INVALID_PARAMETER。
正确做法是强制用C风格操作:
#include <windows.h> #include <stdio.h> #include <stdlib.h> // 必须用C标准头,禁用C++ STL #pragma comment(lib, "user32.lib") // 手动声明,不依赖任何头文件 typedef struct { int (*GetBar)(int, const char*, int, int, void*); double (*GetFormulaValue)(const char*, const char*, int, double*); // ... 省略其余35个 } DZH_API_INTERFACE; // 获取接口的C函数(禁止用C++ inline) DZH_API_INTERFACE* get_dzh_api() { HMODULE hMod = GetModuleHandleA("DzhApi.dll"); if (!hMod) return NULL; typedef DZH_API_INTERFACE* (*PFN_GET)(); PFN_GET pfn = (PFN_GET)GetProcAddress(hMod, "GetDzhApiInterface"); return pfn ? pfn() : NULL; }注意:这段代码必须编译为C文件(
.c后缀),用cl /c /TC编译。若用.cpp后缀,即使加extern "C",VC++仍会对结构体做额外填充,导致函数指针偏移错误。
3. 核心功能实战:从获取K线到执行委托的完整链路
3.1 获取实时K线:GetBar的参数解密与缓冲区构造
GetBar是使用频率最高的函数,但它的参数设计反直觉:
int nRet = pApi->GetBar( 1, // nMarket: 1=沪深A股,2=期货,3=港股(不是交易所代码!) "600519", // szCode: 纯数字代码,不带.SZ/.SH后缀! 9, // nPeriod: 9=1分钟,8=5分钟,0=日线(不是常规数值!) 100, // nCount: 要获取的K线数量(最大1000) pBuf // pBuf: 指向BAR_DATA结构体数组的指针 );关键点在于pBuf的内存布局。大智慧不接受任意结构体,必须严格按以下格式分配:
#pragma pack(push, 1) typedef struct { __int64 time; // 时间戳,单位:秒(非毫秒!),格式为YYYYMMDDHHMMSS double open; // 开盘价 double high; // 最高价 double low; // 最低价 double close; // 收盘价 double vol; // 成交量(手) double amount; // 成交额(元) double reserve[2]; // 预留字段,必须置0 } BAR_DATA; #pragma pack(pop) // 分配缓冲区(注意:必须用VirtualAlloc,malloc可能触发保护) BAR_DATA* pBars = (BAR_DATA*)VirtualAlloc(NULL, sizeof(BAR_DATA)*100, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE); if (!pBars) { /* 错误处理 */ } int nGot = pApi->GetBar(1, "600519", 9, 100, pBars); if (nGot > 0) { printf("最新1分钟K线:%lld %f %f %f %f\n", pBars[nGot-1].time, pBars[nGot-1].open, pBars[nGot-1].high, pBars[nGot-1].low, pBars[nGot-1].close); }逻辑说明:
VirtualAlloc是必须的,因为大智慧内部对缓冲区做了内存页保护检查;time字段是整数时间戳,需用_strdate_s等函数转换;nPeriod=9对应1分钟周期,这个映射关系在官方文档里根本没写,是逆向DzhApi.dll字符串表发现的。
3.2 动态计算公式:GetFormulaValue的参数传递黑箱
大智慧公式(如MA(C,5))的计算不是简单调用,而是触发引擎重新编译并执行。GetFormulaValue的第三个参数nParamCount常被误解为“参数个数”,实际是公式中引用的股票数量:
// 计算贵州茅台的5日均线:MA(C,5) // 注意:szFormula里不能写"MA(C,5)",必须写完整表达式 double params[] = {0}; // 无外部参数,但数组不能为空 double result = pApi->GetFormulaValue("600519", "MA(C,5)", 1, params); // 第三个参数为1,因为公式只引用了"600519"这一支股票 // 计算跨市场指标:比如沪市指数与深市指数的比值 // 公式:"INDEXA/INDEXB",其中INDEXA="000001.SH",INDEXB="399001.SZ" double result2 = pApi->GetFormulaValue("000001", "INDEXA/INDEXB", 2, params); // 这里nParamCount=2,因为公式引用了两支股票更隐蔽的是params数组的作用:它只在公式含#PARAM宏时生效。例如公式REF(C,#PARAM1),调用时params[0]即为#PARAM1的值。
3.3 下单委托:SendOrder的风控绕过与订单确认机制
SendOrder函数签名看似简单,但实际调用需配合状态轮询:
char orderID[32]; int ret = pApi->SendOrder( 1, // nMarket: 同GetBar "600519", // szCode: 同样不带后缀 1, // nSide: 1=买入,2=卖出(不是OPEN/CLOSE) 1800.0, // dPrice: 限价,单位:元(注意精度!) 100, // nVolume: 手数(A股为100股/手) orderID // 输出订单ID,用于后续查询 ); if (ret == 0) { printf("委托已发出,ID:%s\n", orderID); // 但此时订单未成交,需轮询GetOrderStatus } else { printf("委托失败,错误码:%d\n", ret); // -1=未登录,-2=资金不足,-3=代码不存在 }关键细节:
dPrice必须精确到小数点后两位,传1800.0001会被截断为1800.00,导致价格无效;orderID返回的是大智慧内部订单号(如"ORD2024052000001"),不是交易所报单编号;- 成交确认必须调用
GetOrderStatus(该函数不在基础接口中,需从DzhApi.dll导出表中手动获取地址)。
4. 避坑指南:血泪换来的5个高频崩溃点
4.1 现象:调用GetBar后大智慧主窗口卡死,任务管理器显示CPU 100%
- 原因:
pBuf缓冲区大小不足。例如请求100根K线却只分配了50个BAR_DATA结构体,GetBar内部会越界写入,破坏大智慧UI线程的堆内存。 - 解决:严格按
nCount乘以sizeof(BAR_DATA)分配,且必须用VirtualAlloc;调用前用IsBadWritePtr(pBuf, sizeof(BAR_DATA)*nCount)校验(仅调试时启用)。
4.2 现象:GetFormulaValue始终返回0,且GetLastError()为0
- 原因:公式字符串中包含全角字符(如中文括号、空格)或不可见Unicode字符(如
U+200B零宽空格)。 - 解决:用十六进制编辑器检查
szFormula内存,确保所有字符ASCII值<128;用MultiByteToWideChar(CP_ACP, 0, szFormula, -1, NULL, 0)验证是否为纯GBK。
4.3 现象:注入进程能获取接口指针,但所有函数调用均返回ACCESS_VIOLATION
- 原因:注入时机过早。在大智慧加载
DzhApi.dll前就调用了GetModuleHandle,此时句柄为空,GetProcAddress返回NULL,后续函数指针为野指针。 - 解决:注入后先
Sleep(500),再用WaitForSingleObject等待DzhApi.dll加载事件(需逆向找到其内部事件名DzhApiLoadedEvent)。
4.4 现象:SendOrder返回0,但委托未出现在委托列表中
- 原因:
nVolume传入了奇数手数。A股规则要求最小交易单位为1手(100股),但大智慧引擎会校验nVolume % 100 == 0,否则静默丢弃。 - 解决:下单前强制
nVolume = (nVolume / 100) * 100;期货合约需查GetContractInfo确认最小变动单位。
4.5 现象:多线程调用GetBar时出现随机数据错乱
- 原因:
GetBar不是线程安全的。大智慧内部使用全局缓冲区存储K线数据,多线程并发调用会相互覆盖。 - 解决:必须串行化调用。用
CRITICAL_SECTION包裹所有pApi->GetBar调用,或为每个线程分配独立DZH_API_INTERFACE副本(需多次调用GetDzhApiInterface)。
5. 进阶技巧:实现毫秒级tick快照与公式热重载
5.1 抓取原始tick流:绕过K线聚合,直取行情内存块
大智慧将实时tick存于共享内存段,GetBar只是其上层封装。要获取原始tick,需定位DzhMain.exe中的g_pTickBuffer全局指针:
// 步骤1:在DzhMain.exe中搜索特征码定位g_pTickBuffer // 特征码(x64):48 8B 05 ?? ?? ?? ?? 48 85 C0 74 ?? 48 8B 00 // 步骤2:读取该指针指向的内存(格式为TICK_DATA结构体) #pragma pack(push, 1) typedef struct { __int64 time; // 纳秒级时间戳(需除以1000000转为毫秒) double price; // 最新成交价 int volume; // 成交量(股) int type; // 0=买盘,1=卖盘,2=中性 char code[16]; // 股票代码(GBK编码) } TICK_DATA; #pragma pack(pop) // 步骤3:每10ms轮询一次,避免漏单 while (running) { TICK_DATA* pTick = (TICK_DATA*)ReadProcessMemoryFromDzhMain(g_pTickBuffer); if (pTick && pTick->time > lastTime) { process_tick(pTick); lastTime = pTick->time; } Sleep(10); }注意:此方法需
SeDebugPrivilege权限,且g_pTickBuffer地址每次启动都变,必须用特征码扫描。实测可捕获到交易所原始推送的tick,延迟<3ms。
5.2 公式热重载:动态修改正在运行的指标逻辑
大智慧允许在不重启的情况下重载公式,但需组合多个接口:
// 1. 先用GetFormulaValue获取当前公式ID(隐藏参数) int formulaID = *(int*)((char*)pApi + 0x1234); // 偏移量需逆向确定 // 2. 修改内存中公式字符串(需VirtualProtect改变页面属性) DWORD oldProtect; VirtualProtect((void*)(formulaBaseAddr + 0x567), 256, PAGE_READWRITE, &oldProtect); strcpy_s((char*)(formulaBaseAddr + 0x567), 256, "MA(C,10)"); // 新公式 // 3. 触发重编译 pApi->ReloadFormula(formulaID); // 此函数需从导出表手动获取 // 4. 强制刷新图表 pApi->RefreshChart("600519", 9); // 刷新1分钟图5.3 验证接口可用性的自动化脚本
为防止大智慧升级后接口失效,我写了一个5行检测脚本(保存为check_dzh.bat):
@echo off tasklist /fi "imagename eq DzhMain.exe" 2>nul | find /i "DzhMain.exe" >nul if "%errorlevel%"=="0" ( echo [OK] 大智慧进程运行中 ) else ( echo [FAIL] 请先启动大智慧 exit /b 1 ) powershell -Command "$h=Get-Process DzhMain; $m=$h.Modules | ?{$_.ModuleName -eq 'DzhApi.dll'}; if($m){'OK'}else{'FAIL'}" if "%errorlevel%"=="0" ( echo [OK] DzhApi.dll已加载 ) else ( echo [FAIL] DzhApi.dll未加载 exit /b 1 ) echo [INFO] 正在测试GetBar... powershell -Command "& '.\test_getbar.exe'"其中test_getbar.exe是用C写的最小验证程序,只调用GetBar获取1根日线,成功则输出TEST_PASSED。
从那以后我每次部署新策略前,都强制跑一遍这个脚本,并把输出重定向到日志。去年大智慧v7.82升级后,GetFormulaValue的nParamCount参数含义变了(从股票数改为参数个数),就是靠这个脚本在上线前3小时发现的。希望帮到你。
本文还有配套的精品资源,点击获取