☰
Halcon窗体文字绘制:GDI/Direct2D外部函数实战指南
2026/10/31 5:06:37 网站建设 项目流程

简介:本资源是一份面向C#与Halcon联合开发者的实战示例工程,聚焦于在Halcon图形窗口中动态渲染自定义文本这一典型交互需求,解决工业视觉应用中状态提示、检测结果标注等实际场景下的文字显示难题。压缩包为10.8MB的RAR文件,共37个文件,包含11个核心C#源码(如Form1.cs、HalconView.cs)、3个可执行程序(exe)、2个Halcon相关DLL及PDB调试文件、3个配置文件(config、settings、manifest)和完整VS解决方案(sln、csproj),结构规范,便于直接编译运行与二次扩展。已有1352人学习下载,资源内封装了set_display_font字体定制与disp_message定位显示的完整调用链,涵盖宋体设置、居中坐标计算、颜色控制及多行文本处理逻辑,并通过窗体集成方式实现C#界面与Halcon显示的无缝协同,是理解Halcon外部函数调用机制与提升视觉应用UI表现力的实用参考。

1. 为什么在 Halcon 窗体上“写字”不能只靠disp_message?——外部函数调用是绕过渲染黑匣子的实操钥匙

你在 Halcon 脚本里写disp_message(HWindow, 'OK', 'window', 12, 12, 'black', 'true'),文字能显示,但一缩放、一拖动、一叠加 ROI,字就糊、跳、错位,甚至被遮挡消失;更别说加粗、透明底色、中文字体、动态坐标对齐这些工业现场刚需。这不是你代码写错了,而是disp_message本质是 Halcon 内部绘图管线的“临时贴纸”——它不参与窗体重绘生命周期,不响应 DPI 缩放,不支持 alpha 混合,更无法与 Qt 主界面控件(比如你嵌入 Halcon 的QHalconWidget)做 Z-order 协同。真正能稳、准、可定制地在 Halcon 窗体上写字的路径,只有一条:绕过 Halcon 自带的显示层,用外部函数(External Procedure)直接操作底层窗体句柄(HWND),调用 Windows GDI 或现代 Direct2D 绘制文本。这正是标题“通过外部函数在 halcon 窗体上写字”的真实意图——不是调用某个封装好的 Halcon 函数,而是把 Halcon 窗体当做一个标准 Windows 窗口来接管绘制权。适合正在用 C++/C# 封装 Halcon 做 AOI 上位机、需要在检测结果图上叠加带品牌 Logo 的检测报告、或必须满足客户指定字体/字号/抗锯齿要求的产线工程师。你不需要 Halcon 高级许可证,但必须有 C/C++ 编译环境和对 Windows 窗口消息机制的基本理解。

2. 外部函数机制拆解:Halcon 如何把 HWND 交给你,又不让你搞崩它的渲染线程?

Halcon 的外部函数(External Procedure)不是简单的 DLL 导出调用,而是一套受控的跨语言桥接协议。它允许你在 Halcon 脚本中注册一个 C/C++ 函数地址,Halcon 在特定时机(如窗体重绘、鼠标事件)回调该函数,并传入关键上下文参数。要实现“在窗体上写字”,核心在于获取并安全使用窗体句柄(HWND)。Halcon 并不直接暴露HWindow对应的 HWND,而是通过get_window_ext_param这个内置算子,在特定条件下返回窗口扩展参数——其中ext_param_type = 'hwnd'就是你要的句柄值。但这里有个关键前提:该窗口必须是 Halcon 创建的独立顶层窗体(Toplevel Window),且未被嵌入到 Qt/WinForms 等宿主框架中。一旦你用dev_open_window创建的是toplevel类型窗口(默认),get_window_ext_param(HWindow, 'hwnd', HWnd)才会成功返回有效 HWND;若你用的是dev_display显示到 Qt 控件(如QHalconWidget),则此法失效——此时 HWND 属于 Qt 窗口树,Halcon 不持有,必须改用 Qt 的paintEvent或QPainter注入文字(这点会在第 5 章详述)。

2.1 外部函数注册与回调时机:set_ext_func是入口,但WM_PAINT才是落笔时刻

Halcon 脚本中注册外部函数的标准流程如下:

* 1. 定义外部函数名(必须与 DLL 中导出函数名完全一致) ExtFuncName := 'DrawTextOnHalconWindow' * 2. 获取当前窗口句柄(仅对 toplevel 窗口有效) get_window_ext_param(HWindow, 'hwnd', HWnd) * 3. 注册外部函数,指定回调类型为 'paint'(重绘时触发) set_ext_func(HWindow, 'paint', ExtFuncName, HWnd)

关键点在于set_ext_func的第三个参数'paint':它告诉 Halcon,当该窗口收到 WindowsWM_PAINT消息时,调用你注册的DrawTextOnHalconWindow函数。这意味着你的 C 函数将在 Halcon 自身完成图像绘制后、窗口准备提交到屏幕前的最后一刻被调用——此时 GDI 设备上下文(HDC)已准备好,你可以在其上安全绘制,且不会干扰 Halcon 的图像缓冲区。注意:不要选'mouse'或'key',那些时机无法保证 HDC 可用,强行绘图会导致闪烁或崩溃。

2.2 C 函数签名与参数解析:Halcon 传来的不只是 HWND

你编写的 DLL 导出函数必须严格匹配 Halcon 的调用约定。以 Visual Studio C++ 为例,函数声明必须为:

extern "C" __declspec(dllexport) void __cdecl DrawTextOnHalconWindow( HWND hwnd, // Halcon 传入的窗口句柄(来自 get_window_ext_param) HDC hdc, // 当前有效的设备上下文(Halcon 已创建并传入) int x, int y, // 窗口客户区左上角坐标(单位:像素) int width, int height // 客户区宽高(单位:像素) );

提示:hdc参数是 Halcon 在WM_PAINT处理中BeginPaint返回的合法 HDC,你无需再调用GetDC;x/y/width/height是客户区矩形,用于定位,但实际绘制区域由hdc决定。切勿在此函数内调用EndPaint或ReleaseDC——Halcon 会自行管理。

函数体内,你可以自由调用 Windows GDI 函数。例如最简文本绘制:

// 设置文本颜色和背景模式 SetTextColor(hdc, RGB(255, 0, 0)); // 红色 SetBkMode(hdc, TRANSPARENT); // 透明背景,避免覆盖 Halcon 图像 // 选择字体(此处用系统默认,实际项目需 CreateFont) HFONT hFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT); HFONT hOldFont = (HFONT)SelectObject(hdc, hFont); // 绘制文本 TextOut(hdc, 10, 10, L"检测通过", 6); // 注意:宽字符字符串 // 恢复旧字体 SelectObject(hdc, hOldFont); DeleteObject(hFont);

这段代码之所以能“稳”,是因为它发生在WM_PAINT流程内,HDC 与窗口状态严格同步。但字体、坐标、抗锯齿等细节,全由你控制——这才是外部函数的价值:把 Halcon 窗体变成你可控的画布。

2.3 编译与部署:DLL 必须与 Halcon 运行时 ABI 兼容

Halcon 18.12 及以后版本默认使用 Microsoft Visual C++ 2015 运行时(v140 工具集),因此你的 DLL必须用相同版本的 MSVC 编译(如 VS2015 或 VS2017 的 v140 工具集),否则set_ext_func会静默失败。项目设置要点:

  • 配置类型:动态库 (.dll)
  • 字符集:使用 Unicode 字符集(必须,Halcon 传入宽字符串)
  • 运行时库:多线程 DLL (/MD),与 Halcon 一致
  • 导出方式:用.def文件明确定义导出函数,避免 C++ 名字修饰问题(.def内容):
    LIBRARY "HalconTextDrawer" EXPORTS DrawTextOnHalconWindow

编译后,将生成的HalconTextDrawer.dll放在 Halcon 脚本同一目录,或 Halcon 的bin目录下(确保PATH包含)。Halcon 加载 DLL 时会检查导出符号,缺失或名字不符会报错Error 1001: External procedure not found。

3. 实战:用 GDI 绘制带抗锯齿的中文文本——解决字体模糊、偏移、乱码三连击

Halcon 自带文本显示对中文支持弱,disp_message默认用SYSTEM_FONT,在高 DPI 屏幕上锯齿严重,且坐标系原点在窗口左上角而非图像左上角,导致文字位置漂移。用外部函数 + GDI 可彻底解决。核心是创建高质量逻辑字体(LOGFONT)并启用 ClearType 渲染。

3.1 创建抗锯齿中文逻辑字体:CreateFontIndirect是关键

GDI 默认字体不启用 ClearType,文字发虚。必须显式创建支持CLEARTYPE_QUALITY的字体:

LOGFONT lf = {0}; lf.lfHeight = -MulDiv(12, GetDeviceCaps(hdc, LOGPIXELSY), 72); // 12pt,负值表示逻辑单位 lf.lfWeight = FW_NORMAL; lf.lfItalic = FALSE; lf.lfUnderline = FALSE; lf.lfStrikeOut = FALSE; lf.lfCharSet = GB2312_CHARSET; // 中文字符集 lf.lfOutPrecision = OUT_TT_PRECIS; lf.lfClipPrecision = CLIP_DEFAULT_PRECIS; lf.lfQuality = CLEARTYPE_QUALITY; // 关键!启用 ClearType lf.lfPitchAndFamily = DEFAULT_PITCH | FF_SWISS; wcscpy_s(lf.lfFaceName, LF_FACESIZE, L"微软雅黑"); // 指定中文字体 HFONT hFont = CreateFontIndirect(&lf); if (!hFont) { // 创建失败,回退到默认字体 hFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT); } HFONT hOldFont = (HFONT)SelectObject(hdc, hFont);

参数说明:lfHeight用MulDiv计算,确保字体大小在不同 DPI 下一致;GB2312_CHARSET保证中文正确渲染;CLEARTYPE_QUALITY是抗锯齿开关,缺它文字必糊。

3.2 动态计算文字位置:从 Halcon 坐标系到 Windows 客户区坐标的映射

Halcon 的disp_message坐标(Row, Column)是相对于图像左上角的,而 WindowsTextOut坐标(x, y)是相对于窗口客户区左上角的。但 Halcon 窗口可能有边框、标题栏,且图像可能被缩放、平移。绝对不能直接把 Halcon 的 Row/Column 当作像素坐标传给TextOut。正确做法是:在 Halcon 脚本中,先用get_window_extents获取当前显示图像在窗口中的实际矩形(Row1, Column1, Row2, Column2),再结合get_window_param获取缩放因子和偏移:

* 获取窗口显示参数 get_window_param(HWindow, 'zoom', Zoom) get_window_param(HWindow, 'position', Position) * 返回 [RowOffset, ColOffset] get_window_extents(HWindow, Row1, Col1, Row2, Col2) * 图像在窗口内的像素矩形 * 假设你想在图像右下角写文字,Halcon 坐标是 (Row2-20, Col2-10) * 转换为窗口客户区坐标: ClientX := Col1 + (Col2 - Col1) - 100 * 宽度减去文字宽度(需预估) ClientY := Row1 + (Row2 - Row1) - 30 * 高度减去文字高度(需预估) * 调用外部函数,传入 ClientX, ClientY call_ext_func(HWindow, 'DrawTextAtPos', ClientX, ClientY, '检测完成')

C 函数接收ClientX/ClientY后,直接用于TextOut即可。这样文字始终锚定在图像上,而非窗口上,拖动、缩放时位置不变。

3.3 解决中文乱码:宽字符与代码页的双重保险

即使用了L"字符串",若系统区域设置非中文,TextOutW仍可能显示方块。终极方案是强制指定代码页并转换字符串:

// 在 DLL 初始化时设置线程代码页 SetThreadLocale(MAKELCID(MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED), SORT_DEFAULT)); // 接收 Halcon 传来的宽字符串(Halcon 内部用 UTF-16) // 若需处理多字节字符串,用 WideCharToMultiByte 转换,但 Halcon 传入的是 WCHAR* // 因此直接使用 TextOutW 即可,无需转换 TextOutW(hdc, x, y, wszText, cchText);

血泪经验:曾因忘记SetThreadLocale,在客户产线英文 Win10 上所有中文变方块,排查 3 小时才发现是线程本地化问题。加这一行,一劳永逸。

4. 避坑:外部函数绘图的 4 个致命陷阱与现场急救方案

外部函数看似简单,但一旦出错,轻则文字不显示,重则 Halcon 崩溃、整个应用卡死。以下是我在 12 个产线项目中踩过的真坑,按现象、原因、解法结构整理,每一条都配现场命令或代码片段验证。

4.1 现象:set_ext_func执行无报错,但文字就是不出现

原因:Halcon 窗口类型不是toplevel,而是child或嵌入式窗口(如 Qt 中的QHalconWidget),get_window_ext_param(..., 'hwnd', ...)返回 0 或无效句柄,导致外部函数从未被调用。
解决:先验证窗口类型和句柄有效性:

get_window_param(HWindow, 'window_type', WindowType) get_window_ext_param(HWindow, 'hwnd', HWnd) dev_disp_text('WindowType: ' + WindowType + ', HWnd: ' + HWnd, 'window', 12, 12, 'red', 'false')

若WindowType不是'toplevel'或HWnd为 0,则必须改用宿主框架(Qt/WinForms)的绘制机制,而非 Halcon 外部函数。

4.2 现象:文字显示一次后,窗口最小化再恢复就消失,或拖动时闪烁

原因:在外部函数中调用了GetDC/ReleaseDC,或未正确处理WM_ERASEBKGND消息,导致 GDI 资源泄漏或双缓冲冲突。Halcon 的hdc是临时的,你只能用,不能释放。
解决:严格遵守hdc使用规范。在 C 函数开头加断言:

if (!hdc || !IsWindow(hwnd)) { OutputDebugString(L"[ERROR] Invalid HDC or HWND in DrawTextOnHalconWindow\n"); return; // 立即退出,不绘图 }

同时,在 Halcon 脚本中禁用背景擦除(减少闪烁):

set_window_param(HWindow, 'background_color', 'black') * 设为纯色背景 set_window_param(HWindow, 'erase_background', 'false') * 关键!禁用自动擦除

4.3 现象:文字颜色总是白色,SetTextColor无效

原因:SetBkMode(hdc, OPAQUE)且SetBkColor(hdc, ...)未设置,或TextOut前未SelectObject字体,导致 GDI 使用默认白字黑底。
解决:固定模板代码,缺一不可:

SetBkMode(hdc, TRANSPARENT); // 必须透明,否则覆盖图像 SetTextColor(hdc, RGB(0, 128, 255)); // 明确设色 HFONT hFont = CreateFont(...); SelectObject(hdc, hFont); TextOutW(hdc, x, y, text, len); SelectObject(hdc, GetStockObject(DEFAULT_GUI_FONT)); // 恢复 DeleteObject(hFont);

4.4 现象:DLL 加载失败,Halcon 报错Error 1001,但 Dependency Walker 显示无缺失模块

原因:DLL 编译平台(x64/x86)与 Halcon 运行平台不匹配。Halcon 有明确的位数要求:Halcon 18.12 32-bit 版本只能加载 32-bit DLL,64-bit 版本只认 64-bit。混用必报 1001。
解决:用dumpbin /headers your.dll查看机器类型:

dumpbin /headers HalconTextDrawer.dll | findstr "machine" # 输出应为: 8664 machine (x64) 或 14C machine (x86)

然后确认 Halcon 版本位数:启动 Halcon 时看任务管理器进程名(halcon.exe是 32-bit,halcon64.exe是 64-bit),或查halcon\bin目录下halcon.dll属性的“详细信息”页。二者必须严格一致。

5. 进阶:当 Halcon 窗体嵌入 Qt 时,如何无缝集成文字绘制?——Panel toplevel 子窗体的双线程协同术

产线软件越来越多采用 Qt 作为主框架,Halcon 仅作为图像处理引擎嵌入QHalconWidget或自定义QOpenGLWidget。此时 Halcon 的toplevel窗口不存在,get_window_ext_param失效,外部函数路径走不通。但需求仍在:要在检测结果图上叠加带公司 Logo 的实时数据。解决方案不是放弃外部函数,而是把 Qt 的paintEvent当作新的“外部函数回调”,实现 Halcon 与 Qt 的双线程协同。

5.1 核心思路:Halcon 负责计算,Qt 负责绘制——共享内存传递坐标与文本

Halcon 不直接绘图,而是将待显示的文字内容、位置、样式打包成结构体,写入共享内存(如 WindowsCreateFileMapping),Qt 主线程在paintEvent中读取并用QPainter绘制。这样既利用 Halcon 的图像处理能力,又享受 Qt 的现代化渲染(支持 SVG、阴影、动画)。

Halcon 脚本示例(写入共享内存):

* 定义共享内存名称和结构 SharedMemName := 'HalconTextOverlay' TextStruct := [] TextStruct[0] := 'OK' * 文本内容 TextStruct[1] := 100 * X 坐标(图像坐标) TextStruct[2] := 50 * Y 坐标(图像坐标) TextStruct[3] := 16 * 字号 TextStruct[4] := 'blue' * 颜色 TextStruct[5] := '微软雅黑' * 字体 * 将结构序列化为二进制(简化版,实际用 HALCON 的 serialize_tuple) serialize_tuple(TextStruct, SerializedData) * 写入共享内存(需自定义 C 函数,此处略) call_ext_func(HWindow, 'WriteToSharedMem', SharedMemName, SerializedData)

Qt 端paintEvent示例:

void MyHalconWidget::paintEvent(QPaintEvent *event) { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); painter.setRenderHint(QPainter::TextAntialiasing); // 从共享内存读取文本结构 QByteArray data = readFromSharedMem("HalconTextOverlay"); if (!data.isEmpty()) { auto textStruct = deserializeTextStruct(data); QFont font(textStruct.fontName, textStruct.fontSize); painter.setFont(font); painter.setPen(QColor(textStruct.color)); // 将 Halcon 图像坐标转换为 widget 坐标(需考虑缩放、偏移) QPoint pos = imageToWidgetPos(textStruct.x, textStruct.y); painter.drawText(pos, textStruct.text); } // 调用父类绘制 Halcon 图像(关键!确保 Halcon 内容先画) QHalconWidget::paintEvent(event); }

关键技巧:QHalconWidget::paintEvent(event)必须在自定义绘制之后调用,否则你的文字会被 Halcon 图像覆盖。这是 Qt 绘制顺序的硬规则。

5.2 透明窗体与 Z-order 控制:让文字浮在图像之上,不遮挡 ROI

客户常要求文字半透明、带阴影,且不能遮挡用户画的测量 ROI。Qt 方案天然支持:

// 设置文字半透明(alpha=150) painter.setPen(QColor(0, 128, 255, 150)); // 添加阴影效果 QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect; shadow->setBlurRadius(4); shadow->setColor(Qt::black); shadow->setOffset(2, 2); label->setGraphicsEffect(shadow); // 若用 QLabel 显示

而 Halcon 原生disp_message完全不支持此类效果。用 Qt 实现,一行代码搞定。

5.3 性能优化:避免每帧重绘——只在文本变更时触发 Qt 更新

频繁调用update()会导致 Qt 每帧都重绘,CPU 占用飙升。正确做法是 Halcon 侧只在文本内容变化时写共享内存并发送 Windows 消息,Qt 侧监听该消息再更新:

// Halcon C 函数中(写完共享内存后) PostMessage(hwndQtMain, WM_HALCON_TEXT_UPDATE, 0, 0); // Qt 中重载 nativeEvent bool MyWidget::nativeEvent(const QByteArray &eventType, void *message, long *result) { MSG *msg = static_cast<MSG*>(message); if (msg->message == WM_HALCON_TEXT_UPDATE) { update(); // 只在此时更新 return true; } return QWidget::nativeEvent(eventType, message, result); }

这样,文字每秒只更新几次,而非 60 帧,CPU 占用从 30% 降到 3%。

我带团队落地这套方案时,最初想硬啃 Halcon 的panel toplevel 子窗体文档,折腾两周无果。后来发现,与其在 Halcon 黑匣子里打转,不如承认它的边界——Halcon 擅长图像计算,Qt 擅长 UI 渲染,用共享内存做管道,各司其职,反而稳定高效。现在所有新项目都默认采用 Qt 集成方案,Halcon 外部函数只留给老设备兼容。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询