用户 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 测试全过。
221 lines
8.4 KiB
Dart
221 lines
8.4 KiB
Dart
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;
|
||
}
|