金橙子(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(LoadLibraryGetProcAddress)来加载和调用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有几个优势:

  1. 通用性强——任何语言都能轻松实现TCP客户端
  2. 跨平台——Linux服务器上也能运行类似方案
  3. 调试方便——可以直接用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的需求。这个方案的优势在于:

  1. 语言无关——任何能发起TCP请求的语言都能使用
  2. 开箱即用——无需修改主程序代码,只需实现客户端
  3. 易于维护——服务端集中处理DLL调用逻辑
  4. 自动恢复——内置错误码处理和自动重连机制

目前,这个网关服务已经稳定运行,帮助解决了金橙子打标卡在PyQt等64位环境下的二次开发问题。


致谢

在开发过程中,感谢以下博主提供的参考资料:

Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐