其他语言接入
客户端 SDK 对外是标准的 C 接口,只导出三个函数,使用 __stdcall 调用约定。任何能调用 Windows DLL 的语言都可以接入,不需要额外的语言专用 SDK。
接口规格
| 库文件 | protect.dll(32 位程序用 windows/x86/,64 位程序用 windows/x86_64/) |
|---|---|
| 调用约定 | __stdcall(即 Win32 的 WINAPI) |
| 字符编码 | UTF-8 |
| 导出函数 | protect_start / protect_stop / protect_error,均为未修饰名,不带 @4、@0 后缀 |
| 函数 | 签名与返回值 |
|---|---|
protect_start | int protect_start(const char* config) 返回 0 表示成功,其他值表示失败 |
protect_stop | int protect_stop(void) 停止防护,退出前必须调用 |
protect_error | const char* protect_error(void) 最近一次错误描述,无错误返回空串 |
位数必须匹配。32 位程序只能加载
x86 的 DLL,64 位程序只能加载 x86_64 的。位数放错会直接加载失败,而且报错信息通常看不出原因。易语言、32 位 Python、32 位 JVM 都属于 32 位程序。
选择你的语言
C / C++
完整示例(加载、启动、停止、卸载的完整时序,含 protect_error 用法)见 Windows 客户端接入。
最简形式:
#include <windows.h>
typedef int (WINAPI *protect_start_t)(const char*);
typedef int (WINAPI *protect_stop_t)(void);
HINSTANCE h = LoadLibrary("protect.dll");
if (!h) return; // 加载失败:多半是位数不匹配,或 DLL 不在程序目录
auto start = (protect_start_t)GetProcAddress(h, "protect_start");
auto stop = (protect_stop_t)GetProcAddress(h, "protect_stop");
if (!start || !stop) { FreeLibrary(h); return; }
start("{\"access_key\":\"你的接入码\"}");
// ... 程序运行 ...
stop();
FreeLibrary(h);
易语言
易语言可以直接声明 DLL 命令,不需要任何额外组件。注意易语言编译出的是 32 位程序,必须使用 windows/x86/protect.dll。
第一步:声明 DLL 命令(在「插入 → DLL 命令」中填写,或用下面的代码粘贴到源码)
.版本 2
.DLL命令 protect_start, 整数型, "protect.dll", "protect_start", 公开
.参数 config, 文本型, , , , JSON 配置,形如 {"access_key":"接入码"}
.DLL命令 protect_stop, 整数型, "protect.dll", "protect_stop", 公开
.DLL命令 protect_error, 文本型, "protect.dll", "protect_error", 公开
第二步:启动与停止
.子程序 __启动窗口_创建完毕
.局部变量 cfg, 文本型
.局部变量 ret, 整数型
cfg = "{" + #引号 + "access_key" + #引号 + ":" + #引号 + "你的接入码" + #引号 + "}"
ret = protect_start (cfg)
.如果真 (ret ≠ 0)
信息框 ("防护启动失败:" + protect_error (), 0, , )
.如果真结束
.子程序 __启动窗口_将被销毁
protect_stop ()
易语言的「文本型」是 ANSI(GBK)编码。如果你的接入码只包含英文字母和数字(通常都是这样),GBK 与 UTF-8 的字节完全一致,可以直接传。如果接入码包含中文,需要先转成 UTF-8 字节集再把指针传进去。
调用约定。易语言的 DLL 命令默认就是
__stdcall,与本 SDK 一致,不需要额外设置。如果你在命令名后加了 @(表示 __cdecl),会调用崩溃,去掉即可。
错误信息可能显示乱码。
protect_error 返回的是 UTF-8 字符串,而易语言的「文本型」按 GBK 解释,中文错误信息会变成乱码。这不影响启动是否成功的判断(看返回码即可);需要读具体内容时,用精易模块的「编码_Utf8到Ansi」转一下。
Java
用 JNA 调用最简单,不需要写 JNI。先加依赖:
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.14.0</version>
</dependency>
定义接口,注意继承 StdCallLibrary 而不是普通的 Library:
import com.sun.jna.Native;
import com.sun.jna.win32.StdCallLibrary;
public interface Protect extends StdCallLibrary {
Protect INSTANCE = Native.load("protect", Protect.class);
int protect_start(String config);
int protect_stop();
String protect_error();
}
调用:
import com.sun.jna.Native;
public class Demo {
public static void main(String[] args) {
// 让 JNA 按 UTF-8 转换字符串,否则中文错误信息会乱码
Native.setDefaultStringEncoding("UTF-8");
String cfg = "{\"access_key\":\"你的接入码\"}";
int ret = Protect.INSTANCE.protect_start(cfg);
if (ret != 0) {
System.out.println("启动失败: " + Protect.INSTANCE.protect_error());
}
// 退出前停止
Runtime.getRuntime().addShutdownHook(
new Thread(Protect.INSTANCE::protect_stop));
}
}
JVM 位数必须与 DLL 一致。64 位 JVM 要用
x86_64 的 DLL,32 位 JVM 用 x86 的。可以用 com.sun.jna.Native.POINTER_SIZE 判断,返回 4 表示 32 位、8 表示 64 位(比 sun.arch.data.model 可靠,后者在 JDK 9+ 可能取不到)。
JNA 对 String 参数默认按系统编码转换。接入码含中文时,先调用
Native.setDefaultStringEncoding("UTF-8"),或者直接传 byte[]。
C#
用 P/Invoke 声明,注意 CallingConvention.StdCall:
using System;
using System.Text;
using System.Runtime.InteropServices;
class Program
{
[DllImport("protect.dll", CallingConvention = CallingConvention.StdCall)]
static extern int protect_start(byte[] config);
[DllImport("protect.dll", CallingConvention = CallingConvention.StdCall)]
static extern int protect_stop();
[DllImport("protect.dll", CallingConvention = CallingConvention.StdCall)]
static extern IntPtr protect_error();
static void Main()
{
// 手动转 UTF-8 并补 0 结尾,避免系统 ANSI 编码问题
byte[] cfg = Encoding.UTF8.GetBytes("{\"access_key\":\"你的接入码\"}\0");
int ret = protect_start(cfg);
if (ret != 0)
{
IntPtr p = protect_error();
Console.WriteLine("启动失败: " + Marshal.PtrToStringUTF8(p));
}
try
{
Console.ReadLine(); // 程序运行期间保持
}
finally
{
protect_stop(); // 异常退出也能保证停止
}
}
}
这里用
byte[] 而不是 string 传参,是为了精确控制 UTF-8 编码。
如果接入码只含 ASCII 字符,直接用 string 配 CharSet.Ansi 也可以。
Marshal.PtrToStringUTF8 需要 .NET Core 3.0 / .NET 5 及以上。
如果项目跑在 .NET Framework 4.x 上,这个方法不存在,需要手动转:先统计指针处的字节长度,再用
Marshal.Copy 取到 byte[],最后 Encoding.UTF8.GetString() 解码。
Python
用标准库 ctypes,注意 __stdcall 要用 WinDLL(不是 CDLL):
import ctypes
lib = ctypes.WinDLL("protect.dll") # WinDLL = __stdcall
lib.protect_start.argtypes = [ctypes.c_char_p]
lib.protect_start.restype = ctypes.c_uint32
lib.protect_stop.argtypes = []
lib.protect_stop.restype = ctypes.c_uint32
lib.protect_error.argtypes = []
lib.protect_error.restype = ctypes.c_char_p
cfg = '{"access_key":"你的接入码"}'.encode("utf-8")
ret = lib.protect_start(cfg)
if ret != 0:
err = lib.protect_error()
print("启动失败:", err.decode("utf-8", "replace") if err else "(无错误信息)")
try:
input("防护已启动,按回车停止...")
finally:
lib.protect_stop()
用
ctypes.CDLL 加载 __stdcall 的 DLL,在 32 位下会栈不平衡导致崩溃。必须用 WinDLL。
Go
用 syscall.NewLazyDLL,Windows 上默认就是 __stdcall:
package main
import (
"fmt"
"syscall"
"time"
"unsafe"
)
var (
dll = syscall.NewLazyDLL("protect.dll")
start = dll.NewProc("protect_start")
stop = dll.NewProc("protect_stop")
getErr = dll.NewProc("protect_error")
)
func main() {
if err := dll.Load(); err != nil {
fmt.Println("加载 protect.dll 失败:", err)
return
}
cfg := append([]byte(`{"access_key":"你的接入码"}`), 0)
ret, _, _ := start.Call(uintptr(unsafe.Pointer(&cfg[0])))
if ret != 0 {
p, _, _ := getErr.Call()
fmt.Println("启动失败:", syscall.BytePtrToString((*byte)(unsafe.Pointer(p))))
}
defer stop.Call()
time.Sleep(30 * time.Second) // 程序运行期间保持
}
注意 C 字符串需要
\0 结尾,代码里用 append(..., 0) 补上。Go 的字符串本身不带结尾符。
Delphi
直接声明外部函数,加 stdcall:
function protect_start(config: PAnsiChar): DWORD; stdcall; external 'protect.dll';
function protect_stop: DWORD; stdcall; external 'protect.dll';
function protect_error: PAnsiChar; stdcall; external 'protect.dll';
procedure TForm1.FormCreate(Sender: TObject);
var
cfg: AnsiString;
ret: DWORD;
begin
cfg := '{"access_key":"你的接入码"}';
ret := protect_start(PAnsiChar(cfg));
if ret <> 0 then
ShowMessage('启动失败: ' + UTF8ToString(protect_error));
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
protect_stop();
end;
用
AnsiString 而不是 string。在较新的 Delphi 版本里 string 默认是 Unicode(UTF-16),传进去会乱码。
UTF8ToString 需要 Delphi 2009 及以上。如果还在用 Delphi 7,改用
UTF8Decode(protect_error) 拿到 WideString 再显示;或者干脆只看返回码,不读错误文本。
通用注意事项
- 位数匹配。先确认你的程序是 32 位还是 64 位,再选对应目录的
protect.dll。这是最常见的失败原因。 - DLL 放在程序同目录。或者放到系统能找到的路径(当前目录、System32、PATH)。
- 生命周期。启动后必须调用停止函数再退出。漏掉停止可能导致进程退出时卡住。
- 接入码区分平台。控制台创建实例时要选 Windows 平台,用 Android 或 iOS 的接入码会启动失败。
- 失败时调
protect_error。只看返回码很难判断原因,错误描述能直接告诉你问题在哪。
不想写代码
如果客户没有开发能力,也可以用封装工具把 protect.dll 和启动逻辑打包成一个 EXE 启动器,填写接入码后直接生成,客户双击运行即可。这种方式不需要客户接触任何代码。
需要封装工具或协助打包,可以通过 联系我们 页面沟通。