尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

大智慧DLL插件开发:用TCC内嵌编译器实现“改码即生效”

大智慧DLL插件开发:用TCC内嵌编译器实现“改码即生效” 简介这套资源面向使用大智慧行情软件并希望突破公式内置函数限制的进阶用户以DLL插件配合内嵌TCC脚本的方式提供可自由扩展的自定义函数接口。解压后仅需将dll和TCC文件夹放入大智慧根目录即可调用公式中通过“dzhdllFUN1”等形式使用。包体共106个文件以h头文件、c源文件和def定义文件为主包含一个dll及cpp示例脚本总大小约406KB结构与常规C/C工程一致便于理解修改。目前已有419人浏览学习。核心价值在于脚本位于TCC文件夹下的Fun.cpp中只需实现FUN1至FUN4四个函数即可按自己的逻辑输出周期数据无需受平台固定函数约束修改后重启大智慧主程序即可生效适合对指标编写有定制需求、具备一定C语言基础的用户实践二次开发。1. 大智慧DLL插件为什么需要TCC内嵌编译器大智慧的DLL扩展接口老用户都不陌生但大部分教程让你装Visual Studio、配置Win32工程、还要注意字符集和调用约定搞完一个HelloWorld就得半小时。这套dzh2.rar资源走的是另一条路压缩包里的dll和TCC文件夹直接放到大智慧根目录后你只需要改TCC文件夹下Fun.cpp里的四个函数FUN1到FUN4运行一条编译命令再重启大智慧就能在公式里用dzhdllFUN1(5)这样的写法拿到新的指标。内嵌TCC并不是什么新技术但把它和大智慧的动态加载机制绑在一起就变成了一个“改代码即生效”的轻量化开发环境适合那些每天都要微调指标逻辑、又不想在IDE和编译配置上浪费时间的量化爱好者。2. 大智慧DLL加载原理与TCC编译链的选择2.1 大智慧公式调用DLL的接口约定先弄明白大智慧在背后做了什么。公式里写MA5:dzhdllFUN1(5)大智慧会去程序根目录找dzhdll.dll并从其中查找一个名为FUN1的导出函数。这个函数的签名不是随便定的大智慧会按固定的调用约定把数据序列和参数传进去。我在拆资源和实测后采用最常见的一套接口定义void __stdcall FUN1(int DataLen, float* pOut, float* pIn, float* pOpt, int OptLen)DataLen当前周期的K线根数比如日线数据有多少根DLL就被调用时传入多少根。pOut输出序列计算完的指标值写回这里长度也是DataLen。pIn输入序列通常是公式里定义的前置数据。虽然示例公式里没有引用但大智慧会把主图的收盘价等序列作为pIn传入。pOpt公式调用时传入的参数数组。例如(5)会使pOpt[0]5.0f(5,1)则pOpt[1]1.0f。OptLen参数个数。这种签名是早期大智慧DLL插件最常见的约定和你可能见过的通达信DLL接口很接近。要注意pOut和pIn在极端情况下可能指向同一段内存所以在函数内部别直接原地修改输入先拷贝再计算。另外大智慧是32位程序DLL必须编译为32位调用约定用__stdcall最稳别用默认的__cdecl。2.2 为什么选TCC而不是Visual Studio如果只用VS写一次DLL倒也能接受。但搞过的人都明白改一个参数就得重新编译、复制、重启中途还要处理C4819警告、字符集、静态库链接等问题。TCCTiny C Compiler是Fabrice Bellard写的一个小型C编译器压缩后只有几百KB不需要安装命令行里一条指令就能把C源码编译成DLL。而且它的编译速度极快对于只有几百行代码的指标DLL基本是瞬间完成这正好符合“改完公式马上验证”的节奏。我拆开dzh2.rar后确认TCC目录里带了完整的编译链文件libtcc1.a是TCC的运行时静态库dllcrt1.c和wincrt1.c是入口代码tiny_libmaker.c、tiny_impdef.c是TCC自带的辅助工具源码。这意味着你不需要另外准备头文件和库直接用这个TCC目录就能工作。下表是压缩包里出现的关键文件在TCC编译链里的作用文件作用libtcc1.aTCC运行时库编译DLL时需要链接tiny_libmaker.c生成静态库的工具源码tiny_impdef.c生成导入定义文件并查看DLL导出函数crt1.c / wincrt1.c程序入口相关主程序才会用到dllcrt1.c / hello_dll.cDLL入口与DLL示例hello_win.cWin32窗口示例仅作参考fib.c / dll.c递归与DLL调用示例和Visual Studio相比TCC的优势可以列成一个对比表对比点Visual StudioTCC安装体积数GB几百KB编译时间数秒到数十秒毫秒级命令行编译需要配置vcvars直接tcc命令生成DLL依赖可能依赖MSVC运行库仅依赖系统dll调试断点支持不支持但可用printf2.3 编译一条命令从C源码到dzhdll.dll先把工作目录切换到TCC所在目录执行下面的命令cd D:\dzh2\TCC tcc.exe -shared -I. -o D:\dzh2\dzhdll.dll Fun.cpp-shared告诉TCC生成DLL而不是EXE。-I.把当前TCC目录加入头文件搜索路径因为 Fun.cpp 可能会引用到TCC自带的头文件。-o D:\dzh2\dzhdll.dll指定输出文件名和位置这里直接输出到大智慧根目录。如果编译成功会生成一个几十KB的dzhdll.dll。如果Fun.cpp里用了math.hTCC默认会链接libtcc1.a所以不需要额外的/link参数。注意TCC生成的DLL不依赖TCC运行库只依赖系统自带的kernel32.dll等可以直接拷贝到别的机器。编译时如果提示找不到某个头文件多半是-I.路径写错了如果提示Undefined symbol检查Fun.cpp里是否包含了对应的系统头文件以及是否链接了libtcc1.a下的数学库。3. 大智慧DLL导入与公式调用实战3.1 dll导入的两种姿势手动复制与自动映射大智慧里说的“DLL导入”有两种理解。一是把编译好的DLL文件手动复制到程序根目录然后在公式中以文件名函数名的形式引用二是老版本大智慧公式编辑器里有一个“导入DLL”按钮需要你把DLL加载进来并指定函数名。新版的大智慧基本支持第一种也更省事。注意这个“安装DLL”的过程没有注册表参与不要用regsvr32去注册dzhdll.dll不是COM组件。我自己操作时步骤是解压dzh2.rar后先确认dzhdll.dll放到了大智慧安装根目录也就是主程序exe所在目录再把整个TCC文件夹也复制过去。这样做的理由是大智慧加载DLL时只会在当前工作目录搜索根目录里放着TCC文件夹也没关系大智慧不会加载它它只是你的编译工具。如果你想把DLL放到子目录大智慧可能识别不到除非你用绝对路径但那会降低可移植性。3.2 公式层写法参数如何传给DLL新建一个技术指标公式输入以下内容MA5:dzhdllFUN1(5),COLORWHITE; MA10:dzhdllFUN1(10),COLORYELLOW; STD20:dzhdllFUN2(20,0),COLORLIB;这个写法每行前半部分如MA5是公式输出名双引号内是DLL调用声明后面跟函数名DLL的导出名括号内是参数。大智慧把5、10、20分别填充到pOpt[0]把0填充到pOpt[1]。如果你只传给一个参数OptLen就是1。所以你在Fun.cpp里可以通过OptLen判断用户是否传了周期标志。有不少人在这里踩坑函数名写成小写DLL里导出的是大写结果提示“函数不存在”。实际上大智慧的解析不区分大小写但DLL导出表里必须存在对应名称大小写取决于编译时用了什么名字。为了避免麻烦统一全大写。写公式的时候还要注意双引号必须用英文引号如果从网页直接复制成全角引号大智慧会报语法错误。3.3 周期识别现在只能自己约定大智慧的DLL接口没有直接给你一个“当前是日线还是周线”的参数所以摘要里说的“周期选择自己弄自己识别”就是这个意思。我的做法是在公式层把一个固定的周期编码作为第二个参数传进DLL比如WEEKMA:dzhdllFUN1(5,1); DAYMA:dzhdllFUN1(5,0);然后在Fun.cpp里读取pOpt[1]等于1时走周线分支等于0时走日线分支。如果你不想改公式也可以利用大智慧的“周期”属性在公式管理里把指标运行周期设置为“日线”或“周线”然后DLL内部通过DataLen的大致数量来猜。例如日线数据通常有300根周线只有60根但这种判断不可靠不如显式传参。另一种思路是从pIn序列的特征去判断比如检查时间戳但大智慧传入的只是浮点序列没有时间信息所以最实用的还是公式层传参。4. 在Fun.cpp中实现FUN1-FUN4四函数骨架与周期分支4.1 函数签名与全局缓冲设计我打开资源里的Fun.cpp看到作者预设了四个空函数。这里给出一个可直接运行的实现把FUN1-FUN4分别做成均线、标准差、最高值、最低值每个函数都接受(N, 周期标志)两个参数。为了演示“周期识别”我们让FUN1在周期标志为1时计算加权均线其它都是普通均线。代码如下#include math.h #include string.h #define DllExport __declspec(dllexport) // 普通均线 static void CalcMA(float* out, const float* in, int len, int n) { double sum 0.0; for (int i 0; i len; i) { sum in[i]; if (i n) sum - in[i - n]; out[i] (i n - 1) ? (float)(sum / n) : 0.0f; } } // 加权均线周期越大最后几根K线的权重越高 static void CalcWMA(float* out, const float* in, int len, int n) { for (int i 0; i len; i) { if (i n - 1) { out[i] 0; continue; } double sum 0.0, wsum 0.0; for (int j 0; j n; j) { float w (float)(j 1); sum in[i - n 1 j] * w; wsum w; } out[i] (float)(sum / wsum); } } // 标准差 static void CalcSTD(float* out, const float* in, int len, int n) { for (int i 0; i len; i) { if (i n - 1) { out[i] 0; continue; } double mean 0; for (int j i - n 1; j i; j) mean in[j]; mean / n; double var 0; for (int j i - n 1; j i; j) var (in[j] - mean) * (in[j] - mean); out[i] (float)sqrt(var / n); } } // N日最高 static void CalcHHV(float* out, const float* in, int len, int n) { for (int i 0; i len; i) { int start (i n - 1) ? (i - n 1) : 0; float h in[start]; for (int j start 1; j i; j) if (in[j] h) h in[j]; out[i] h; } } // N日最低 static void CalcLLV(float* out, const float* in, int len, int n) { for (int i 0; i len; i) { int start (i n - 1) ? (i - n 1) : 0; float l in[start]; for (int j start 1; j i; j) if (in[j] l) l in[j]; out[i] l; } } DllExport void __stdcall FUN1(int Len, float* pOut, float* pIn, float* pOpt, int OptLen) { int n (OptLen 0) ? (int)pOpt[0] : 5; int periodFlag (OptLen 1) ? (int)pOpt[1] : 0; if (periodFlag 1) CalcWMA(pOut, pIn, Len, n); else CalcMA(pOut, pIn, Len, n); } DllExport void __stdcall FUN2(int Len, float* pOut, float* pIn, float* pOpt, int OptLen) { int n (OptLen 0) ? (int)pOpt[0] : 20; CalcSTD(pOut, pIn, Len, n); } DllExport void __stdcall FUN3(int Len, float* pOut, float* pIn, float* pOpt, int OptLen) { int n (OptLen 0) ? (int)pOpt[0] : 20; CalcHHV(pOut, pIn, Len, n); } DllExport void __stdcall FUN4(int Len, float* pOut, float* pIn, float* pOpt, int OptLen) { int n (OptLen 0) ? (int)pOpt[0] : 20; CalcLLV(pOut, pIn, Len, n); }4.2 代码逻辑说明上面代码里DllExport展开为__declspec(dllexport)这是让函数从DLL导出的关键。如果漏掉tcc -shared生成的DLL里就没有导出符号大智慧会提示函数不存在。四个导出函数都用__stdcall因为大智慧内部以stdcall方式调用和默认的cdecl不兼容轻则参数错位重则栈不平衡导致崩溃。FUN1通过periodFlag判断当前调用是不是周线周期pOpt[1]1时走了加权均线分支否则走普通均线。实际应用中你可以把periodFlag用于其它场景比如做指标的周期自适应让不同周期下参数n自动缩放例如n (periodFlag1) ? n*5 : n。这种“自己识别周期”的方式完全由你定不受大智慧限制。CalcMA、CalcSTD这些静态辅助函数都放在DLL内部不导出因此不会污染导出表。每个函数都处理了i n-1的空白段此时输出0避免大智慧在K线图最左侧画出不真实的指标。注意CalcMA中使用滑动求和避免每次O(n)循环但CalcSTD和最高最低值仍是暴力循环如果数据特别长比如3000根你可以在自己的版本里改用增量计算否则在分钟级别的K线上可能存在性能压力。4.3 参数说明与编译验证periodFlag自己约定的周期编码0日线1周线2月线。你也可以把0定义为“未指定”。OptLen用户如果只传一个参数OptLen为1此时pOpt[1]不可读。所以每个函数里都先判断OptLen 1。编译命令和第2章一样执行后重启大智慧。如果编译报错常见原因是tcc.exe不在当前目录或者Fun.cpp文件编码不是ANSI。大智慧公式编辑器不支持UTF-8带BOMFun.cpp用纯ASCII编码最安全。修改Fun.cpp之后不会即时生效必须等待大智慧主进程完全退出再重新启动只关闭公式编辑器窗口是没用的。可以在任务管理器里确认没有hx.exe或dzh.exe相关进程残留。5. 大智慧DLL排错与导出函数检查技巧5.1 最常见的“DLL初始化失败”与解决方案大智慧在加载DLL失败时通常不会给出具体错误码只会提示“无法加载动态链接库”或者“dll初始化例程失败”。你可以在Windows事件查看器里看到模块路径。排错时按下面表格逐个检查错误现象可能原因解决办法提示找不到dzhdll.dllDLL没放到大智慧根目录把dzhdll.dll复制到主程序exe同目录报表提示“函数不存在”导出函数名与公式不一致用tiny_impdef看实际的导出名修改Fun.cpp后结果没变没重新编译或大智慧进程未完全退出重新编译DLL并检查任务管理器关闭全部大智慧进程指标线全部为0n参数小于1或pIn被错误修改在DLL里加分支判断n0时返回1数据错位或崩溃pOut与pIn指向同一块内存先memcpy到临时数组再计算5.2 用tiny_impdef检查DLL导出表TCC自带的tiny_impdef是一个被低估的工具。在命令行里执行D:\dzh2\TCC\tiny_impdef.exe D:\dzh2\dzhdll.dll它会解析DLL的导出表并输出类似下面的内容FUN1 FUN2 FUN3 FUN4如果这里没有输出任何函数说明你的Fun.cpp没用__declspec(dllexport)导出。另外你也可以用它为第三方DLL生成.def文件这在你想通过#pragma comment(linker)去链接其他插件时非常有用。需要注意的是输出顺序不一定和源码顺序一致这和TCC内部生成导出表的方式有关不必纠结。5.3 一个提高调试效率的技巧给Fun.cpp增加一个不依赖大智慧的本地测试入口在文件末尾用#ifdef _DEBUG_MAIN包一个main()函数用假数据调用FUN1输入输出打印到控制台。这样你可以在编译成DLL之前先用 tcc.exe 运行 C文件进行裸调试。比如#ifdef _DEBUG_MAIN #include stdio.h int main() { float in[10] {1,2,3,4,5,6,7,8,9,10}; float out[10] {0}; float opt[2] {3, 0}; FUN1(10, out, in, opt, 2); for (int i 0; i 10; i) printf(%.2f , out[i]); return 0; } #endif用tcc.exe -D_DEBUG_MAIN -run Fun.cpp运行几毫秒就能看到输出。确认算法正确后再用-shared生成DLL。这个技巧能帮你把大智慧公式调试和DLL逻辑调试彻底分开减少反复重启大智慧的时间。另外你可以在同一个Fun.cpp里继续加FUN5、FUN6只要按同样的导出格式声明编译后大智慧就能直接通过dzhdllFUN5(参数)调用不用额外注册。本文还有配套的精品资源点击获取
返回列表