金橙子(EzCAD)Python打标卡二次开发:跨越32位与64位的鸿沟——MarkEzd.dll TCP网关开发实战
金橙子(EzCAD)打标卡二次开发:跨越32位与64位的鸿沟——MarkEzd.dll TCP网关开发实战
前言
⚠️ 重要提示:本手册不提供金橙子的二次开发库(MarkEzd.dll)以及金橙子软件(EzCad2.exe)。
TCP网关源码,以及使用方式请从章节 五、重要提醒开始查看。
源码:https://download.csdn.net/download/weixin_42042144/92764398
使用手册:https://download.csdn.net/download/weixin_42042144/92764400
在金橙子打标卡的二次开发过程中,很多开发者都会遇到一个棘手的问题:MarkEzd.dll 是32位库,但主程序往往是64位。受限于Windows的DLL调用机制,64位程序无法直接加载32位DLL,这给跨语言、跨平台的二次开发带来了很大麻烦。
有没有一种方案,能够让任意语言、任意架构的程序都能调用这个32位DLL?答案是肯定的——TCP网关中转。本文将详细介绍我是如何用Python实现这个网关服务的。
一、问题背景
金橙子科技的MarkEzd.dll是用于EzCAD2和lmc1控制卡二次开发的动态链接库。根据官方文档,开发者需要使用C/C++通过Windows API(LoadLibrary、GetProcAddress)来加载和调用DLL。
然而,这个DLL是32位的。如果你使用64位Python、PyQt、C#或者其他64位语言开发主程序,就会遇到这样的错误:
OSError: [WinError 193] %1 不是有效的 Win32 应用程序
这个问题并非金橙子独有。32位DLL与64位程序的不兼容,是Windows平台开发中一个经典而又令人头疼的问题。
二、解决方案
架构设计
既然64位程序无法直接加载32位DLL,那么我们可以引入一个中间层——32位TCP服务端:
[64位主程序] ←TCP Socket JSON→ [ezd_service.exe] ←ctypes→ [MarkEzd.dll]
- ezd_service.exe:运行在32位Python环境下,作为TCP服务器接收主程序的请求
- 主程序:可以是Python、Java、C#、Go等任意语言,只需通过Socket发送JSON格式的请求
为什么选择TCP?
相比于COM组件、命名管道等其他IPC方案,TCP Socket有几个优势:
- 通用性强——任何语言都能轻松实现TCP客户端
- 跨平台——Linux服务器上也能运行类似方案
- 调试方便——可以直接用telnet或网络调试工具测试
三、核心实现
1. DLL封装(DllWrapper类)
服务核心是 DllWrapper 类,负责与32位DLL的交互:
class DllWrapper:
def __init__(self):
self.dll = None
self.ezcad_path = None
self.test_mode = False
def load(self, ezcad_path, test_mode=False):
self.dll = ctypes.CDLL(DLL_FILE)
result = self.dll.lmc1_Initial(ezcad_path_w, ctypes.c_bool(test_mode))
return result
def call_func(self, func_name, args):
func = getattr(self.dll, func_name)
result = func(*args)
return result
def free_dll(self):
if self.dll:
handle = self.dll._handle
ctypes.windll.kernel32.FreeLibrary(handle)
self.dll = None
关键方法说明:
| 方法 | 功能 |
|---|---|
load() | 加载DLL并初始化 |
unload() | 正常卸载DLL |
free_dll() | 强制释放DLL(解决错误码1问题) |
call_func() | 动态调用DLL函数 |
auto_reload() | 自动重连机制 |
2. 函数签名设置
DLL中的函数需要设置正确的参数类型和返回值类型:
def setup_all_functions(self):
self.dll.lmc1_Initial.restype = ctypes.c_int
self.dll.lmc1_Initial.argtypes = [ctypes.c_wchar_p, ctypes.c_bool, ctypes.c_void_p]
self.dll.lmc1_Mark.restype = ctypes.c_int
self.dll.lmc1_Mark.argtypes = [ctypes.c_bool]
self.dll.lmc1_ChangeTextByName.restype = ctypes.c_int
self.dll.lmc1_ChangeTextByName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p]
# ... 共60+个函数
3. TCP服务(EzCADService类)
服务类负责接收客户端请求并转发给DLL:
class EzCADService:
def process_request(self, data):
req = json.loads(data)
cmd = req.get('cmd', '')
params = req.get('params', {})
if cmd == 'load_dll':
result = self.dll_wrapper.load(ezcad_path, test_mode)
return {'code': result, 'msg': get_error_msg(result)}
elif cmd.startswith('lmc1_'):
code, result, error_msg = self.dll_wrapper.call_func(cmd, args)
return {'code': code, 'data': {'result': result}, 'msg': error_msg}
4. 错误码处理
DLL返回的错误码是数字,查阅文档才能知道含义。为了提升用户体验,我在服务端实现了错误码到中文信息的映射:
LMC1_ERROR_CODES = {
0: ("LMC1_ERR_SUCCESS", "成功"),
1: ("LMC1_ERR_EZCADRUN", "发现 EZCAD 在运行"),
2: ("LMC1_ERR_NOFINDCFGFILE", "找不到 EZCAD.CFG"),
3: ("LMC1_ERR_FAILEDOPEN", "打开 LMC1 失败"),
# ... 其他错误码
}
SERVICE_ERROR_CODES = {
1001: ("SERVICE_ERR_DLL_NOT_LOADED", "DLL未加载,请先调用 load_dll"),
1002: ("SERVICE_ERR_DLL_LOAD_FAILED", "DLL加载失败"),
1003: ("SERVICE_ERR_FUNC_CALL_FAILED", "函数调用异常"),
1004: ("SERVICE_ERR_UNKNOWN_FUNC", "调用的函数不存在"),
}
这样客户端收到的响应就变成了:
{
"code": 0,
"data": {"result": 1},
"msg": "发现 EZCAD 在运行"
}
5. 自动重连机制
服务实现了智能重连,当DLL返回特定错误码时会自动尝试恢复:
# 处理需要自动重连的错误码
if result in [1, 3, 9, 11]:
logger.warning(f"错误码{result},尝试自动重连...")
reload_result = self.auto_reload()
if reload_result == 0:
result = func(*args) # 重试
6. 错误码21自动清理
在开发过程中,我发现DLL返回错误码21(硬件参数错误)后,如果不彻底释放DLL,后续调用会持续失败。
服务实现了自动清理机制:
if result == 21: # LMC1_ERR_PARAM2
self.dll.lmc1_Close()
self.free_dll() # 强制释放
return {"code": 21, "msg": "硬件参数错误,已自动清理"}
四、源码结构
ezd_service.py
├── 错误码定义
│ ├── LMC1_ERROR_CODES # DLL错误码 (0-21)
│ └── SERVICE_ERROR_CODES # 服务错误码 (1001+)
│
├── DllWrapper类 # DLL封装
│ ├── load() # 加载DLL
│ ├── unload() # 卸载DLL
│ ├── free_dll() # 强制释放
│ ├── call_func() # 调用函数
│ ├── setup_all_functions() # 设置函数签名
│ └── auto_reload() # 自动重连
│
├── EzCADService类 # TCP服务
│ ├── load_config() # 加载配置
│ ├── process_request() # 处理请求
│ ├── convert_args() # 参数类型转换
│ └── run() # 运行服务
│
└── 主程序入口
└── 创建服务并运行
五、重要提醒
⚠️ 必须使用32位Python
MarkEzd.dll 是32位库,必须使用32位Python运行服务。
# 验证Python位数
python -c "import struct; print(struct.calcsize('P') * 8)"
# 输出32 = 32位Python ✓
# 输出64 = 64位Python ✗
⚠️ 打包注意事项
使用PyInstaller打包时:
- 使用32位Python执行打包
- spec文件需包含所有依赖目录(FONT、LANG、PARAM、plug、res)
- 必须将源码放在Ezcad2根目录,和ezcad2.exe与MarkEzd.dll在同级目录下打包
⚠️ 配置文件
服务支持 config.ini 配置文件:
[network]
host = 127.0.0.1
port = 5899
[dll]
test_mode = false
⚠️ 端口占用
默认端口为5899,确保端口未被占用。
六、使用示例
Python客户端调用
import socket
import json
class EzCADClient:
def __init__(self, host='127.0.0.1', port=5899):
self.host = host
self.port = port
def send(self, cmd, params=None):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((self.host, self.port))
req = {'cmd': cmd, 'params': params or {}, 'seq': 1}
sock.sendall(json.dumps(req).encode('utf-8'))
response = sock.recv(4096)
sock.close()
return json.loads(response.decode('utf-8'))
# 使用示例
client = EzCADClient()
# 1. 加载DLL
client.send('load_dll', {'ezcad_path': r'f:\project', 'test_mode': True})
# 2. 加载模板
client.send('lmc1_LoadEzdFile', {'args': [r'f:\project\template.ezd']})
# 3. 修改文本
client.send('lmc1_ChangeTextByName', {'args': ['name', 'Hello World']})
# 4. 执行标刻
client.send('lmc1_Mark', {'args': [False]})
其他语言
由于通信协议是标准的JSON over TCP,任何语言都能调用:
- C#:
TcpClient+JsonSerializer - Java:
Socket+Jackson - Go:
net/http+json.Unmarshal - Node.js:
net+JSON.parse
七、总结
通过TCP网关中转的方案,我们成功实现了64位程序调用32位DLL的需求。这个方案的优势在于:
- 语言无关——任何能发起TCP请求的语言都能使用
- 开箱即用——无需修改主程序代码,只需实现客户端
- 易于维护——服务端集中处理DLL调用逻辑
- 自动恢复——内置错误码处理和自动重连机制
目前,这个网关服务已经稳定运行,帮助解决了金橙子打标卡在PyQt等64位环境下的二次开发问题。
致谢
在开发过程中,感谢以下博主提供的参考资料:
- 北京金橙子ezcad2和lmc1控制卡二次开发的动态连接库手册 —— 详细的DLL函数文档
- Python 二次开发金橙子打印软件 —— Python调用DLL的实践案例
更多推荐



所有评论(0)