首页 / 交易学院 / 告别自制 DLL:用 memcpy 直连 WinAPI 传递结构体与指针
告别自制 DLL:用 memcpy 直连 WinAPI 传递结构体与指针
🔌

告别自制 DLL:用 memcpy 直连 WinAPI 传递结构体与指针

MQL5 编程 难度 · 高级 2011 14 分钟阅读
WinAPImemcpy结构体指针msvcrt免 DLL

MQL5 功能总有不够用的时候:你想连数据库、用通信套接字、调操作系统 API,却被两件事卡住——怎么把复杂结构传给 API 函数?怎么处理 API 返回的指针?很多人的第一反应是写个中间 DLL。本文证明大可不必:用系统自带的 msvcrt.dll 里的 memcpy,配合“内存即字节数组”的思想,就能在纯 MQL5 里完成结构传递与指针读取,彻底甩掉自制 DLL。

一、核心思想:内存就是字节序列

任何变量(含结构、数组)都存在内存里,地址是一个 int 值。只要知道地址和字节数,就能把一段内存当成任意类型解读——这叫“内存即字节序列”。C 库 msvcrt.dll 的 memcpy(dst, src, cnt) 能把从 src 地址开始的 cnt 字节复制到 dst 地址,并返回 dst 地址。这意味着:不管 src 是 char、double、数组还是结构,只要知道地址和大小,就能在内存区域间自由搬运。

二、用 memcpy 取变量地址

memcpy 返回接收缓冲的地址,这个特性可用来“取任意变量的地址”:把同一变量同时作为源和接收、cnt 传 0(不实际复制),返回值就是它的地址。文中示范取 double 变量和 short 数组首地址——拿到地址后,就能把它当作参数传给需要指针的 API 函数,或作为 memcpy 的源/目标。

// 用 memcpy 取变量地址(节选)
#import "msvcrt.dll"
   int memcpy(short &dst[], short &src[], int cnt);
   int memcpy(double &dst,    double &src, int cnt);
#import
void OnStart()
  {
   short src[5];
   int adr = memcpy(src, src, 0);   // 取数组首地址
   double var;
   adr = memcpy(var, var, 0);        // 取变量地址
  }

三、复制数组与传递结构给 API

复制数组:先 ArrayResize 分配目标,cnt 用 sizeof(类型)*ArraySize 算字节数,再 memcpy。传结构给 API 是更实用的招:MQL5 不能直接传结构指针,但结构可以整体复制进一个同尺寸的 uchar 数组(如 struct str2 { uchar c[28]; }),再把数组传给 API。文中以套接字 connect 为例:构造 sockaddr_in 结构 → 整体赋给同尺寸 ref_sockaddr_in 字节数组 → 把数组作为 connect 的参数提交,结构直达 API,无需 DLL。

// 把结构转字节数组再传给 API(节选)
struct sockaddr_in { short sin_family; u_short sin_port; in_addr sin_addr; char sin_zero[8]; };
struct ref_sockaddr_in { uchar c[2+2+8+8]; };  // 同尺寸字节数组
// 填充 addrin 后:
ref_sockaddr_in ref = addrin;     // 结构 -> 字节数组
res = connect(asock, ref.c, sizeof(addrin));

四、处理 API 返回的指针:以内存映射文件为例

API 常返回指向结构/数组的指针,MQL5 不会自动拆。办法是用 memcpy 从指针地址复制指定字节数到本地数组。文中用内存映射文件(MMF)示范:OpenFileMappingW 拿到句柄,MapViewOfFile 返回内存视图指针,再 memcpy(view, src, 10) 读前 10 字节、memcpy(view, num, 4) 写 4 字节 int——读写共享内存全程纯 MQL5,不用任何 DLL。

边界提醒:memcpy 不检查类型语义,字节数算错就会越界或读废数据。复制结构前务必用 sizeof 核对尺寸、用 ArraySize*元素大小算数组字节数;涉及对齐/padding 的结构要和 API 文档的字段布局逐一对齐,否则跨进程数据会错位。

五、从 MySQL 取回数据:指针的指针

更难的 case 是 API 返回“指针的指针”,比如 mysql_fetch_row 返回字符串数组(每个字段又是一个数组)。文中用 mysql_num_rows / mysql_num_fields / mysql_fetch_lengths 先拿到行数、字段数、各字段长度,再用 memcpy 按长度从返回指针逐个字段拷回本地 uchar 数组。字段可能是二进制,所以不能用 string 数组硬套——这正是“按字节长度精确搬运”的价值。

六、小结

摆脱自制 DLL 的关键在于“内存即字节序列”+ msvcrt 的 memcpy:取地址用 memcpy(var,var,0)、传结构用“结构→同尺寸字节数组”、读 API 返回的指针用 memcpy 按长度搬回。它让你在纯 MQL5 里直连 WinAPI、套接字、共享内存、数据库,省掉维护 DLL 的编译/部署/兼容负担。代价是字节级操作容错低——尺寸算错就崩,所以每个 sizeof 都要核对、每个字段布局都要对齐文档。把它当“高级系统编程”的钥匙,而不是随手乱用的捷径。

常见问题

因为 MQL5 能用 #import 直接调用系统 DLL(如 msvcrt.dll、Ws2_32.dll、kernel32.dll)里的函数。卡点只在“传结构/处理指针”,而用 msvcrt 的 memcpy 做内存级搬运就能解决,无需自己写中间 DLL。
利用 memcpy 返回接收缓冲地址的特性:把同一变量同时作为源和接收、cnt 传 0(不实际复制),返回值就是它的地址。如 memcpy(src, src, 0) 取数组首地址,memcpy(var, var, 0) 取变量地址。拿到地址后即可当指针传给需要它的 API。
把结构整体复制进一个同尺寸的 uchar 字节数组(如 struct { uchar c[28]; }),再把该数组作为 API 参数提交。文中 connect 套接字示例就是:构造 sockaddr_in → 赋给同尺寸 ref_sockaddr_in 字节数组 → 把数组传给 connect,结构直达 API。
用 memcpy 从返回指针地址精确复制指定字节数到本地数组。MMF 示例:MapViewOfFile 返回视图指针,memcpy(本地数组, 视图指针, 字节数) 读、memcpy(视图指针, 数值, 4) 写。MySQL 则用 mysql_num_fields/fetch_lengths 拿字段长度,再逐字段 memcpy 搬回。
memcpy 不做类型检查,字节数算错会越界或读废数据。复制结构前要用 sizeof 核对尺寸、数组用 ArraySize*元素大小算字节数;含对齐/padding 的结构要和 API 文档字段布局逐一对齐,否则跨进程数据错位。它适合高级系统编程,不能随手乱用。