一个 SDK,Windows / Linux 都能接
无需自己实现加密、签名、机器码、心跳——调用几个函数就能完成卡密登录、账号登录、到期检测、点数扣减等全部流程。Windows 是 DLL,Linux 是 .so,两边接口完全一致。
1这是什么
ZhilianSDK 是一套网络验证客户端 SDK:Windows 下是 DLL,Linux 下是 .so,两个平台的函数名、参数、返回值完全一致。你的程序只要调用它,就能对接验证服务器,完成:
- 登录验证:卡密登录、账号密码登录、设备试用登录
- 状态维护:心跳保活、到期时间查询、账号状态检测
- 账号体系:注册、充值、改密、找回密码、转绑
- 数据交互:程序公告、远程变量、远程函数、账号云数据、点数
- 加壳配套:WinLicense 授权、VMP 授权码
2目录里有什么
Windows 包
Linux 包
DllImport、Python 用 ctypes;C/C++ 同样不需要头文件。x64 的 DLL,32 位程序加载 x86 的 DLL。搞反了会直接报「无法加载 DLL」或 0xC0000135。Linux 包目前只提供 x86_64。libc / libpthread / libdl。拷过去就能用。35 分钟接入
登录验证后台 → 进入你的应用 → 点「一键生成对接参数」→ 复制那串加密文本(product_token)。
这串东西里已经包含了服务器地址、应用 UUID、加密密钥、验签公钥、算法,不用再手填任何字段。
Windows:把 ZhilianSDK_x64.dll(或 x86)复制到 exe 旁边。
Linux:把 libZhilianSDK.so 放到程序同目录,编译时加 -Wl,-rpath,'$ORIGIN'(详见第七节)。
ZL_Create → ZL_SetConfig(对接串) → ZL_CardLogin(卡密),完事。
#include <stdio.h>
int main(void) {
void* h = ZL_Create();
if (!h) return 1;
// ① 配置:粘贴后台「一键生成对接参数」里的整串
if (ZL_SetConfig(h, "这里粘贴 product_token") != 0) {
char err[512];
ZL_GetLastErrorMessage(h, err, sizeof(err));
printf("配置失败: %s\n", err);
return 1;
}
// ② 卡密登录
int r = ZL_CardLogin(h, "你的卡密");
if (r != 0) {
char err[512];
ZL_GetLastErrorMessage(h, err, sizeof(err));
printf("登录失败: %s\n", err);
ZL_Destroy(h);
return 1;
}
// ③ 开启自动心跳(默认是关的,建议打开)
ZL_SetAutoHeartbeat(h, 1, 0);
// ④ 取数据
char buf[65536];
ZL_GetExpire(h);
ZL_GetResult(h, buf, sizeof(buf));
printf("到期信息: %s\n", buf);
ZL_Logout(h);
ZL_Destroy(h);
return 0;
}
ZL_SetConfig 和 ZL_CardLogin 里自动完成。4配置参数
方式一:整串配置(推荐)
直接把后台生成的 product_token 交给 ZL_SetConfig:
ZL_SetConfig(h, "后台复制的那一整串");
返回值 0 表示配置成功;返回 -2 表示这串内容不合法(抄漏了、被改过、或根本不是对接串)。
方式二:逐项设置(一般不用)
如果你确实需要自己控制每一项,可以分别调用:
| 函数 | 说明 | 必填 |
|---|---|---|
ZL_SetServer | 服务器地址,如 https://your-server.com(不要带 /api/open) | 必填 |
ZL_SetAppUUID | 后台应用 UUID | 必填 |
ZL_SetSecret | Ed25519 验签公钥(sign_scheme=1)或对称 Secret(=0) | 必填 |
ZL_SetKeys | AES 与 ChaCha20 密钥(64 位 hex) | 按算法 |
ZL_SetCrypto | algorithm:0 明文 / 1 AES / 2 ChaCha20;sign_scheme:0 对称 / 1 Ed25519 | 必填 |
ZL_SetClientVersion | 你的程序版本号,如 1.0.0 | 建议 |
ZL_SetProgramName | 程序名(用于提示框标题、后台显示) | 选填 |
-3,错误信息里会直接写「缺少:xxx」,照着补即可。其它可调项
| 函数 | 作用 | 默认 |
|---|---|---|
ZL_SetAutoHeartbeat | 开启内置自动心跳线程。interval=0 表示跟随服务端下发的周期 | 关闭 |
ZL_SetMachineRule | 机器码采集方案,见第九节 | 规则 2 |
ZL_SetMachineId | 手动指定机器码(覆盖自动生成) | 自动 |
ZL_SetDeviceName | 设备名,默认「系统名-计算机名」 | 自动 |
ZL_SetChallengeMode | 挑战码模式:-1 自动 / 0 关闭 / 1 强制 | -1 自动 |
5调用流程
标准流程如下,绝大多数场景照这个顺序写就行:
ZL_Create() 创建实例,拿到句柄
│
ZL_SetConfig(对接串) 配置(一次性)
│
ZL_SetAutoHeartbeat(1, 0) 开自动心跳(建议)
│
ZL_CardLogin(卡密) 登录 → 成功返回 0
│ └─ 失败:ZL_GetLastErrorMessage 取原因
│
┌──┴──────────────────────┐
│ 业务调用 │
│ ZL_GetExpire │
│ ZL_GetNotice │
│ ZL_GetVariable │
│ ZL_PointsDeduct │
│ ... │
└──┬──────────────────────┘
│
ZL_Logout() 退出登录
│
ZL_Destroy() 销毁实例(自动停心跳、释放资源)
ZL_Destroy 必须调用,否则心跳线程和句柄资源不会释放。程序退出前记得销毁。结果怎么取
所有业务接口只返回一个 int(成功 0 / 失败非 0),真正的数据要用 ZL_GetResult 取:
char buf[65536];
int r = ZL_GetExpire(h);
if (r == 0) {
ZL_GetResult(h, buf, sizeof(buf));
// buf 里是 JSON,例如:
// {"code":0,"data":{"expired_at":"2026-12-31T23:59:59+08:00",...}}
printf("%s\n", buf);
} else {
char err[512];
ZL_GetLastErrorMessage(h, err, sizeof(err));
printf("失败(%d): %s\n", r, err);
}
ZL_GetResult 返回 -9,内容会被截断,不会崩。6各语言示例
C / C++
工程设置:不需要包含目录(包里没有头文件),链接器附加依赖项加 ZhilianSDK_x64.lib 即可。
#pragma comment(lib, "ZhilianSDK_x64.lib")
void* h = ZL_Create();
ZL_SetConfig(h, token);
if (ZL_CardLogin(h, card) == 0) {
ZL_SetAutoHeartbeat(h, 1, 0);
// 登录成功
}
ZL_Destroy(h);
Python
import ctypes, os
# DLL 与脚本同目录;若放别处,把该目录加进搜索路径
os.add_dll_directory(os.path.dirname(os.path.abspath(__file__)))
dll = ctypes.WinDLL("ZhilianSDK_x64.dll")
dll.ZL_Create.restype = ctypes.c_void_p
dll.ZL_SetConfig.argtypes = [ctypes.c_void_p, ctypes.c_char_p]
dll.ZL_CardLogin.argtypes = [ctypes.c_void_p, ctypes.c_char_p]
dll.ZL_GetResult.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_uint32]
dll.ZL_Destroy.argtypes = [ctypes.c_void_p]
h = dll.ZL_Create()
assert dll.ZL_SetConfig(h, open("token.txt").read().strip().encode()) == 0
assert dll.ZL_CardLogin(h, b"你的卡密") == 0
buf = ctypes.create_string_buffer(65536)
dll.ZL_GetExpire(h)
dll.ZL_GetResult(h, buf, 65536)
print(buf.value.decode("utf-8"))
dll.ZL_Destroy(h)
bytes(.encode()),返回值也要 .decode("utf-8")。别忘了给每个函数设 restype/argtypes,否则 64 位下句柄会被截断。易语言
易语言编译出来是 32 位程序,请用 ZhilianSDK_x86.dll。
- 菜单「插入」→「DLL命令」,新建一条:
| 项 | 填写内容 |
|---|---|
| DLL命令名 | ZL_Create |
| 返回值类型 | 整数型 |
| 公开 | 否 |
| 库文件名 | ZhilianSDK_x86.dll |
| 在库中对应命令名 | ZL_Create |
| 调用方式 | stdcall(一定要选这个) |
按同样方式依次添加:
| 命令名 | 返回值 | 参数 |
|---|---|---|
ZL_SetConfig | 整数型 | 句柄(整数型)、配置串(文本型) |
ZL_CardLogin | 整数型 | 句柄(整数型)、卡密(文本型) |
ZL_GetResult | 整数型 | 句柄(整数型)、缓冲区(文本型,传址)、长度(整数型) |
ZL_GetLastErrorMessage | 整数型 | 句柄(整数型)、缓冲区(文本型,传址)、长度(整数型) |
ZL_SetAutoHeartbeat | 整数型 | 句柄(整数型)、开启(整数型)、间隔(整数型) |
ZL_Destroy | 无返回值 | 句柄(整数型) |
.局部变量 句柄, 整数型
.局部变量 结果, 文本型
.局部变量 长度, 整数型
句柄 = ZL_Create ()
.如果真 (ZL_SetConfig (句柄, 配置串) ≠ 0)
信息框 (“配置失败”, 0, , )
返回 ()
.如果真结束
.如果真 (ZL_CardLogin (句柄, 卡密) ≠ 0)
结果 = 取空白文本 (512)
ZL_GetLastErrorMessage (句柄, 结果, 512)
信息框 (结果, 0, , )
ZL_Destroy (句柄)
返回 ()
.如果真结束
ZL_SetAutoHeartbeat (句柄, 1, 0)
' ... 业务代码 ...
ZL_Destroy (句柄)
ZL_Utf8ToAnsi(utf8, 缓冲, 长度) 转成 GBK 再显示,或者直接用支持 UTF-8 的组件。C#
using System;
using System.Runtime.InteropServices;
using System.Text;
class ZL {
const string D = "ZhilianSDK_x64.dll";
[DllImport(D, CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr ZL_Create();
[DllImport(D, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
public static extern int ZL_SetConfig(IntPtr h, string json);
[DllImport(D, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
public static extern int ZL_CardLogin(IntPtr h, string card);
[DllImport(D, CallingConvention = CallingConvention.StdCall)]
public static extern int ZL_SetAutoHeartbeat(IntPtr h, int enable, int interval);
[DllImport(D, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
public static extern int ZL_GetLastErrorMessage(IntPtr h, StringBuilder buf, uint size);
[DllImport(D, CallingConvention = CallingConvention.StdCall)]
public static extern void ZL_Destroy(IntPtr h);
}
// 调用
IntPtr h = ZL.ZL_Create();
if (ZL.ZL_SetConfig(h, token) != 0) { /* 失败 */ }
if (ZL.ZL_CardLogin(h, "卡密") == 0) {
ZL.ZL_SetAutoHeartbeat(h, 1, 0);
}
ZL.ZL_Destroy(h);
Delphi / VB6 / 其他
function ZL_Create: Pointer; stdcall; external 'ZhilianSDK_x86.dll'; function ZL_SetConfig(h: Pointer; cfg: PAnsiChar): Integer; stdcall; external 'ZhilianSDK_x86.dll'; function ZL_CardLogin(h: Pointer; card: PAnsiChar): Integer; stdcall; external 'ZhilianSDK_x86.dll';
Private Declare Function ZL_Create Lib "ZhilianSDK_x86.dll" () As Long Private Declare Function ZL_SetConfig Lib "ZhilianSDK_x86.dll" (ByVal h As Long, ByVal cfg As String) As Long Private Declare Function ZL_CardLogin Lib "ZhilianSDK_x86.dll" (ByVal h As Long, ByVal card As String) As Long
7Linux 版(.so)
Linux 版和 Windows 版是同一份源码编译出来的:64 个函数名、参数、返回值、协议、加密、验签完全一致,服务端和后台配置都不用改。不同的只有文件形态和几处平台相关的东西。
运行时只要一个 .so
| 项目 | 值 |
|---|---|
| 文件 | libZhilianSDK.so(约 895 KB) |
| 架构 | x86_64(ELF64) |
| glibc | ≥ 2.17(CentOS 7 / Ubuntu 16.04 起全兼容) |
| 依赖 | 只有 libc / libpthread / libdl |
| 导出 | 64 个 ZL_*,与 Windows 逐个一致 |
libssl.so.3 / 1.1),没有就自动降级成 HTTP,绝不会因为缺库导致 .so 加载失败。libZhilianSDK.so,客户程序链接后记的就是这个名字,改名会报 cannot open shared object file——和 Windows 上把 DLL 改名是同一类问题。编译与链接
-Wl,-rpath,'$ORIGIN' 的意思是「优先在程序自己的目录找 .so」。这样客户把程序和 .so 一起拷到任何目录都能跑,不用配 LD_LIBRARY_PATH。
# C gcc main.c -I. -L. -lZhilianSDK -Wl,-rpath,'$ORIGIN' -o myapp # C++ g++ main.cpp -I. -L. -lZhilianSDK -Wl,-rpath,'$ORIGIN' -o myapp
不想用 rpath 的话还有两种放法:
| 方式 | 做法 | 适用场景 |
|---|---|---|
| 程序同目录 | -Wl,-rpath,'$ORIGIN' | 推荐,随程序一起分发 |
| 系统库目录 | cp libZhilianSDK.so /usr/local/lib/ && ldconfig | 装在服务器上给多个程序共用 |
| 环境变量 | export LD_LIBRARY_PATH=/your/path:$LD_LIBRARY_PATH | 临时调试 |
C 示例
#include <stdio.h>
#include <stdlib.h>
int main(void) {
void* h = ZL_Create();
if (!h) { printf("创建失败\n"); return 1; }
/* 粘贴后台「一键生成对接参数」里那串加密文本 */
int r = ZL_SetConfig(h, "粘贴你的 product_token");
if (r != 0) { printf("配置失败: %d\n", r); ZL_Destroy(h); return 1; }
r = ZL_CardLogin(h, "你的卡密");
if (r != 0) {
char err[1024] = {0};
ZL_GetLastErrorMessage(h, err, sizeof(err));
printf("登录失败: %d %s\n", r, err);
ZL_Destroy(h);
return 1;
}
char* buf = (char*)malloc(65536);
ZL_GetResult(h, buf, 65536);
printf("登录结果: %s\n", buf);
free(buf);
ZL_SetAutoHeartbeat(h, 1, 0); /* 自动心跳,周期跟随服务端下发 */
/* ... 你的业务逻辑 ... */
ZL_Destroy(h);
return 0;
}
Python 示例
用 ctypes 一定要先设 restype = c_void_p,否则 64 位下句柄会被截成 32 位整数,后面所有调用都会失败。
import ctypes
lib = ctypes.CDLL("./libZhilianSDK.so")
H, S, U = ctypes.c_void_p, ctypes.c_char_p, ctypes.c_uint32
lib.ZL_Create.restype = H
lib.ZL_SetConfig.argtypes = [H, S]
lib.ZL_CardLogin.argtypes = [H, S]
lib.ZL_GetResult.argtypes = [H, S, U]
lib.ZL_GetLastErrorMessage.argtypes = [H, S, U]
lib.ZL_SetAutoHeartbeat.argtypes = [H, ctypes.c_int, ctypes.c_int]
lib.ZL_Destroy.argtypes = [H]
h = lib.ZL_Create()
print("SetConfig =", lib.ZL_SetConfig(h, b"粘贴你的 product_token"))
print("CardLogin =", lib.ZL_CardLogin(h, b"你的卡密"))
buf = ctypes.create_string_buffer(65536)
lib.ZL_GetResult(h, buf, 65536)
print(buf.value.decode("utf-8", "replace"))
lib.ZL_SetAutoHeartbeat(h, 1, 0)
# ... 业务 ...
lib.ZL_Destroy(h)
与 Windows 版的差异
| 项目 | Windows | Linux |
|---|---|---|
| 交付文件 | DLL + 导入库 | 单个 .so |
| 调用约定 | __stdcall | 无(ZL_CALL 展开为空) |
| 机器码来源 | CPUID / SMBIOS / DeviceIoControl | /proc/cpuinfo、/sys/class/dmi/id、getifaddrs |
| 同一台机器的机器码 | 一套 | 另一套(与 Windows 不同,设计如此) |
| HTTPS | 系统 WinINet 自带 | 运行时加载 OpenSSL,缺则仅 HTTP |
| 弹窗接口 | MessageBox 生效 | 函数保留,但没有窗口 |
| Utf8ToAnsi | 真实转码(给 GBK 环境用) | 原样返回(全链路 UTF-8) |
| 加壳 | VMProtect 可加 | 无对应工具(交付前已 strip 符号) |
跑不起来怎么查
| 现象 | 原因 | 解决 |
|---|---|---|
| cannot open shared object file | 找不到 .so | 放程序同目录 + rpath,或 LD_LIBRARY_PATH,或装到 /usr/local/lib 后 ldconfig |
| wrong ELF class: ELFCLASS32 | 32 位程序加载 64 位 .so | 本包只有 x86_64,需用 64 位程序 |
| version `GLIBC_2.28' not found | 系统 glibc 太老 | 本包要求 glibc ≥ 2.17 |
| 提示「系统缺少 OpenSSL」 | 没装 libssl | 装 openssl-libs,或服务端改用 HTTP |
| 中文乱码 | 终端 / locale 不是 UTF-8 | 输出本身就是 UTF-8,换个 UTF-8 终端或设 LANG |
| 改名后加载失败 | SONAME 固定 | 保持文件名 libZhilianSDK.so |
上线前自检
Linux 包里带了两个自检程序,随便用哪个:
chmod +x selftest ./selftest # 不带卡:加载 / 机器码 / 配置 / 公告 / 更新 / 版本 / 挑战码 ./selftest 你的卡密 # 完整:登录 / 心跳 / 到期 / 数据 / 授权 / 自动心跳 # 或者用 Python 版(信息更详细,会打印系统版本、glibc、.so 的 MD5) python3 test_so.py 你的卡密
8接口清单
全部 64 个导出函数。返回 0 成功;>0 服务端业务错误;<0 SDK 层错误。
h = 句柄(指针,易语言填整数型);card / account / password / token / json / name = UTF-8 字符串 const char*;buf, size = 输出缓冲区 + 容量;amount / seconds / minutes = 整数;返回值统一 int。配置与生命周期
| 接口 | 函数 | 说明 |
|---|---|---|
| — | ZL_Create / ZL_Destroy | 创建 / 销毁实例 |
| — | ZL_SetConfig(h, json) | 一次性配置(对接串或 JSON) |
| — | ZL_SetServer / SetAppUUID / SetSecret / SetKeys / SetCrypto | 逐项配置 |
| — | ZL_SetClientVersion / SetProgramName | 版本号 / 程序名 |
| — | ZL_SetMachineRule / SetMachineId / GetMachineId / SetDeviceName | 机器码与设备名 |
| — | ZL_SetAutoHeartbeat / SetChallengeMode | 自动心跳 / 挑战码模式 |
登录
| 接口 | 函数 | 说明 |
|---|---|---|
| 5 | ZL_CardLogin(h, card) | 卡密登录,成功后 token 由 SDK 内部保存 |
| 6 | ZL_AccountLogin(h, user, pass) | 账号密码登录 |
| 30 | ZL_DeviceTrialLogin(h, email, code) | 设备试用登录(需后台开启) |
| 13 | ZL_Logout(h) | 退出登录 |
账号
| 接口 | 函数 | 说明 |
|---|---|---|
| 7 | ZL_UserRegister(h, user, pass, email, code, card) | 注册(code 为邮箱验证码,card 可传空串) |
| 8 | ZL_Recharge(h, user, card) | 账号充值 |
| 9 | ZL_SendEmailCode(h, email) | 发送注册验证码 |
| 10 | ZL_ClaimTrial(h, user, pass) | 领取试用 |
| 11 | ZL_SendResetCode(h, email) | 发送找回密码验证码 |
| 12 | ZL_ResetPassword(h, email, code, newpass) | 找回密码 |
| 20 | ZL_UpdatePassword(h, user, oldpass, newpass) | 修改密码 |
| 21 | ZL_ChangeBind(h, user, pass, replace_machine) | 转绑(replace_machine 传空 = 删除当前机器绑定) |
查询与数据
| 接口 | 函数 | 说明 |
|---|---|---|
| 1 | ZL_GetNotice(h) | 程序公告 / 更新信息(免登录可调) |
| 2 | ZL_GetUpdateUrl(h) | 更新地址 |
| 3 | ZL_CheckVersion(h, version) | 检测最新版本(version 传 NULL 用配置版本) |
| 4 | ZL_GetCardInfo(h, card) | 卡密信息(免登录可调) |
| 14 | ZL_GetExpire(h) | 到期时间 |
| 15 | ZL_Heartbeat(h) | 心跳 / 检测账号状态 |
| 16 | ZL_GetAppData(h) | 程序数据 |
| 17 | ZL_GetVariable(h, alias) | 取远程变量 |
| 18 | ZL_ExecuteFunction(h, alias, params) | 执行远程函数(params 传 "{}") |
| 19 | ZL_GetUserData(h) | 取账号云数据 |
| 22 | ZL_SetUserData(h, json) | 存账号云数据 |
| 28 | ZL_PointsDeduct(h, amount, biz_no, remark) | 扣减点数(biz_no 幂等号可传 NULL,remark 为备注) |
| 29 | ZL_PointsQuery(h) | 查询点数余额 |
| 31 | ZL_SendTrialCode(h, email) | 发送试用邮箱验证码 |
| 34 | ZL_PublicChallenge(h) | 申请挑战码(登录自动处理,一般不用手动调) |
| 35 | ZL_ExecuteLua(h, alias, params) | 执行 LUA 远程函数 |
授权与支付
| 接口 | 函数 | 说明 |
|---|---|---|
| 26 | ZL_WinLicense(h, machine_code) | 申请 WinLicense 授权文件(machine_code 传 NULL 用当前机器码) |
| 36 | ZL_VMPLicense(h, vmp_hwid) | 申请 VMP 授权码(vmp_hwid 为 VMP 环境的硬件 ID) |
| 37 | ZL_OnlinePay(h, tier_id) | 在线支付下单 |
| 38 | ZL_Call(h, 38, "{}") | 获取 VMP 成品授权(RSA 公钥等),无独立函数 |
工具函数
| 函数 | 说明 |
|---|---|
ZL_GetResult(h, buf, size) | 取上次操作的完整结果 JSON(UTF-8) |
ZL_GetLastError(h) | 取上次错误码 |
ZL_GetLastErrorMessage(h, buf, size) | 取错误描述(含服务端返回的中文提示) |
ZL_GetState(h) | 当前状态:0 已创建未配置 / 1 已配置 / 2 已登录 / 3 已登出 |
ZL_GetToken(h, buf, size) | 取当前会话 token |
ZL_GetHeartbeatInterval(h) | 服务端下发的心跳间隔(秒) |
ZL_Utf8ToAnsi / ZL_AnsiToUtf8 | UTF-8 ↔ GBK 转换(易语言常用) |
ZL_SetUICallback(h, cb, ud) | 自定义提示回调(替代系统 MessageBox) |
ZL_ShowErrorBox / ZL_ShowResultBox | 可选提示框,只有你显式调用才会弹 |
ZL_GetVersionString() | SDK 版本号(两平台一致) |
ZL_IsProtected() | 是否已被 VMProtect 加壳:Windows 加壳后 1、未加壳 0;Linux 恒 0 |
ZL_GetResult / ZL_GetLastErrorMessage 给你,怎么显示完全由你决定。ZL_Show*Box 不调用就永远不会有窗口(Linux 上这两个函数保留,但没有窗口可弹)。9返回值与错误码
返回值的三种情况
| 返回值 | 含义 | 怎么处理 |
|---|---|---|
= 0 | 成功 | 用 ZL_GetResult 取数据 |
> 0 | 服务端业务错误(如卡号不存在、账号已到期) | 用 ZL_GetLastErrorMessage 取中文提示,直接展示给用户 |
< 0 | SDK 层错误(网络、配置、验签等) | 见下表 |
错误码
| 值 | 常量 | 含义与处理 |
|---|---|---|
| -1 | ZL_ERR_INVALID_HANDLE | 句柄无效,检查是否已 ZL_Destroy |
| -2 | ZL_ERR_INVALID_ARG | 参数不合法(如对接串被改过) |
| -3 | ZL_ERR_NOT_CONFIGURED | 配置不完整,错误信息会写明缺哪个字段 |
| -4 | ZL_ERR_NETWORK | 连不上服务器,检查地址、端口、防火墙 |
| -5 | ZL_ERR_TIMEOUT | 请求超时 |
| -6 | ZL_ERR_HTTP | HTTP 层错误 |
| -7 | ZL_ERR_JSON | 响应格式异常 |
| -8 | ZL_ERR_CRYPTO | 加解密失败(密钥不对) |
| -9 | ZL_ERR_BUFFER_SMALL | 你的缓冲区太小,换大一点 |
| -10 | ZL_ERR_INTERNAL | 内部错误 |
| -11 | ZL_ERR_NOT_LOGGED_IN | 还没登录就调了需要登录的接口 |
| -12 | ZL_ERR_SESSION_EXPIRED | 会话失效,需要重新登录 |
| -13 | ZL_ERR_SIGN_VERIFY | 响应验签失败(数据被篡改,或配置的公钥与服务器不匹配) |
| -14 | ZL_ERR_MACHINE_CODE | 机器码生成失败 |
r == 0 判定成功,非 0 就把 ZL_GetLastErrorMessage 的内容显示出来——服务端返回的中文提示已经足够友好,不用自己再翻译一遍。10机器码说明
机器码用于「一机一码」绑定。SDK 会自动采集硬件信息并生成 32 位大写 MD5,你只需要调 ZL_GetMachineId 取。
不同硬件环境(虚拟机、云主机、部分精简系统)可能取不到某些项,可以换规则:
| 规则 | 采集项 | 适用场景 |
|---|---|---|
| 1 | CPU + 主板UUID + 磁盘 + 主板序列号 + MAC | 最严格,但换网卡会变 |
| 2 | CPU + 主板UUID + 磁盘 + 主板序列号 | 默认,稳定性最好 |
| 3 | 磁盘 + 主板序列号 | CPU 等信息取不到时 |
| 4 | 仅 CPU | 虚拟机环境 |
| 5 | 仅磁盘 | 换硬件频繁时 |
| 6 | 仅 MAC | 特殊场景 |
ZL_SetMachineRule(h, ZL_MACHINE_RULE_DEFAULT); // 2
char mc[128];
ZL_GetMachineId(h, mc, sizeof(mc));
printf("机器码: %s\n", mc); // 例如 EDB3C3254251E0206503850039E74BE6
11常见问题
加载 DLL 报 0xC0000135 / 找不到模块
- DLL 没放在 exe 同目录(或程序搜索路径里)
- 位数不匹配:32 位程序加载了 x64 的 DLL,或反过来
Linux 上加载 .so 失败
按报错对号入座:
cannot open shared object file—— 找不到文件。放程序同目录并加-Wl,-rpath,'$ORIGIN',或设LD_LIBRARY_PATHwrong ELF class—— 位数不对,本包只有x86_64version `GLIBC_2.xx' not found—— 系统 glibc 太老,本包要求 ≥ 2.17
完整对照表和示例代码见第七节。
返回 -3,说「缺少 xxx」
配置不完整。用对接串一般不会出现;如果是逐项设置,按错误信息补对应字段即可。
返回 -13 验签失败
三种可能:① 对接串里是别人的密钥;② 服务器地址填错,连到了另一个站;③ 中间有代理改包。先核对对接串是不是这个应用的。
中文显示乱码
接口返回的是 UTF-8。易语言、VB6 这类 GBK 环境用 ZL_Utf8ToAnsi 转一下;Python 用 .decode("utf-8");C# 用 Encoding.UTF8.GetString。
要不要自己开心跳
建议开:ZL_SetAutoHeartbeat(h, 1, 0)。默认是关闭的,不开的话长时间在线可能被服务端判定掉线。interval 传 0 就会自动跟随服务端下发的周期,不用自己算。
多线程能共用一个句柄吗
SDK 内部有锁,多线程调同一个句柄不会崩,但结果会互相覆盖(ZL_GetResult 取到的是"最后一次")。推荐每个线程用独立句柄(各自 ZL_Create),互不干扰。
公告里说的「挑战码」我要处理吗
不用。SDK 默认会自动判断:后台开了公开效验就自动申请并带上,没开就不发。除非你手动调了 ZL_SetChallengeMode(h, 0) 关掉,否则不用管。
WinLicense / VMP 授权怎么用
登录成功后调 ZL_WinLicense(接口 26)或 ZL_VMPLicense(接口 36),把返回的授权数据交给对应的加壳工具。machine_code / vmp_hwid 传 NULL 时会自动用当前环境的值。
能不能做成单文件,不单独带 DLL
可以,DLL 不是非得躺在 exe 旁边。常见三种做法:
- 资源嵌入 + 运行时释放再加载(最稳,推荐)
把 DLL 作为RCDATA资源编进 exe,启动时写到%TEMP%或%LOCALAPPDATA%目录,再LoadLibrary那个路径。加壳版 DLL 也能正常用,兼容性最好。 - 用打包工具虚拟进 exe
比如 Enigma Virtual Box,配置好把 DLL 虚拟进去,不落盘。省事,但个别杀软可能对产物误报。 - MemoryModule 之类内存加载(完全不落盘)
注意:本 DLL 经过 VMProtect 加壳,标准 MemoryModule 实现通常不处理 TLS 回调和异常目录,大概率加载失败。想走这条路必须自己实测,别直接上生产。
.lib 是导入库,不是静态库——用它链接后运行时仍然需要 DLL,不能靠它做到「真静态链接」。要做单文件只能用上面三种方式。需要头文件(ZhilianSDK.h)吗
不需要,包里不提供 .h。声明函数各语言都能自己做,一次写完就不用再管:
- 易语言:菜单「插入 → DLL命令」,按「八、接口清单」填函数名和参数类型
- C#:
[DllImport("ZhilianSDK_x64.dll")] - Delphi / VB6:
external/Declare Function ... Lib - Python:
ctypes设restype/argtypes - C / C++:自己声明原型,或用
GetProcAddress/dlsym动态取地址
12注意事项
- 位数必须匹配:32 位程序用
x86,64 位用x64;Linux 目前只提供x86_64。 - Windows 的 DLL 默认放 exe 同目录,或保证在系统搜索路径里;不想单独带文件的话可以内嵌成单文件(见常见问题)。
- Linux 的 .so 放程序同目录并加
-Wl,-rpath,'$ORIGIN';文件名不要改,SONAME 固定为libZhilianSDK.so。 - 用完必须
ZL_Destroy,否则心跳线程不退出。 - 句柄不要跨进程/跨模块传递,它只在当前进程有效。
- 字符串统一 UTF-8,GBK 环境记得转码。
- 缓冲区建议 ≥ 64KB,公告和账号数据可能比较长。
- 对接串属于敏感信息,不要明文写在会被别人看到的地方;它已经做了加密,但能被复制就能被使用。
- 如果程序要加壳(VMProtect / WinLicense 等),先完成接入调试,再加壳,否则排错会很痛苦。Linux 没有对应的加壳工具,交付的 .so 已 strip 掉符号表。
ZL_GetLastErrorMessage 的输出和调用顺序发过来,定位起来很快。