简介针对PowerBuilder编译DLL时提示“Error opening file c:\windows\system32\cgen\en32t.h”的报错这份资源提供了PB开发中必需的EN32T.h头文件适用于早期PB版本使用FullBuilder或ER工具进行本地DLL生成的场景。压缩包内共4个文件包括两个头文件与两个预编译.pch文件合计约596KB可满足编译环境缺失时的补全需求。已有598人学习下载适合遇到相同报错、需要快速修复编译环境的PB初学者参考使用。通过将对应文件放置到系统目录可有效规避DLL生成过程中的头文件缺失问题提升编译成功率。 说实话我本来以为把PowerBuilder的模块编译成DLL格式是一件“照着文档点几下”的事结果刚搭好编译环境就被一个叫 EN32T.H 的头文件卡了整整两天。网上中文资料少得可怜官方文档对此也是一笔带过最后靠自己翻安装目录、比对版本、反复试include路径才彻底搞定。这篇文章我就把这段经历完整写出来包括EN32T.H到底是什么、正常应该从哪里获取、编译DLL时怎么配置路径以及我踩过的各种报错和排查思路希望能给正在维护老PB项目、或者想把PB对象封装成DLL格式的朋友省点时间。1. PB编译DLL为什么绕不开EN32T.H1.1 EN32T.H到底是什么先说结论EN32T.H不是PowerBuilder运行时的文件而是PowerBuilder在生成DLL格式项目时C编译器链路里要用到的一个模板头文件。PowerBuilder的DLL机制和VC、C#直接编译原生DLL不太一样。PB本身是事件驱动、面向对象的高级开发工具它的对象比如NVO、Window、UserObject默认运行在PB虚拟机上。当你选择把PB代码编译成DLL格式时PB实际上是生成了一层C的“胶水代码”把这些PB对象的方法、属性、事件暴露成C可以识别的导出接口。这层胶水代码在编译的时候会include一组模板头文件EN32T.H就是其中最关键的一个。从命名上可以反推它的作用EN代表ANSI非Unicode32代表32位T代表Template。也就是说它是给32位ANSI编译环境用的模板头文件负责声明PB与C之间的数据类型映射、导出函数格式、调用约定等基础信息。我在实际使用中把PB生成的中间C代码翻出来看过里面确实有#include EN32T.H这样的引用所以这个文件一丢C编译器连预处理那关都过不去。需要注意的是不同PB版本对应的EN32T.H并不完全一样。PB 9、PB 10、PB 11、PB 12甚至后来的PB 12.5/12.6因为对象模型和编译器版本有差异模板头文件的内容也会有调整。所以最稳妥的做法是使用和你当前PB版本完全匹配的那一份EN32T.H而不是随便从某个老项目里拷一份就完事。1.2 没有它会发生什么如果你在编译DLL格式项目时编译器的Include目录没有指向EN32T.H所在的位置或者这个文件根本不存在你会看到的报错大致是这样的fatal error C1083: Cannot open include file: EN32T.H: No such file or directory这个错误一出整个编译流程直接就中断了DLL格式的输出文件不会生成。而且它带来的连锁反应很讨厌C编译器一旦找不到第一个头文件后面所有依赖这个头文件的代码全部跟着报错错误列表里一下子刷出几十条红色信息很容易让人误以为是代码本身有问题实际上根子就一个——EN32T.H没找到。我刚开始排错的时候就走偏了以为是工程设置里的宏定义写错来回改了半天的预处理选项结果完全没用。后来冷静下来看第一个错误才发现是include路径的问题。这里也提醒一句C编译报错一定要先看第一条后面绝大多数都是它引发的连锁错误不要被错误数量吓到。2. 环境准备EN32T.H从哪来、怎么验证2.1 找到头文件的正确位置EN32T.H一般在PowerBuilder安装目录里不同版本放的位置略有差异。以我用的PB 10为例典型路径是这样的C:\Program Files\Sybase\PowerBuilder 10.0\C Class Builder\IncludePB 9的路径类似只是版本目录换成9.0。PB 11及以后版本可能位于PowerBuilder\11.0\...或者Sybase\Shared\PowerBuilder\...之类的目录下具体得看你安装时的目录结构。如果你的机器上装了PB但不确定这个文件在哪最快的办法是用Everything这类文件搜索工具直接搜EN32T.H几秒钟就能定位。如果没有这类工具也可以用Windows自带的搜索但速度会比较慢尤其是在装了多个PB版本的情况下。还有一种情况你用的PB安装包是精简版或者当时安装的时候没有勾选C编译相关的组件那么安装目录里可能压根没有EN32T.H。这时候就得回到安装介质重新运行安装程序把C Class Builder组件补装上或者从同版本完整安装包里提取。我强烈不建议从网上那些DLL下载站、头文件下载站随便搞一个下来来源不明的东西很可能版本不对甚至带安全风险为省这几分钟完全不值得。2.2 验证头文件与PB版本是否匹配找到了文件不代表就能用版本匹配这步一定要做。EN32T.H本身是文本文件用记事本或者VS Code打开看文件头部的注释信息一般会写明适用于哪个PB版本、哪个编译环境。我自己的习惯是把当前版本的EN32T.H和从老项目里翻出来的那份做一次文本对比看差异大不大。如果就差几行注释通常可以通用如果核心的类型定义、宏定义差了很多那就必须换回本版本的文件。版本混用最常见的表现是编译时报一堆莫名其妙的“宏重定义”或者“未知类型”错误这类问题排查起来比文件缺失更费劲因为报错位置分散在PB生成的C代码里很容易让人误以为是PB工程配置错了。还有一点要特别留意EN32T.H是32位环境下用的。如果你的编译工具链是64位的或者你正在折腾把PB对象输出给64位程序调用那你要找的可能是对应的64位版本头文件文件名可能不同。早期PB的C Class Builder基本是32位工具链这块大家踩坑最多后面我会专门讲。2.3 配置编译搜索路径找到EN32T.H之后还得让编译器能“看”到它否则编译依然过不去。C编译器找头文件靠的是Include搜索路径。你需要在PB的DLL项目编译配置里把EN32T.H所在的目录加到Include路径中。我用的PB 10操作路径大概是打开DLL工程进入Project属性或者编译选项配置页找到类似Include Path/Additional Include Directories的设置项把目录填进去。如果是用命令行方式调用C编译器编译PB生成的中间代码那就用-I参数指定cl.exe -IC:\Program Files\Sybase\PowerBuilder 10.0\C Class Builder\Include ...有些老项目是直接用make文件或者批处理编的那就检查编译命令行里的-I参数是否覆盖了EN32T.H所在目录。这里有个很容易忽略的细节路径里如果有空格一定要用引号包起来否则编译器会把路径按空格拆开照样找不到文件。也有一种做法是把EN32T.H所在目录加到系统环境变量INCLUDE里这样所有C编译任务都能找到它。但我不太推荐这么做因为环境变量是全局的多个PB版本共存时容易串版本今天编A项目用的是PB 9的头文件明天编B项目可能就被环境变量里的PB 10版本干扰了反而制造新的问题。3. 实操把PB代码编译成DLL格式的完整流程3.1 创建DLL项目并配置编译器环境准备好之后真正的实操就开始了。在PowerBuilder里新建工程选择DLL格式的项目类型。PB 10里叫C DLL或者类似名字老一点的版本可能叫C Class Builder DLL位置在New对话框的Project标签页里。建好工程后第一步是选择你要封装成DLL的对象。大多数情况是选NVONon-Visual Object因为可视对象Window封装成DLL给外部程序用场景比较受限而NVO里放业务逻辑、计算函数导出去给其他语言调用是更常见的需求。接下来配置C编译器。PB编译DLL格式时底层是要调用真正的C编译器的PB本身不负责把C中间代码编译成机器码。我用的PB 10默认配合的是Watcom C或者你装了对应版本的Visual C之后在工程配置里指定编译器的路径。这一步很关键编译器路径配错后面会连着报Cannot find cl.exe或者Cannot open ...之类的问题。配置编译器时我建议顺手确认一下编译器的位数。PB 9/10的C Class Builder默认生成的是32位DLL如果你本机只装了64位的MSVC工具链编译大概率会出问题。要么找到PB配套的32位编译器要么装一个支持32位编译的VC工具集然后把编译目标设为32位。3.2 设置Include与Lib路径这一步就是前面说的EN32T.H配置的实际落地。在DLL工程的编译选项里找到Include路径设置把EN32T.H所在的目录加进去。同时还需要把编译器库文件目录加到Lib路径里否则最后链接阶段会报LNK1120: unresolved external symbol一类的错误。我习惯的做法是写一个配置模板方便每次建新工程时直接套用Include Path: C:\Program Files\Sybase\PowerBuilder 10.0\C Class Builder\Include C:\Program Files\Sybase\PowerBuilder 10.0\C Class Builder\SDK\Include Lib Path: C:\Program Files\Sybase\PowerBuilder 10.0\C Class Builder\Lib具体目录名以你机器上的实际安装结构为准但大致思路就是Include找你所有需要引用的头文件目录Lib找你所有需要链接的库文件目录。配置完整路径之后再重新编译EN32T.H找不到的报错就会消失。这一步还有一个经验如果你引用了PB运行库的导入库比如PBVM的.libLib路径里也要包含这些库文件所在位置否则链接时一样过不去。我见过不少人在Include配置上一切正常却败在Lib路径上最后链接阶段报一堆无法解析的外部符号排查半天才发现是.lib路径漏配了。3.3 编译并验证DLL输出配置全部就位后就可以执行Deploy或者Build操作PB会先生成中间C代码然后调用配置好的C编译器完成编译和链接最终输出DLL格式的文件。编译成功的标志是输出窗口里出现类似Build succeeded的信息并且目标目录下出现xxx.dll文件。但文件生成出来不代表就能直接用我每次都会做三轮验证第一轮用Dependencies开源的DLL依赖查看工具或者Visual Studio自带的dumpbin工具查看DLL的依赖项确认它依赖了哪些运行库。PB编出来的DLL通常会依赖PBVMxx.dll这类PowerBuilder虚拟机运行库这是正常现象说明这个DLL并不是完全独立的原生C DLL发布时要把对应的运行库一起带上。第二轮查看DLL导出了哪些函数。用dumpbin或者Dependencies确认你要公开给外部调用的函数确实导出成功了。如果导出函数列表是空的说明PB工程里对象的声明方式可能有问题需要检查是否把非可视对象的方法设置为Public以及项目里是否勾选了导出选项。第三轮写一个最小的调用方程序做实测。我用C#和C分别写过测试壳用LoadLibrary/PInvoke去加载这个DLL调用里面的导出函数确认返回值正确。这一步能同时验证DLL本身没问题、依赖库齐全、位数匹配三个点。实测通过才算真正完成。4. 常见问题排查与经验补遗4.1 高频报错速查表这些年在PB编译DLL格式上遇到的问题我整理成一个速查表基本都是真实撞过的墙报错信息原因解决办法fatal error C1083: Cannot open include file: EN32T.HInclude路径没配好或文件缺失把EN32T.H所在目录加入Include路径缺失则补装C Class Builder组件或从同版本安装介质提取大量宏重定义、类型重定义错误EN32T.H版本与PB版本不匹配换用与当前PB版本完全匹配的EN32T.H用文本对比确认差异LNK1120: unresolved external symbolLib库路径缺失链接不到导入库把PB运行库.lib所在目录加入Lib路径生成的DLL在目标机器上无法加载报找不到指定的程序DLL依赖的PBVMxx.dll等运行库缺失把PB运行库一起发布或让目标机器安装对应PB运行时64位进程调用DLL时失败编译出的DLL是32位位数不匹配统一调用方与DLL位数如需64位DLL需要64位编译链路和对应头文件加载时提示DLL初始化例程失败依赖链中有其他DLL缺失或顺序不对用Dependencies查看完整依赖链补齐所需运行库4.2 调用端踩坑DLL依赖与位数不匹配编译通过只是第一步真正让很多人在深夜崩溃的是调用阶段。PB编出来的DLL格式有一个特点很多人不知道它不是一个完全独立的原生DLL它依赖PowerBuilder虚拟机运行库。也就是说你把这个DLL发给别人对方机器上如果没有对应的PBVMxx.dll、PBRTxx.dll这些运行时文件LoadLibrary直接失败。我做过一个测试把编译好的DLL放到一台没有装PB运行时的干净机器上用C#去调用报OSError: [WinError 1114] 动态链接库(DLL)初始化例程失败。一开始以为是DLL本身损坏后来用Dependencies一看发现PBVM的依赖项在目标机器上根本不存在问题一目了然。所以发布PB编出来的DLL时记得把PowerBuilder运行时库打包进去。位数不匹配也是高频问题。早期PB版本编出来的DLL基本是32位如果你的调用方是64位程序直接LoadLibrary会失败错误信息五花八门有时候报“不是有效的Win32应用程序”有时候报“内存位置访问无效”。这个问题没有任何花哨的解法要么把调用方改成32位要么想别的办法做进程隔离通信就是不能拿64位进程去硬Load一个32位DLL。4.3 我的几点实操心得最后分享几个我踩过坑之后养成的习惯希望能帮你少走弯路。第一个习惯为每个PB版本建一个独立的编译环境目录。把EN32T.H、其他相关头文件、运行库.lib、PBVMxx.dll全部放到这个目录里按版本命名。项目维护时间长了机器上可能同时存在PB 9、PB 10、PB 12好几个环境混用出问题的概率极高环境目录互相独立能省很多事。第二个习惯编译报错时永远先看第一条。C编译器的错误信息有很强的连锁效应第一条错误往往是根因后面的全是“并发症”。我在EN32T.H缺失那次就是犯了先看中间错误的毛病白白浪费了大半天。第三个习惯先跑通最小项目再迁移业务。第一次做PB编译DLL格式的时候不要直接把庞大的业务NVO全部拖进去先建一个只有一个Add方法的测试NVO走通“PB工程配置-C编译-DLL生成-外部调用”整条链路。链路通了再逐步往里面加真实业务对象这样即使出问题也能快速定位是编译配置问题还是业务代码问题。这个技能属于老系统维护里比较冷门但关键时刻救命的那类。如果你也在折腾PB编DLL格式希望这篇能帮你把EN32T.H这个坎顺利迈过去。真被卡住的时候回来看看速查表就行。本文还有配套的精品资源点击获取
