简介:这份资源面向需要在通达信公式环境中处理文本数据的量化开发者与插件编写者,聚焦一个具体痛点:通达信公式数据以浮点数为主,字符串类型无法直接传递,必须借助句柄映射机制间接实现。资源包共3个文件,包含1个inscode工程配置、1个html说明页面和1个gitignore忽略规则文件,整体约6KB,属于轻量级源码代码包,便于快速导入与查阅。内容围绕字符串函数的工作原理展开,给出初始化句柄到字符串的转换函数、由句柄反向获取字符串内容的函数,以及一个可实际运行的测试插件,并演示如何在公式逻辑中嵌入这些字符串处理能力。已有199人学习,适合希望打通通达信DLL插件字符串支持、理解句柄映射思路并直接复用示例代码的开发者参考。
1. 通达信 DLL 插件字符串支持:一份能直接编译的 C++ 代码包拆解
如果你写过通达信公式,大概率遇到过这个场景:想把外部数据、自定义指标或者一段业务逻辑塞进通达信里,公式语言写起来束手束脚,最后只能走 DLL 插件这条路。但真动手写的时候,第一个卡住的地方往往不是算法,而是字符串——通达信主程序和插件之间的字符串传递,参数怎么定义、编码怎么处理、返回值怎么给,官方文档语焉不详,网上搜到的示例又七零八落。这份「通达信 DLL 插件字符串支持」代码包,解决的就是这个具体问题:它把插件里字符串输入输出的完整链路拆成了可编译、可调试的源码,覆盖了参数声明、编码转换、缓冲区管理这几个最容易翻车的环节。适合已经会写基础 DLL 插件、但被字符串交互卡住的开发者,也适合想系统看一眼通达信插件接口长什么样的新手。
2. 通达信 DLL 接口的字符串机制:从参数表到内存布局
2.1 通达信插件函数的参数约定
通达信调用 DLL 插件时,走的是标准的导出函数约定。一个典型的插件函数签名长这样:
// 通达信插件标准导出函数原型 extern "C" __declspec(dllexport) void WINAPI PluginName( int nParamCount, // 参数个数 float* pfParam, // 数值型参数数组 char** ppszParam, // 字符串型参数数组 float* pfResult, // 数值型返回值数组 char** ppszResult, // 字符串型返回值数组 int nResultCount // 返回值个数 );这里的关键在于char** ppszParam和char** ppszResult。通达信传进来的字符串是一个字符指针数组,每个元素指向一个以\0结尾的字符串。数值参数走float*,字符串参数走char**,两条通道是分开的。很多新手第一次写插件,把字符串参数当成数值参数去读,结果拿到的是指针地址而不是内容,这就是没搞清楚内存布局。
nParamCount告诉你实际传了几个参数,pfParam和ppszParam的下标是对齐的——第 i 个参数如果是数值型,从pfParam[i]取;如果是字符串型,从ppszParam[i]取。通达信不会告诉你哪个参数是哪种类型,这个信息得在公式调用端自己保证。常见做法是:在公式里用TDXDLL1(1, C, 'ABC')这种形式,第一个参数是功能号,后面按约定顺序传数值和字符串。
2.2 字符串编码:GBK 还是 UTF-8
这是血泪经验最集中的地方。通达信主程序在中文 Windows 环境下,内部字符串用的是 GBK 编码。你的插件如果按 UTF-8 去解析,中文直接变乱码。反过来,插件返回给通达信的字符串,也必须转成 GBK,否则公式端显示出来就是一堆问号。
代码包里处理这件事的方式是封装两个转换函数:
#include <windows.h> #include <string> // UTF-8 转 GBK,用于插件返回字符串给通达信 std::string Utf8ToGbk(const std::string& utf8Str) { if (utf8Str.empty()) return ""; // 先转宽字符 int wlen = MultiByteToWideChar(CP_UTF8, 0, utf8Str.c_str(), -1, nullptr, 0); std::wstring wstr(wlen, 0); MultiByteToWideChar(CP_UTF8, 0, utf8Str.c_str(), -1, &wstr[0], wlen); // 宽字符转 GBK int glen = WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), -1, nullptr, 0, nullptr, nullptr); std::string gbkStr(glen, 0); WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), -1, &gbkStr[0], glen, nullptr, nullptr); // 去掉末尾多余的 \0 if (!gbkStr.empty() && gbkStr.back() == '\0') gbkStr.pop_back(); return gbkStr; } // GBK 转 UTF-8,用于插件读取通达信传入的字符串 std::string GbkToUtf8(const std::string& gbkStr) { if (gbkStr.empty()) return ""; int wlen = MultiByteToWideChar(CP_ACP, 0, gbkStr.c_str(), -1, nullptr, 0); std::wstring wstr(wlen, 0); MultiByteToWideChar(CP_ACP, 0, gbkStr.c_str(), -1, &wstr[0], wlen); int ulen = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, nullptr, 0, nullptr, nullptr); std::string utf8Str(ulen, 0); WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, &utf8Str[0], ulen, nullptr, nullptr); if (!utf8Str.empty() && utf8Str.back() == '\0') utf8Str.pop_back(); return utf8Str; }逻辑说明:Windows 的MultiByteToWideChar和WideCharToMultiByte是编码转换的标准 API,中间用宽字符(UTF-16)做桥梁。CP_UTF8和CP_ACP分别指定源和目标编码,CP_ACP在当前系统区域设置下就是 GBK。参数-1表示输入字符串以\0结尾,函数会自动计算长度。转换后的字符串末尾会多一个\0,用pop_back去掉,避免拼接时出问题。
提示:如果你的开发环境默认字符集是 Unicode,
std::string里存的可能是 UTF-8,也可能是本地编码,取决于你怎么赋值。建议在插件入口处统一转成 UTF-8 处理,出口处再转回 GBK,中间逻辑全部用 UTF-8,这样最不容易乱。
2.3 返回值缓冲区:谁分配、谁释放
字符串返回值的处理是另一个高频翻车点。通达信给插件传进来的ppszResult是一个预先分配好缓冲区的字符指针数组,插件需要把结果拷贝进去,而不是直接修改指针指向。常见错误是直接ppszResult[0] = myString.c_str(),这样做的后果是:函数返回后myString被析构,通达信拿到的是野指针,轻则乱码,重则崩溃。
正确的做法是拷贝:
// 安全地写入字符串返回值 void SafeWriteResult(char** ppszResult, int index, const std::string& value) { if (ppszResult == nullptr || ppszResult[index] == nullptr) return; // 假设通达信分配的缓冲区足够大,常见为 256 或 1024 字节 // 这里用 strncpy_s 保证不溢出 strncpy_s(ppszResult[index], 256, value.c_str(), _TRUNCATE); }参数说明:ppszResult[index]是通达信分配的缓冲区首地址,256是假设的缓冲区大小,实际值需要根据通达信版本确认,_TRUNCATE表示超长时截断而不是报错。如果你不确定缓冲区大小,保守做法是限制返回字符串长度在 200 字节以内。
3. 把代码包跑起来:编译、注册与公式调用
3.1 编译环境与项目配置
代码包提供的是 C++ 源码,编译成 DLL 需要 Windows 平台下的编译器。我一般用 Visual Studio 的 MSVC 工具链,社区版就够。新建项目时选「动态链接库 (DLL)」,把源码文件加进去,注意几个配置项:
| 配置项 | 推荐值 | 原因 |
|---|---|---|
| 平台 | x86 | 通达信主程序是 32 位,插件必须匹配 |
| 字符集 | 使用多字节字符集 | 避免 Unicode 宏导致的类型不匹配 |
| 运行库 | 多线程 (/MT) | 减少对 VC 运行时的依赖,分发方便 |
| 优化 | 最大优化 (/O2) | 插件被频繁调用,性能敏感 |
平台选 x86 是硬性要求。我见过有人编译了 64 位 DLL 然后纳闷为什么通达信加载不了,这就是没注意位数匹配。字符集选多字节,是因为通达信接口用的是char*,如果项目设成 Unicode,char*和TCHAR*的转换会多出一堆麻烦。
3.2 导出函数的声明与注册
编译出 DLL 后,通达信需要知道从哪个函数入口调用。这通过公式里的TDXDLL1、TDXDLL2等函数来绑定。具体步骤:
第一步,确认 DLL 导出了正确的函数名。用dumpbin /exports yourplugin.dll查看,应该能看到类似_PluginName@24的修饰名。如果用了extern "C"和__declspec(dllexport),名字会保持 C 风格。
第二步,把 DLL 放到通达信安装目录下的T0002\dlls文件夹里。这个路径是通达信约定的插件目录,放别处它找不到。
第三步,在公式管理器里新建公式,调用方式如下:
{通达信公式示例:调用 DLL 插件处理字符串} DLLNAME := 'myplugin'; PARAM1 := 1; STR1 := '测试输入'; RESULT := TDXDLL1(PARAM1, C, STR1);这里的TDXDLL1对应第一个 DLL 入口,PARAM1是功能号,C是收盘价(数值参数示例),STR1是字符串参数。插件内部根据功能号决定走哪段逻辑。
3.3 一个完整的字符串处理示例
代码包里有一个字符串拼接的示例函数,逻辑是:接收两个字符串参数,拼接后返回。核心实现:
extern "C" __declspec(dllexport) void WINAPI StrConcat( int nParamCount, float* pfParam, char** ppszParam, float* pfResult, char** ppszResult, int nResultCount) { // 参数校验:至少需要两个字符串参数 if (nParamCount < 2 || ppszParam == nullptr) return; if (ppszParam[0] == nullptr || ppszParam[1] == nullptr) return; // 读取输入并转 UTF-8 处理 std::string s1 = GbkToUtf8(ppszParam[0]); std::string s2 = GbkToUtf8(ppszParam[1]); // 业务逻辑:拼接 std::string result = s1 + s2; // 转回 GBK 写入返回值 std::string gbkResult = Utf8ToGbk(result); SafeWriteResult(ppszResult, 0, gbkResult); }逻辑说明:函数先做参数校验,防止空指针导致崩溃。然后从ppszParam读取两个字符串,转成 UTF-8 后拼接,最后转回 GBK 写入ppszResult[0]。整个过程不修改输入指针,不分配需要外部释放的内存,是安全的写法。
参数说明:nParamCount是实际参数个数,这里要求至少 2 个;ppszParam[0]和ppszParam[1]是两个输入字符串;ppszResult[0]是输出缓冲区。如果你的通达信版本对返回值个数有要求,nResultCount可以用来判断。
4. 避坑与排查:字符串插件的五个高频翻车点
4.1 中文乱码:现象、原因与解决
现象:公式里传入中文,插件处理后返回,显示为乱码或问号。
原因:编码转换缺失或方向搞反。通达信传入的是 GBK,插件按 UTF-8 解析;或者插件返回 UTF-8,通达信按 GBK 显示。
解决:在插件入口统一做GbkToUtf8,出口统一做Utf8ToGbk。用上面封装的两个函数,不要在每个分支里手动转,容易漏。
4.2 插件加载失败:位数与依赖问题
现象:通达信公式里调用TDXDLL1返回错误,或者根本没反应。
原因:DLL 位数不匹配(64 位 DLL 放到 32 位通达信里),或者缺少 VC 运行时依赖。
解决:确认编译平台是 x86;用 Dependency Walker 检查依赖,如果缺msvcp140.dll之类的,改成静态链接运行库(/MT)。
4.3 返回值截断:缓冲区大小估计错误
现象:返回的字符串比预期短,后半截丢失。
原因:通达信分配的返回值缓冲区大小有限,常见是 256 字节。写入时没有检查长度,或者strncpy_s的 size 参数给大了导致截断。
解决:限制返回字符串长度,保守控制在 200 字节以内;用strncpy_s并传_TRUNCATE,确保不溢出。
4.4 频繁调用导致性能下降
现象:公式在大量数据上运行时卡顿。
原因:每次调用都做编码转换和字符串拷贝,开销累积。
解决:如果输入字符串在多次调用中不变,可以在插件内做缓存;或者把字符串处理逻辑移到公式端用通达信内置函数完成,DLL 只处理必须的部分。
4.5 多线程调用下的数据竞争
现象:插件在后台选股或预警中偶发崩溃。
原因:通达信可能在多线程环境下调用插件,如果插件内用了全局变量或静态缓冲区,会出现竞争。
解决:避免使用全局可变状态;所有数据在函数栈上分配;如果必须用静态数据,加临界区保护。
5. 进阶技巧:用字符串参数做插件配置与调试输出
字符串参数除了传业务数据,还有一个实用场景:做插件的运行时配置。比如你写了一个指标计算插件,想让用户在不重新编译的情况下切换算法模式,可以用一个字符串参数当配置项:
// 根据字符串参数选择算法分支 std::string mode = GbkToUtf8(ppszParam[0]); if (mode == "fast") { // 快速算法 } else if (mode == "accurate") { // 精确算法 } else { // 默认算法 }这样公式端只需要改一个字符串常量,就能切换插件行为,比重新编译 DLL 方便得多。
另一个技巧是用字符串返回值做调试输出。通达信公式的调试手段有限,你可以在插件里把中间变量拼成字符串返回,在公式里用MSGOUT或者直接显示在指标线上,快速定位问题。我一般会在插件开发阶段加一个debug功能号,返回内部状态字符串,上线前再关掉。
验证插件是否正常工作,最直接的方法是写一个最小公式:传入固定字符串,调用插件,把返回值画在副图上。如果显示正确,说明链路通了;如果乱码,回去查编码;如果没输出,查 DLL 是否加载成功。从那以后我每次写完字符串插件,都强制走一遍「最小公式验证 → 中文输入输出 → 边界长度测试」这三步,能挡掉九成以上的问题。希望帮到你。
本文还有配套的精品资源,点击获取