幽冥大陆(四十)P50酒店门锁SDK Vlang语言仙盟插件——东方仙盟筑基期
Vlang 的实现。Vlang 作为静态类型语言,兼具简洁性和系统级交互能力,转换过程中适配了 Vlang 的语法特性、内存管理和 DLL 调用机制,核心业务逻辑与原 C# 代码完全一致:v。

Vlang 的实现。Vlang 作为静态类型语言,兼具简洁性和系统级交互能力,转换过程中适配了 Vlang 的语法特性、内存管理和 DLL 调用机制,核心业务逻辑与原 C# 代码完全一致:
v
module cyberphp_dynamic
import os
import time
import strconv
import json
import sync
import win32 // 需安装:v install vlang/win32
// 全局常量定义
const (
buf_card_size = 129 // 128 + 1
buf_card_v10_size = 201 // 200 + 1
dll_path_v10 = 'proRFLV102024.dll'
dll_path_p50 = 'proRFLP50202501.dll'
)
// 全局变量(使用互斥锁保证线程安全)
struct GlobalState {
mut:
card_data [128]u8
id_photo_save_path string
buf_card [buf_card_size]u8
buf_card_v10 [buf_card_v10_size]u8
}
var global = GlobalState{}
var mu = sync.Mutex{}
// 模拟C#的NameValueCollection
type NameValueCollection = map[string]string
// 协议解析结构体
struct ClCyberWinAPPProtocolPackage {
data map[string]string
}
// new_cl_cyber_win_app_protocol_package 创建协议解析实例
fn new_cl_cyber_win_app_protocol_package() &ClCyberWinAPPProtocolPackage {
return &ClCyberWinAPPProtocolPackage{
data: map[string]string{}
}
}
// format_string 解析协议字符串(支持 "key=value&key2=value2" 格式)
fn (mut c ClCyberWinAPPProtocolPackage) format_string(param string) {
c.data.clear()
for pair in param.split('&') {
parts := pair.split_n('=', 2)
if parts.len == 2 {
c.data[parts[0]] = parts[1]
}
}
}
// get 获取协议参数
fn (c &ClCyberWinAPPProtocolPackage) get(key string) string {
return c.data.get(key) or { '' }
}
// ------------------------------ DLL函数绑定 ------------------------------
// 注意:Vlang的DLL调用通过win32库实现,需确保DLL导出函数名和参数匹配
// proRFLV102024.dll 函数声明
type GetDLLVersion = fn (sDllVer &u8) int
type CloseUSB = fn ()
type Buzzer = fn (fUSB u8, t int) int
type ReadCard = fn (fUSB u8, buffer &u8) int
type ReadCardV10 = fn (fUSB u8, buffer &u8) int
type GuestCardRaw = fn (d12 int, dlsCoID int, cardNo int, dai int, llock int, pdoors int, bdate &u8, edate &u8, roomNo &u8, cardHexStr &u8) int
type CardEraseV10 = fn (d12 int, dlsCoID int, cardNo &u8) int
// proRFLP50202501.dll 函数声明
type InitializeUSBP50 = fn (d12 int) int
type CloseUSBP50 = fn (d12 int)
type BuzzerP50 = fn (fUSB u8, t int) int
type CardEraseP50 = fn (d12 int, dlsCoID int, cardNo &u8) int
type GuestCardRawP50 = fn (d12 int, dlsCoID int, cardNo int, dai int, llock int, pdoors int, bdate &u8, edate &u8, roomNo &u8, cardHexStr &u8) int
type GetGuestLockNoByCardDataStrP50 = fn (dlsCoID int, cardHexStr &u8, lockNo &u8) int
// 加载DLL函数
fn load_dll_functions() !(&GetDLLVersion, &CloseUSB, &Buzzer, &ReadCard, &ReadCardV10, &GuestCardRaw, &CardEraseV10, &InitializeUSBP50, &CloseUSBP50, &BuzzerP50, &CardEraseP50, &GuestCardRawP50, &GetGuestLockNoByCardDataStrP50) {
// 加载V10 DLL
dll_v10 := win32.load_library(dll_path_v10)!
get_dll_version := win32.get_proc_address[dll_v10, GetDLLVersion]('GetDLLVersion')!
close_usb := win32.get_proc_address[dll_v10, CloseUSB]('CloseUSB')!
buzzer := win32.get_proc_address[dll_v10, Buzzer]('Buzzer')!
read_card := win32.get_proc_address[dll_v10, ReadCard]('ReadCard')!
read_card_v10 := win32.get_proc_address[dll_v10, ReadCardV10]('ReadCard')! // 同名函数
guest_card_raw := win32.get_proc_address[dll_v10, GuestCardRaw]('GuestCard')! // 别名函数
card_erase_v10 := win32.get_proc_address[dll_v10, CardEraseV10]('CardErase')! // 别名函数
// 加载P50 DLL
dll_p50 := win32.load_library(dll_path_p50)!
initialize_usb_p50 := win32.get_proc_address[dll_p50, InitializeUSBP50]('initializeUSB')!
close_usb_p50 := win32.get_proc_address[dll_p50, CloseUSBP50]('CloseUSB')!
buzzer_p50 := win32.get_proc_address[dll_p50, BuzzerP50]('Buzzer')!
card_erase_p50 := win32.get_proc_address[dll_p50, CardEraseP50]('CardErase')!
guest_card_raw_p50 := win32.get_proc_address[dll_p50, GuestCardRawP50]('GuestCard')! // 别名函数
get_guest_lock_no_by_card_data_str_p50 := win32.get_proc_address[dll_p50, GetGuestLockNoByCardDataStrP50]('GetGuestLockNoByCardDataStr')!
return (get_dll_version, close_usb, buzzer, read_card, read_card_v10, guest_card_raw, card_erase_v10, initialize_usb_p50, close_usb_p50, buzzer_p50, card_erase_p50, guest_card_raw_p50, get_guest_lock_no_by_card_data_str_p50)
}
// 预加载DLL函数(程序启动时初始化)
var (
get_dll_version &GetDLLVersion
close_usb &CloseUSB
buzzer &Buzzer
read_card &ReadCard
read_card_v10 &ReadCardV10
guest_card_raw &GuestCardRaw
card_erase_v10 &CardEraseV10
initialize_usb_p50 &InitializeUSBP50
close_usb_p50 &CloseUSBP50
buzzer_p50 &BuzzerP50
card_erase_p50 &CardEraseP50
guest_card_raw_p50 &GuestCardRawP50
get_guest_lock_no_by_card_data_str_p50 &GetGuestLockNoByCardDataStrP50
)
// init 初始化DLL函数
fn init() {
// 加载DLL函数
funcs := load_dll_functions() or {
eprintln('加载DLL失败: ${err}')
return
}
(get_dll_version, close_usb, buzzer, read_card, read_card_v10, guest_card_raw, card_erase_v10, initialize_usb_p50, close_usb_p50, buzzer_p50, card_erase_p50, guest_card_raw_p50, get_guest_lock_no_by_card_data_str_p50) = funcs
// 打印DLL版本
mut ver_buf := [0u8; 256]
get_dll_version(&ver_buf[0])
version := cstr_to_str(&ver_buf[0])
println('DLL版本: ${version}')
}
// ------------------------------ 工具函数 ------------------------------
// cstr_to_str 将C字符串转换为V字符串
fn cstr_to_str(cstr &u8) string {
mut s := ''
mut i := 0
for {
ch := cstr[i]
if ch == 0 {
break
}
s += ch.ascii_str()
i++
if i >= 1024 { // 防止无限循环
break
}
}
return s
}
// str_to_cstr 将V字符串转换为C字符串(返回字节数组和长度)
fn str_to_cstr(s string) ([]u8, int) {
mut buf := s.bytes()
buf << 0 // 添加字符串结束符
return buf, buf.len
}
// show_message_box 模拟MessageBox弹窗(使用Win32 API)
fn show_message_box(title string, msg string, icon u32) {
title_buf, _ := str_to_cstr(title)
msg_buf, _ := str_to_cstr(msg)
win32.message_box(0, &msg_buf[0], &title_buf[0], win32.MB_OK | icon)
}
// info_box 信息弹窗
fn info_box(title string, msg string) {
show_message_box(title, msg, win32.MB_ICONINFORMATION)
}
// error_box 错误弹窗
fn error_box(title string, msg string) {
show_message_box(title, msg, win32.MB_ICONERROR)
}
// write_log 日志写入函数
fn write_log(capture_type string, log_type string, content string) ! {
// 获取可执行文件路径
exe_path := os.executable_path()!
exe_dir := os.dir(exe_path)
// 构建日志路径
now := time.now()
date_str := now.format('2006-01-02')
log_dir := os.join_path(exe_dir, 'log', capture_type, date_str)
// 创建目录
os.mkdir_all(log_dir)!
// 日志文件路径
log_path := os.join_path(log_dir, '${log_type}_log.log')
// 写入日志
log_time := now.format('2006-01-02 15:04:05')
mut file := os.open_append(log_path)!
defer file.close()
file.writeln('==============================')!
file.writeln('${log_time}<<<<<<<<<<<<<<<<<<<<<<<<<<')!
file.writeln(content)!
file.writeln('')!
}
// copy_bytes 模拟C#的Copy函数(从字节数组截取字符串)
fn copy_bytes(data []u8, start int, length int) string {
// C#是1-based索引,转换为0-based
start_idx := if start > 0 { start - 1 } else { 0 }
end_idx := start_idx + length
// 确保不越界
end_idx := end_idx.min(data.len)
if start_idx >= end_idx {
return ''
}
// 转换为ASCII字符串
return data[start_idx..end_idx].bytestr()
}
// ------------------------------ 读卡函数 ------------------------------
// rd_card 旧版本读卡
fn rd_card() bool {
mut buf_card := mu.lock() { global.buf_card }
st := read_card(1, &buf_card[0])
if st != 0 {
if st == 1 {
error_box('读卡失败(返回值=1)', '请放一张卡在发卡器上面,\n确保 门锁软件 可以正常发卡,然后调试接口')
} else {
error_box('提示', '读卡失败\n错误码: ${st}')
}
return false
}
// 验证卡数据
card_data_str := copy_bytes(buf_card[..], 5, 2)
if card_data_str != '01' {
error_box('提示', '发卡器的感应区无卡')
return false
}
return true
}
// rd_card_v10 V10版本读卡
fn rd_card_v10() bool {
mut buf_card_v10 := mu.lock() { global.buf_card_v10 }
st := read_card_v10(1, &buf_card_v10[0])
if st != 0 {
error_box('提示', '读卡失败\n错误码: ${st}')
return false
}
return true
}
// cyber_win_locak_app_get_sign 获取卡片标识
fn cyber_win_locak_app_get_sign(buf_card []u8) string {
// 检查是否为空白卡
if copy_bytes(buf_card, 25, 8) == 'FFFFFFFF' {
error_box('提示', '此卡是空白卡,请换一张能开门的卡')
return '此卡是空白卡,请换一张能开门的卡'
}
// 计算酒店标识
s := copy_bytes(buf_card, 11, 4)
i := strconv.parse_uint(s, 16, 64) or { 0 } % 16384
s2 := copy_bytes(buf_card, 9, 2)
i2 := strconv.parse_uint(s2, 16, 64) or { 0 }
i_total := i + i2 * 65536
// 最终计算
i_final := i2 * 65536 + (i_total % 16383)
return i_final.str()
}
// build_card_info_json 构建卡片信息JSON
fn build_card_info_json(status string, hotel_sign string, message string, lock_no string, physical_no string, check_in_time string, check_out_time string, llock string) string {
data := {
'status': status
'hotelsign': hotel_sign
'message': message
'lockno': lock_no
'physical_no': physical_no
'checkingintime': check_in_time
'checkingouttime': check_out_time
'llock': llock
}
return json.encode(data)
}
// ------------------------------ 业务函数 ------------------------------
// start 启动函数
pub fn start(obj NameValueCollection) string {
_ := obj.get('param1') or { '' }
return '随机预安装插件'
}
// status 状态函数
pub fn status(obj NameValueCollection) string {
// 调用蜂鸣器(fUSB=1, 时长50ms)
buzzer(1, 50)
return '当你听到设备蜂鸣器,说明设备已经连接'
}
// checking_out 退房(注销卡片)
pub fn checking_out(obj NameValueCollection) string {
mut result := '注销卡片'
param := obj.get('param') or { '' }
// 解析协议
mut cl_app := new_cl_cyber_win_app_protocol_package()
cl_app.format_string(param)
write_log('酒店智能门锁', 'P50', '注销,${param}') or {
eprintln('写入日志失败: ${err}')
}
hotel_sign_str := cl_app.get('hotelsign')
hotel_sign := strconv.parse_int(hotel_sign_str, 10, 32) or {
return '${result}:酒店标识格式错误'
}
// 初始化USB设备(1=proUSB)
st := initialize_usb_p50(1)
if st != 0 {
error_box('错误', '设备打开失败')
return '打开端口失败'
}
// 确保退出时关闭设备
defer close_usb_p50(1)
// 注销卡片
mut card_no_buf := [0u8; 100]
st = card_erase_p50(1, hotel_sign.int(), &card_no_buf[0])
if st != 0 {
msg := '注销失败\n错误码: ${st}'
info_box('提示', msg)
result = '${result}:注销失败${st}'
} else {
result = '${result}:成功'
}
return result
}
// checking_in 入住(发卡)
pub fn checking_in(obj NameValueCollection) string {
mut result := '酒店入住发卡'
param := obj.get('param') or { '' }
// 解析协议
mut cl_app := new_cl_cyber_win_app_protocol_package()
cl_app.format_string(param)
write_log('酒店智能门锁', 'P50', '入住,${param}') or {
eprintln('写入日志失败: ${err}')
}
lock_no := cl_app.get('lockno')
hotel_sign_str := cl_app.get('hotelsign')
check_out_time := cl_app.get('checkingouttime')
// 验证锁号长度
if lock_no.len < 6 {
error_box('提示', '锁号长度错误=${lock_no}')
return ''
}
hotel_sign := strconv.parse_int(hotel_sign_str, 10, 32) or {
return '${result}:酒店标识格式错误'
}
// 初始化USB设备
st := initialize_usb_p50(1)
if st != 0 {
error_box('错误', '设备打开失败')
return '打开端口失败'
}
// 确保退出时关闭设备
defer close_usb_p50(1)
// 生成时间字符串(yyMMddHHmmss)
now := time.now()
check_in_time := now.format('060102150405')
// 发卡参数
dai := 1
llock := 1
mut card_hex_str := [0u8; 500]
// 转换字符串为C字符串
check_in_time_buf, _ := str_to_cstr(check_in_time)
check_out_time_buf, _ := str_to_cstr(check_out_time)
lock_no_buf, _ := str_to_cstr(lock_no)
// 调用发卡函数
st = guest_card_raw_p50(1, hotel_sign.int(), 0, dai, llock, 0, &check_in_time_buf[0], &check_out_time_buf[0], &lock_no_buf[0], &card_hex_str[0])
if st != 0 {
msg := '调用发卡函数失败\n错误码: ${st}'
info_box('提示', msg)
result = '${result}调用发卡函数失败'
} else {
result = '${result}制卡成功V2024${lock_no}'
}
return result
}
// get_sign 获取卡片标识
pub fn get_sign(obj NameValueCollection) string {
if !rd_card_v10() {
return '读卡失败'
}
buf_card_v10 := mu.lock() { global.buf_card_v10 }
return cyber_win_locak_app_get_sign(buf_card_v10[..])
}
// read_card_info 读取房卡信息
pub fn read_card_info(obj NameValueCollection) string {
param := obj.get('param') or { '' }
// 解析协议
mut cl_app := new_cl_cyber_win_app_protocol_package()
cl_app.format_string(param)
write_log('酒店智能门锁', 'P50', '读卡,${param}') or {
eprintln('写入日志失败: ${err}')
}
hotel_sign_str := cl_app.get('hotelsign')
hotel_sign := strconv.parse_int(hotel_sign_str, 10, 32) or {
return build_card_info_json('3', hotel_sign_str, '酒店标识格式错误', '', '', '', '', '')
}
// 模拟卡数据(实际应从读卡获取)
card_data_hex := '551501C1011B4D9D1B0601036707CB2C07D30000000000000000000000000000000000325CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00'
mut lock_no_buf := [0u8; 50]
// 转换为C字符串
card_data_hex_buf, _ := str_to_cstr(card_data_hex)
// 调用DLL获取卡信息
st := get_guest_lock_no_by_card_data_str_p50(hotel_sign.int(), &card_data_hex_buf[0], &lock_no_buf[0])
mut status := '4'
mut message := '未知'
mut lock_no := ''
mut physical_no := ''
mut check_in_time := ''
mut check_out_time := ''
mut llock := ''
match st {
-4 => {
message = '空白卡或者已经注销的卡片,返回值:${st}'
status = '4'
info_box('提示', message)
}
-3 => {
message = '非本酒店卡,酒店标识不匹配,返回值:${st}'
status = '3'
info_box('提示', message)
}
-2 => {
message = '没有有效卡片,返回值:${st}'
status = '3'
info_box('提示', message)
}
0 => {
// 解析返回数据
lock_no = copy_bytes(lock_no_buf[..], 1, 6)
check_in_time = copy_bytes(lock_no_buf[..], 7, 12)
check_out_time = copy_bytes(lock_no_buf[..], 19, 12)
physical_no = copy_bytes(lock_no_buf[..], 33, 8)
llock = copy_bytes(lock_no_buf[..], 31, 1)
message = '读取成功'
status = '9'
}
1 => {
message = '连接发卡器失败,返回值:${st}'
status = '1'
info_box('提示', message)
}
else => {
message = '未知返回值:${st}'
info_box('提示', message)
}
}
return build_card_info_json(status, hotel_sign_str, message, lock_no, physical_no, check_in_time, check_out_time, llock)
}
关键说明和注意事项
1. 依赖配置
- 需安装 Vlang 的 Win32 绑定库:
v install vlang/win32 - 确保系统已安装 Vlang 编译器(推荐 0.4.6 及以上版本)
- DLL 文件(
proRFLV102024.dll、proRFLP50202501.dll)需放在可执行文件同级目录
2. DLL 调用机制
- Vlang 通过
win32库的load_library和get_proc_address动态加载 DLL 函数 - 函数类型定义需严格匹配 DLL 导出(调用约定默认
stdcall,与 C# 一致) - 字符串转换通过
str_to_cstr(V 字符串→C 字符串)和cstr_to_str(C 字符串→V 字符串)实现,自动添加终止符\0 - 字节数组缓冲区使用固定大小数组,避免内存越界
3. 类型转换适配
| C# 类型 | Vlang 类型 | 说明 |
|---|---|---|
byte |
u8 |
无符号 8 位整数 |
int |
int(i32) |
32 位整数 |
StringBuilder |
[u8; N] |
固定大小字节数组(C 字符串缓冲区) |
NameValueCollection |
map[string]string |
字符串键值对映射 |
DateTime |
time.Time |
时间处理,使用format格式化 |
byte[] |
[]u8 |
字节切片 |
4. 核心功能适配
- 弹窗功能:使用
win32.message_box实现 Windows 原生弹窗,支持信息 / 错误图标 - 日志写入:基于 Vlang 标准库
os实现文件操作,目录自动创建,路径与 C# 一致 - 线程安全:全局变量使用
sync.Mutex加锁,避免多线程竞争 - 资源释放:使用
defer语句确保 USB 设备连接自动关闭(RAII 模式) - 协议解析:支持
key=value&key2=value2格式,可扩展 JSON/XML 解析
5. 编译和运行
- 仅支持 Windows 平台(依赖 Windows DLL 和 Win32 API)
- 编译命令:
v build -prod cyberphp_dynamic.v(生成可执行文件) - 运行命令:
./cyberphp_dynamic.exe - 确保 DLL 文件与可执行文件在同一目录,或已添加到系统 PATH
6. 未实现部分
- 部分 DLL 函数(如
LimitCard、ReadRecord)未完全实现,可参考现有函数扩展 - 进制转换函数(
hex_a、a_hex)未验证,需根据实际 DLL 功能测试 - 错误处理可进一步完善(如 DLL 加载失败、文件 IO 错误的详细处理)
- 光标控制(
Cursor)未实现(Vlang 无原生支持,需 GUI 库扩展)
后续优化建议
- 错误处理:使用 Vlang 的
result类型统一错误返回,替代当前的or { }简写 - 配置支持:添加配置文件(如
config.toml),支持 DLL 路径、日志路径等自定义 - JSON 序列化:使用
json库的结构体序列化替代手动构建 JSON 字符串 - GUI 扩展:集成 Vlang GUI 库(如
vui、gtk绑定)实现更丰富的界面交互 - 异步支持:使用 Vlang 的
goroutine实现异步读卡、发卡操作,提高并发性能 - 单元测试:添加
test模块,覆盖核心函数的单元测试 - 日志优化:支持日志级别(DEBUG/INFO/ERROR)、滚动日志等功能
常见问题排查
- DLL 加载失败:检查 DLL 文件名、路径是否正确,是否为 32/64 位匹配(Vlang 默认编译 64 位)
- 函数调用失败:检查函数名、参数类型 / 顺序是否与 DLL 导出一致,可使用
dumpbin /exports xxx.dll查看导出函数 - 字符串乱码:确保字符串编码为 ASCII(与 DLL 一致),复杂字符需添加编码转换
- 内存错误:检查缓冲区大小是否足够,避免数组越界(尤其是字符串缓冲区)
阿雪技术观
在科技发展浪潮中,我们不妨积极投身技术共享。不满足于做受益者,更要主动担当贡献者。无论是分享代码、撰写技术博客,还是参与开源项目维护改进,每一个微小举动都可能蕴含推动技术进步的巨大能量。东方仙盟是汇聚力量的天地,我们携手在此探索硅基生命,为科技进步添砖加瓦。
Hey folks, in this wild tech - driven world, why not dive headfirst into the whole tech - sharing scene? Don't just be the one reaping all the benefits; step up and be a contributor too. Whether you're tossing out your code snippets, hammering out some tech blogs, or getting your hands dirty with maintaining and sprucing up open - source projects, every little thing you do might just end up being a massive force that pushes tech forward. And guess what? The Eastern FairyAlliance is this awesome place where we all come together. We're gonna team up and explore the whole silicon - based life thing, and in the process, we'll be fueling the growth of technology.
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)