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.dllproRFLP50202501.dll)需放在可执行文件同级目录
2. DLL 调用机制
  • Vlang 通过win32库的load_libraryget_proc_address动态加载 DLL 函数
  • 函数类型定义需严格匹配 DLL 导出(调用约定默认stdcall,与 C# 一致)
  • 字符串转换通过str_to_cstr(V 字符串→C 字符串)和cstr_to_str(C 字符串→V 字符串)实现,自动添加终止符\0
  • 字节数组缓冲区使用固定大小数组,避免内存越界
3. 类型转换适配
C# 类型 Vlang 类型 说明
byte u8 无符号 8 位整数
int inti32 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 函数(如LimitCardReadRecord)未完全实现,可参考现有函数扩展
  • 进制转换函数(hex_aa_hex)未验证,需根据实际 DLL 功能测试
  • 错误处理可进一步完善(如 DLL 加载失败、文件 IO 错误的详细处理)
  • 光标控制(Cursor)未实现(Vlang 无原生支持,需 GUI 库扩展)

后续优化建议

  1. 错误处理:使用 Vlang 的result类型统一错误返回,替代当前的or { }简写
  2. 配置支持:添加配置文件(如config.toml),支持 DLL 路径、日志路径等自定义
  3. JSON 序列化:使用json库的结构体序列化替代手动构建 JSON 字符串
  4. GUI 扩展:集成 Vlang GUI 库(如vuigtk绑定)实现更丰富的界面交互
  5. 异步支持:使用 Vlang 的goroutine实现异步读卡、发卡操作,提高并发性能
  6. 单元测试:添加test模块,覆盖核心函数的单元测试
  7. 日志优化:支持日志级别(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.

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐