YStudio Y++

DLL 工程(用 Y++ 编写并导出 `.dll`)

Y++ / YStudio 开发文档 · 一点滴

第一版为纯导出库:用窗口、默认代码视图。打开工程不会进设计画布。

写法:先写函数,再导出

导出名默认可与 Y++ 函数名相同(含中文)。也可用别名让 PE 导出为 ASCII,Y++ 仍用中文函数名:

' —— math.yc ——
函数 相加(整数 a, 整数 b) 整数 {
    返回(a + b)
}

' —— exports.yex ——
导出 "相加"              ' 导出名 = 函数名
导出 "Add" = 相加        ' PE 导出名 Add,函数仍是 相加
导出 ["相减"]            ' 批量(仅同名)

也支持不带引号:导出 相加。

调用约定

项目设置 → DLL 调用约定:stdcall(默认,WinAPI / 习惯)或 cdecl(多数 C 库)。改后须重编 DLL 与宿主。

生成的 .yp 会在别名后写上约定(stdcall / cdecl),宿主 thunk 按该约定生成。

仅 type=dll 工程可用 导出。

DllMain 钩子

函数 DLL.加载 {
    ' LoadLibrary 成功、本 DLL 初始化时(PROCESS_ATTACH)
}

函数 DLL.卸载 {
    ' FreeLibrary / 进程退出时(PROCESS_DETACH)
}

新建 DLL 工程会自带 main.yc + exports.yex。菜单「添加新文件 → 导出清单 (.yex)」可再建。

右侧「导出」面板

打开 DLL 工程后,右侧栏有 导出 页:

操作 作用
刷新 按当前 .yc / .yex(含未保存编辑器)重扫导出表
勾选 多选后生成 .yp / 复制片段只含勾选项
双击行 跳转到函数定义源码
Y++片段 复制 加载DLL / 取导出函数 / 调用函数Ex
C#片段 复制 [DllImport](含 CallingConvention)
生成 .yp 写入本工程 源代码/{工程名}.yp(带约定),并复制全文
调用 填参后跑同级宿主,无信息框;结果写回面板(临时文件)
一键宿主 创建/更新同级 {工程名}_host,弹出信息框后自动关闭
读PE 解析发布目录 .dll 的导出表(无需 dumpbin)
表右键 跳转定义 / 复制导出名或「导出」行 / 片段 / 调用 / 宿主 / 读PE
.yex 中 Ctrl+E 一键插入尚未导出的函数(导出 "函数名")

自动 .yp 示例

@"MyLib.dll"
整数 相加(整数 a, 整数 b) 相加 stdcall
整数 Add(整数 a, 整数 b) 相加 stdcall   ' 若写了 导出 "Add" = 相加

中文导出名经 DLL 内 YplDll_ResolveExport 解析。生成 .yp 时:

  • 简单整数/文本:原样声明
  • 字节集:写成 长整数 xxx_ptr, 整数 xxx_len(对齐导出 C-ABI)
  • &结构 / 数组 / 子程序指针:按 .yp 语法写出,不再整行注释跳过
  • 一键「调用」测试仍仅支持无字节集/回调/数组的简单签名

同级测试宿主

父目录/
  MyLib/           ← DLL 工程
  MyLib_host/      ← 一键测试 / 调用测试创建的窗口工程

F5 编译 DLL 不会自动弹宿主。若尚无 .dll 会先编译本 DLL 再继续测试。

依赖清单 / 版本资源

  • 项目设置 → DLL 依赖清单:每行一个同级工程名或 xxx.dll;宿主 F5 / 发布时只拉清单中的项,以及源码 / .yp 里出现的 "xxx.dll"。不会把同目录下其它无关 DLL 工程产物一并拷来。
  • VERSIONINFO:勾选「编译时写入」后,.dll 使用 VFT_DLL;资源管理器「属性 → 详细信息」可见版本字段。

导出 C-ABI(给 C# / C 用)

Y++ DLL 的 extern "C" 导出层按稳定 C 约定编组(内部函数仍是 C++):

Y++ 导出 ABI 说明
文本 入参 LPCWSTR / const wchar_t* 空指针按 L""
文本 返回 LPCWSTR TLS 环形缓冲驻留,调用方须立即拷贝
字节集 入参 const unsigned char *ptr, int len 两参数;不要只传指针
字节集 返回 const unsigned char * + 末尾 int *out_len TLS 驻留;out_len 写长度
整数/逻辑/小数 与内部一致 long long / int / double 等

「一键调用」暂只覆盖无字节集的简单签名;含字节集的导出请用 C# 片段或手写 .yp(长整数+整数)。

加载DLL 对磁盘路径会先读 PE 头做 x86/x64 位数预检,不一致时 DLL_取错误() 有明确提示。

结构体 / 数组 / 字节集(与 .yt、.yp 对齐)

写导出或 .yp 声明时,按下列约定可少踩坑:

场景 写法 说明
简单整数/逻辑/文本 整数 a、文本 s 与普通函数一致
.yt 结构体指针 .yp:&矩形 框;Y++ 内先定义类型 嵌套 / 定长数组≤256 / 字节集入参已编组;见 dll-pointers.md §3.5
字节缓冲 字节 缓冲[] 或 字节集 传 BYTE*;空则 nullptr
整数数组 整数 表[] 元素为 64 位;若 DLL 要 32 位 int*,用 DLL_整数指针32
回调 子程序指针 cb 不要加 & 类型;约定与工程 stdcall/cdecl 一致

更细的指针边界见 dll-pointers.md、dllapi.md。

宿主调用

长整数 p = 取导出函数(模块, "相加")   ' 或 "Add"
整数 和 = 到整数(调用函数Ex(p, 2, 3))

或 .yp 别名。

产物与分发

  • F5 / 编译:只编译不启动 → 发布/{arch}/{cfg}/项目名.dll(及同名 .lib)
  • 发布打包:选目标文件夹 →「开始打包」;拷贝 .dll(及 .lib)。DLL 工程不生成安装包。
  • Debug 启动位置(窗口工程 / 测试宿主):项目设置可勾选 「Debug 也从发布/ 目录启动」,少纠缠 %TEMP%\ystudio-build;默认仍从临时缓存启动以避杀软二次扫描。

宿主怎么找到 DLL?

宿主 F5 时按「依赖清单 + 源码/.yp 引用」拉取同级已发布的 xxx.dll;一键宿主会自动把本 DLL 工程名写入宿主的依赖清单。

长整数 模块 = 加载DLL(取程序目录() + "\\你的库.dll")

中文导出名由 YplDll_ResolveExport 解析;ASCII 别名可直接 GetProcAddress。