Files
desktop-plugin/lib/print/pavo/pavo_ffi.dart
T
dzc 1deb3125be fix: Pavo 布局自检期望值错误(80→88)——上一轮审查时手数错了指针数
用户 Windows 实测触发 HitiCardPrintParameter 布局 assert。mac 实测 sizeOf
坐实:HitiCardPrintParameter=88(4×uint32 + 8×uint8 + 8×pointer = 24+64,
8 个指针=front BGR/K/O + back BGR/K/O + Reserve×2),其余三个(32/16/48)
正确——结构体布局本身与 C# 实机版一致,错的只是 assert 里手写的期望值 80。

顺带:此错误当时被 pavo_isolate_test 的 catch 转 offline 文案掩盖(断言只查
unsendable),新增 test/pavo_ffi_layout_test 直接 expect 四个 sizeOf,
布局回归在 mac 上即红,不必等 Windows 实机。15 测试全过。
2026-09-21 16:19:32 +08:00

221 lines
8.4 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:ffi';
import 'package:ffi/ffi.dart';
/// HiTi CS-2xx PavoApi 的 dart:ffi 绑定。
///
/// 依据 SDK Include/PavoApi.h(PrinterCardSDK_1_11_25)与 C# 版 Hiti/PavoApi.cs 翻译;
/// 使用宽字符 DLL `PavoApiW_x64.dll`。C# CharSet.Unicode 的导出名回退行为
/// (先找 PAVO_xxxW 再回退 PAVO_xxx)在此用 try-catch 复刻。
/// 返回值约定:0 = 成功,非 0 = 错误码(ds* 状态码常量)。
///
/// 所有导出函数只允许在 Windows 上实际调用(DLL 加载失败即抛错);
/// 结构体定义本身跨平台可编译,便于 mac 上 analyze 与单测。
class PavoApi {
PavoApi() : _lib = DynamicLibrary.open(_dllName) {
_enumUsb = _tryEnum('PAVO_EnumUSBCardPrintersW') ?? _tryEnum('PAVO_EnumUSBCardPrinters')!;
_checkStatus =
_tryCheckStatus('PAVO_CheckPrinterStatusW') ?? _tryCheckStatus('PAVO_CheckPrinterStatus')!;
_getDeviceInfo =
_tryGetDeviceInfo('PAVO_GetDeviceInfoW') ?? _tryGetDeviceInfo('PAVO_GetDeviceInfo')!;
_printOneCard = _tryPrintOneCard('PAVO_PrintOneCardW') ?? _tryPrintOneCard('PAVO_PrintOneCard')!;
// 布局自检:Dart Struct 按 C ABI 自然对齐,与 C# 版(已实机验证)的
// sizeof 逐一核对;首个 Windows 环境首跑即暴露对齐差异
assert(sizeOf<HitiCardPrintParameter>() == 88, 'HitiCardPrintParameter 布局异常');
assert(sizeOf<GdiBitmap>() == 32, 'GdiBitmap 布局异常');
assert(sizeOf<HitiHeatingEnergy>() == 16, 'HitiHeatingEnergy 布局异常');
assert(sizeOf<HitiSettings>() == 48, 'HitiSettings 布局异常');
}
static const String _dllName = 'PavoApiW_x64.dll';
// ---------------- 状态码(PAVO_DS_*,CheckPrinterStatus 输出 / 任务错误码) ----------------
static const int dsBusy = 0x00080000;
static const int dsOffline = 0x00000080;
static const int dsPrinting = 0x00000002;
static const int dsProcessingData = 0x00000005;
static const int dsSendingData = 0x00000006;
static const int dsCardJam1 = 0x00030000;
static const int dsCardJam2 = 0x00040000;
static const int dsCardJam3 = 0x00050000;
static const int dsCmdSeqError = 0x000301FE;
static const int dsPrintFail1 = 0x00030002;
static const int dsPrintFail2 = 0x00030003;
static const int dsCoverOpen = 0x00050001;
static const int dsCardOut = 0x00008000;
static const int dsCardLow = 0x00008001;
static const int dsCardThickWrong = 0x00008010;
static const int dsRibbonMissing = 0x00080004;
static const int dsOutOfRibbon = 0x00080103;
static const int dsUnsupportRibbon = 0x000806FE;
static const int dsUnknownRibbon = 0x000808FE;
static const int dsNoFlipperModule = 0x1100000D;
static const int dsWriteFail = 0x0000001F;
static const int dsReadFail = 0x0000002F;
static const int locked = 0x00007540;
// ---------------- GetDeviceInfo 信息类型 ----------------
static const int devinfoMfgSerial = 1;
static const int devinfoModelName = 2;
static const int devinfoFirmwareVersion = 3;
static const int devinfoRibbonInfo = 4;
static const int devinfoPrintCount = 5;
// ---------------- 色带类型 ----------------
static const int ribbonTypeYmcko = 0;
static const int ribbonTypeK = 1;
static const int ribbonTypeKo = 3;
static const int ribbonTypeYmckok = 4;
static const int ribbonTypeHalfYmcko = 5;
static const int ribbonTypeYmckfo = 12;
static const int ribbonMissing = 255;
final DynamicLibrary _lib;
// 枚举 USB 打印机:两段式(首传 nullptr 取所需大小,再传实际缓冲;缓冲内容编码见 PavoPrinter 解码)
late final int Function(Pointer<Uint8>, Pointer<Uint32>) _enumUsb;
late final int Function(Pointer<Utf16>, Pointer<Uint32>) _checkStatus;
late final int Function(Pointer<Utf16>, int, Pointer<Uint8>, Pointer<Uint32>) _getDeviceInfo;
late final int Function(
Pointer<Utf16>, Pointer<HitiCardPrintParameter>, Pointer<HitiHeatingEnergy>, Pointer<HitiSettings>) _printOneCard;
// 宽字符导出名兼容:先找 xxxW,失败回退原名(对齐 C# CharSet.Unicode 行为)。
int Function(Pointer<Uint8>, Pointer<Uint32>)? _tryEnum(String name) {
try {
return _lib.lookupFunction<Uint32 Function(Pointer<Uint8>, Pointer<Uint32>),
int Function(Pointer<Uint8>, Pointer<Uint32>)>(name);
} on ArgumentError {
return null;
}
}
int Function(Pointer<Utf16>, Pointer<Uint32>)? _tryCheckStatus(String name) {
try {
return _lib.lookupFunction<Uint32 Function(Pointer<Utf16>, Pointer<Uint32>),
int Function(Pointer<Utf16>, Pointer<Uint32>)>(name);
} on ArgumentError {
return null;
}
}
int Function(Pointer<Utf16>, int, Pointer<Uint8>, Pointer<Uint32>)? _tryGetDeviceInfo(String name) {
try {
return _lib.lookupFunction<Uint32 Function(Pointer<Utf16>, Uint32, Pointer<Uint8>, Pointer<Uint32>),
int Function(Pointer<Utf16>, int, Pointer<Uint8>, Pointer<Uint32>)>(name);
} on ArgumentError {
return null;
}
}
int Function(Pointer<Utf16>, Pointer<HitiCardPrintParameter>, Pointer<HitiHeatingEnergy>,
Pointer<HitiSettings>)? _tryPrintOneCard(String name) {
try {
return _lib.lookupFunction<
Uint32 Function(Pointer<Utf16>, Pointer<HitiCardPrintParameter>,
Pointer<HitiHeatingEnergy>, Pointer<HitiSettings>),
int Function(Pointer<Utf16>, Pointer<HitiCardPrintParameter>, Pointer<HitiHeatingEnergy>,
Pointer<HitiSettings>)>(name);
} on ArgumentError {
return null;
}
}
/// 两段式枚举 USB 证卡打印机:buf 传 nullptr 时返回所需缓冲大小。
int enumUsbCardPrinters(Pointer<Uint8> buf, Pointer<Uint32> size) => _enumUsb(buf, size);
/// 查询状态码(0=就绪);返回值非 0 时是 API 层错误码。
int checkPrinterStatus(Pointer<Utf16> printer, Pointer<Uint32> status) => _checkStatus(printer, status);
/// 读设备信息(缓冲写 UTF-16LE;RIBBON_INFO 为 dword[0]=类型 dword[1]=剩余张数)。
int getDeviceInfo(
Pointer<Utf16> printer, int infoType, Pointer<Uint8> data, Pointer<Uint32> dataLen) =>
_getDeviceInfo(printer, infoType, data, dataLen);
/// 单卡直打(分层 BITMAP + 默认加热能量 + 默认设置)。
int printOneCard(
Pointer<Utf16> printer,
Pointer<HitiCardPrintParameter> jobPara,
Pointer<HitiHeatingEnergy> heatEnergy,
Pointer<HitiSettings> settings) =>
_printOneCard(printer, jobPara, heatEnergy, settings);
}
/// wingdi BITMAP:指向裸像素缓冲(x64 下 32 字节,Dart Struct 按 C ABI 自然对齐,布局一致)。
final class GdiBitmap extends Struct {
@Int32()
external int bmType;
@Int32()
external int bmWidth;
@Int32()
external int bmHeight;
@Uint32()
external int bmWidthBytes;
@Uint16()
external int bmPlanes;
@Uint16()
external int bmBitsPixel;
external Pointer<Uint8> bmBits;
}
/// PAVO_PrintOneCard 参数。像素约定(头文件注释):
/// BGR 层 24bit BGRBGR…;K 层 8bit,0x00 的像素由树脂 K 转印到卡面;O 层 8bit,0xFF 的像素转印覆膜。
final class HitiCardPrintParameter extends Struct {
@Uint32()
external int dwSize;
@Uint32()
external int dwReserve1;
@Uint32()
external int dwReserve2;
@Uint32()
external int dwFlags;
/// 1=portrait 2=landscape
@Uint8()
external int byOrientation;
/// 0=0.3mm 1=0.5mm 2=0.8mm 3=1.0mm
@Uint8()
external int byCardThickness;
/// 0=白卡 1=透明卡
@Uint8()
external int byTransparentCard;
@Uint8()
external int byReserve4;
@Uint8()
external int byReserve5;
@Uint8()
external int byReserve6;
@Uint8()
external int byReserve7;
@Uint8()
external int byReserve8;
external Pointer<Uint8> lpFrontBGR;
external Pointer<Uint8> lpFrontK;
external Pointer<Uint8> lpFrontO;
external Pointer<Uint8> lpBackBGR;
external Pointer<Uint8> lpBackK;
external Pointer<Uint8> lpBackO;
external Pointer<Uint8> lpReserve1;
external Pointer<Uint8> lpReserve2;
}
/// 加热能量,全 0 表示用驱动默认值(x64 下 16 字节)。
final class HitiHeatingEnergy extends Struct {
@Array(16)
external Array<Uint8> data;
}
/// 打印附加设置,全 0 即可(x64 下 48 字节)。
final class HitiSettings extends Struct {
@Uint8()
external int bSpecialO;
@Array(15)
external Array<Uint8> bReserved;
@Array(8)
external Array<Uint16> wReserved;
@Array(4)
external Array<Uint32> dwReserved;
}