简介:该PDF文档提供了一段完整的ABAP程序源代码(REPORT ZZXUE01),用于从SAP R/3系统中批量下载指定程序的源代码,适合具备一定ABAP基础、希望学习代码下载实现逻辑或进行二次开发的SAP技术人员。文档以代码加注释的形式,清晰展示了从声明数据库表(RS38M、TRDIR)、定义内表结构、变量与内表,到ALV列表输出及宏定义(MCR_FIELD、MCR_RANGE)的完整实现过程,读者可据此掌握利用ABAP Workbench检索程序源并导出的编程思路。压缩包内仅含1个PDF文件,大小约21KB,方便随时查阅。该资源已有632人学习,对于想了解ABAP程序下载机制、研究典型报表程序结构或在此基础上扩展功能的开发者,具有较强的参考价值。 在SAP项目里待久了你会发现,把ABAP程序源代码批量导出来这个需求,出现的频率远比想象中高。做代码走查要导,上线前做基线备份要导,季度审计面前几百个程序清单还是要导。我最初也和大家一样,SE38打开一个复制一个,直到有次整理三百多个程序清单时彻底改变想法,才动手写了这个"下载ABAP程序源代码的程序"。这篇文章就把这个工具的完整思路、技术选型和落地代码全部捋一遍,包括我在实际使用中踩过的坑和优化办法,想省事的ABAP开发、BASIS和审计相关同事都能直接用上。
1. 需求与场景:为什么下载ABAP源代码要单独做个工具
1.1 手工导出源码的三大痛点
先说最基本的痛点。假设系统里有300个自开发程序,审计要求全部提供源代码。手工操作流程是:SE38进入ABAP编辑器,打开程序,菜单栏进入源代码,全选复制,再切到记事本粘贴保存。单个程序熟练操作也要两分钟,300个就是600分钟,整整十个小时的机械劳动。而且这种操作特别容易漏,中间接个电话、回个工作消息,序号就乱了,漏掉几个程序根本发现不了。
第二个痛点是格式失真。从SE38复制到本地文本,编辑器会自动调整缩进和引号,如果中间经过Windows剪贴板或Word的“自动更正”,中文字符引号会变成全角,IDoc、RFC这类对代码长度敏感的程序,导入回系统时可能直接语法报错。
第三个痛点是版本一致性。手工复制无法保证拿到的代码和系统当前激活版本一致。你可能打开的是某个旧变式,或者程序在其它请求中已经被修改但尚未激活,肉眼很难分辨。这些问题在“download程序源代码”这个场景下被放大,所以纯手工方案只适用于个别程序,批量场景必须交给程序来处理。
1.2 一个理想工具的评判标准
既然要写工具,我给自己定了几条硬性要求:
- 能按程序名、开发类、包名做范围过滤,不用每次改代码。
- 能自动覆盖主程序、包含程序、子程序,不打散文件。
- 支持按语言导出,避免把英文注释和中文注释混在一起。
- 输出到本地可读的文本文件,同时保留原代码行结构。
- 执行日志可追溯,至少能看出来哪些程序成功、哪些失败。
- 对已有代码零侵入,不修改任何被导出程序。
后面整个代码实现,都是围绕这几条标准展开的。先说结论:最后我用的是标准的RPY_PROGRAM_READ函数读取源码,再用GUI_DOWNLOAD写到本地,配合TADIR对象目录来筛选目标程序,效果非常稳定。
2. 技术选型:读取ABAP代码的几种主流方式
2.1 四种读取源码方案的横向对比
实现“读取ABAP程序源代码”这个动作,业内常用的方案主要有四种。我整理成了表格,方便直观比较。
| 方案 | 实现方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| SE38/SE80手工操作 | 编辑器复制粘贴 | 无需开发 | 效率极低、易遗漏 | 单程序临时导出 |
| READ_REPORT函数 | 直接读取报告 | 简单、无额外依赖 | 对超长源码有限制 | 程序体较短时 |
| RPY_PROGRAM_READ函数 | 读取源码到内表 | 稳定、支持版本信息 | 调用要传完整程序名 | 批量导出首选 |
| ABAP Git/外部插件 | Eclipse插件拉取 | 可视化、可版本管理 | 需要额外安装和权限 | 团队持续集成场景 |
其中SE38手工方式就不再展开了,效率问题决定它成不了批量方案。ABAP Git这种方式适合团队长期维护代码,但对临时审计、交付这种“一次性动作”来说,搭环境的成本又太高。所以我从系统自带的函数入手。
2.2 为什么把RPY_PROGRAM_READ作为主力函数
先说READ_REPORT,它确实能读源码,但在超大程序、尤其是包含大量INCLUDE的程序上不够稳,它一次性读入所有代码行的能力有限,源码超长时报“too many source codes”我是亲历过的。而RPY_PROGRAM_READ是SAP开发工作台底层使用的函数,读取主程序和包含程序都很可靠,它返回的SOURCE表是字符串行表,每一行对应源文件的一行,结构上与SE38看到的源码是一一对应的。
这里有个容易忽略的细节:RPY_PROGRAM_READ的PROGRAM_NAME参数要求输入的是程序名,而不是TADIR里完整对象名。对于普通REPORT程序两者一致,但遇到INCLUDE程序,对象名长度可能是30位,程序名则是包含程序名称;如果你在TADIR里查询时没注意对象类型,直接拿OBJ_NAME去调用,可能读不到源码。我在代码里专门做了对象名裁剪和异常处理,后面会详细说。
导出的方式,老项目中常见GUI_DOWNLOAD函数,新项目中更推荐类CL_GUI_FRONTEND_SERVICES里的GUI_DOWNLOAD方法。两者参数基本一致,方法版本更面向对象,出错信息也更友好。兼容性考虑,我在示例代码里保留了GUI_DOWNLOAD,同时会给出新写法的替换方式。
3. 核心实现:写一个完整的批量下载ABAP源码工具
3.1 选择屏幕与参数设计
这个报表的入口参数,我按真实使用习惯设计成五个:
- S_PROG:程序名选择范围,支持通配符,默认值可以用Z*。
- S_DEVC:开发类(包)范围,支持通配符,例如ZDEV。
- P_LANG:导出的源代码语言,默认当前登录语言。
- P_PATH:导出到本地的目录路径,默认C盘下的abap_src文件夹。
- P_ALV:是否先在ALV中预览程序清单再做勾选确认。
语言参数这个很多人容易忽略,但跨语言系统导出时很关键。如果你在英文环境登录,而代码是中文注释,P_LANG指定为中文后,导出内容才不会出现注释乱码。
3.2 主程序核心代码
下面直接给出可运行的ABAP报表主代码。代码逻辑并不复杂,关键环节我都加了注释。
REPORT z_download_abap_src. TYPES: BEGIN OF ty_program, obj_name TYPE tadir-obj_name, devclass TYPE tadir-devclass, author TYPE tadir-author, srcdate TYPE tadir-srcdate, END OF ty_program. DATA: lt_programs TYPE TABLE OF ty_program, lt_source TYPE TABLE OF string, lt_result TYPE TABLE OF ty_program, lv_index TYPE i, "计数器 lv_filename TYPE string, "完整文件路径 lv_rc TYPE i. SELECTION-SCREEN BEGIN OF BLOCK b1 WITH FRAME TITLE TEXT-t01. SELECT-OPTIONS: s_prog FOR tadir-obj_name, s_devc FOR tadir-devclass DEFAULT 'Z*' OPTION CP. PARAMETERS: p_lang TYPE sy-langu DEFAULT sy-langu, p_path TYPE string LOWER CASE DEFAULT 'C:\abap_src\', p_alv TYPE c AS CHECKBOX DEFAULT 'X'. SELECTION-SCREEN END OF BLOCK b1. START-OF-SELECTION. PERFORM frm_query_programs. IF lt_programs IS INITIAL. MESSAGE '没有查询到符合条件的程序' TYPE 'S' DISPLAY LIKE 'E'. RETURN. ENDIF. IF p_alv = 'X'. PERFORM frm_show_alv CHANGING lt_result. IF lt_result IS INITIAL. RETURN. ENDIF. ELSE. lt_result = lt_programs. ENDIF. PERFORM frm_download_source.子程序frm_query_programs是从TADIR表按对象目录查询目标程序:
FORM frm_query_programs. SELECT obj_name devclass author srcdate INTO CORRESPONDING FIELDS OF TABLE lt_programs FROM tadir WHERE pgmid = 'R3TR' AND object = 'PROG' AND obj_name IN s_prog AND devclass IN s_devc. IF sy-subrc <> 0. CLEAR lt_programs. ENDIF. ENDFORM.核心下载子程序frm_download_source如下。这里我对程序名截断、源码读取失败、本地文件写入失败都做了容错,单条失败不会中断整体流程:
FORM frm_download_source. LOOP AT lt_result INTO DATA(ls_prog). ADD 1 TO lv_index. CLEAR lt_source. CALL FUNCTION 'RPY_PROGRAM_READ' EXPORTING program_name = ls_prog-obj_name language = p_lang with_includelist = abap_true TABLES source = lt_source EXCEPTIONS OTHERS = 1. IF sy-subrc <> 0. WRITE: / lv_index, '读取失败', ls_prog-obj_name. CONTINUE. ENDIF. CONCATENATE p_path ls_prog-obj_name '.txt' INTO lv_filename. PERFORM frm_check_dir. PERFORM frm_write_local USING lv_filename lt_source. ENDLOOP. WRITE: / '=================================================='. WRITE: / '执行完成,共处理程序数:', lv_index. ENDFORM. FORM frm_write_local USING uv_filename TYPE string ut_source TYPE STANDARD TABLE. DATA: lv_filename TYPE string. CALL FUNCTION 'GUI_DOWNLOAD' EXPORTING filename = uv_filename filetype = 'ASC' codepage = '4110' TABLES data_tab = ut_source EXCEPTIONS file_write_error = 1 invalid_type = 2 OTHERS = 3. IF sy-subrc = 0. WRITE: / lv_index, '已导出:', uv_filename. ELSE. WRITE: / lv_index, '下载失败:', uv_filename. ENDIF. ENDFORM.这里面的P_LANG参数对应RPY_PROGRAM_READ的LANGUAGE,CODE:PAGE设为4110即UTF-8,适配非Unicode系统导出中文注释,避免记事本打开看到乱码。
3.3 文件命名与目录整理技巧
导出文件命名有个小讲究。直接用程序名加.txt当然直观,但如果同一个请求交付里既有主程序又有包含程序,文件会散落一地不好归档。我实际项目中给文件命名时会在程序名前面加上开发类前缀,例如:
ZDEV_ZMAIN_PROG.txt这样做的好处是,按开发类归档时所有文件自动聚在同一个目录下,审计交付时不用二次整理。
目录不存在时程序会报错,所以最好调用前端服务类创建目录。新版类写法如下:
DATA: lo_frontend TYPE REF TO cl_gui_frontend_services. CALL METHOD lo_frontend->directory_create EXPORTING directory = p_path EXCEPTIONS OTHERS = 1.注意这个类方法不能递归创建多级目录,如果路径中父目录不存在会失败。稳妥的做法是在程序中先按分隔符拆解路径,逐级创建。
4. 实操过程与运行效果验证
4.1 执行前的准备检查
运行这个程序前,建议先在SE80中确认一下自己的开发权限。因为程序要反复读取TADIR对象目录和源码,至少需要S_DEVELOP授权,否则RPY_PROGRAM_READ会报“对象锁”或“无权访问”错误。BASIS账号通常没问题,但普通业务顾问账号建议先在测试环境验证。
执行时在SE38输入报表名Z_DOWNLOAD_ABAP_SRC,点击执行,工作区会先显示选择屏幕,默认包范围是Z*。你可以输入具体范围,也可以只填写某个程序。我一般习惯先用P_ALV勾选打开预览,确认筛选出来的程序清单没问题,再正式下载。
4.2 一次完整导出的实测流程
假设要导出开发类ZDEV下所有程序。选择屏幕填入:
- 程序名:留空或者输入Z*
- 开发类:ZDEV
- 语言:中文(登录语言)
- 路径:D:\audit\ZDEV\
- ALV预览:勾选
点击执行,ALV弹出所有符合条件的程序清单,我按需勾选后点“下载”按钮,程序开始循环写文件。300个程序文件全部下载完,大约耗时40秒左右,平均每个程序100毫秒级别,瓶颈几乎都在文件IO上。
导出的文件打开后,源码行号、注释、字符串引号都和SE38里完全一致。用自己写的工具导出的代码,可以放心拿去做二次编译或审计留档。
4.3 用时间戳避免重复导出覆盖
有一次我没有指定独立目录,把多次导出的文件都放在同一个默认路径下,第二次导出就把第一次的覆盖了,虽然审计要的是当天快照问题不大,但如果要对比版本差异,还是要保留历史文件。后来我在文件命名上增加了时间戳参数:
DATA: lv_timestamp TYPE timestampl. GET TIME STAMP FIELD lv_timestamp. CONCATENATE p_path ls_prog-obj_name '_' lv_timestamp '.txt' INTO lv_filename.SAP时间戳精确到毫秒,重复执行也不会重名。这个技巧在处理带版本对比的交付场景时非常实用。
5. 常见问题与排查技巧实录
5.1 RPY_PROGRAM_READ读取不到源码的三种原因
第一种是TADIR里对象类型不匹配。我在前期测试时,直接用S_PROG过滤TADIR的所有对象类型,结果会把一些非PROG类型的对象也捞进来,导致RPY_PROGRAM_READ报错。解决办法就是在查询条件中固定OBJECT = 'PROG'。
第二种是程序名超长。TADIR的OBJ_NAME字段长度是40位,而ABAP程序名上限是30位,如果对象是变式或视图,直接拿40位名称给RPY_PROGRAM_READ就会失败。稳妥做法是在调用前判断程序名长度,超过30位时用INCLUDE类型特殊处理。
第三种是对象被标记为删除但尚未清理。TADIR里能看到,源码表里其实已经不存在了。这种情况只能捕获异常后记录日志,跳过继续处理后面的程序。
5.2 GUI_DOWNLOAD中文乱码问题
如果导出文件用记事本打开出现中文乱码,多半是CODEPAGE参数不对。Unicode系统建议用4110(UTF-8),非Unicode系统要看系统默认代码页,常见的是1100(Latin-1)或4102(中文)。这里有个判断技巧:先在SE38里看程序源码中文字符是否正常显示,如果正常,则导出的代码页要和系统代码页保持一致。遇到乱码最省事的做法是改用CL_GUI_FRONTEND_SERVICES的GUI_DOWNLOAD方法,它会根据系统自动处理。
5.3 大批量程序导出时的性能优化
几百个程序一起导出时,内存占用其实可以忽略,因为每次循环都会CLEAR内部表,不会积压。但如果你在循环内把LT_SOURCE表附加到另一个大表中,比如为了最后统一下载,内存就会飙升。我的建议是不要攒,边读边写,这样内存非常稳定。
另外,如果下载目标是应用服务器而不是前端,可以用OPEN DATASET直接写文件,速度比GUI_DOWNLOAD快不少,但文件路径需要BASIS配合提前创建好。
5.4 导完代码后如何留痕
审计场景下,导出的代码文件本身还不足够,往往还要附一份导出日志。我在程序里增加了一个简单DB表ZSRCDDLOG,执行成功后插入一条记录:
INSERT zsrcddlog FROM TABLE lt_log. COMMIT WORK.如果你们项目有代码仓库平台,也可以在这里额外调用RFC或HTTP接口,把文件推送到远端。这个扩展点很灵活,需要注意的只是别在循环里反复提交,建议全部处理完成后再集中写日志。
5.5 程序比源码更重要的一点思考
工具本身不是银弹。下载源码只是开始,真正的难点在于整理成可交付、可追溯的文档。我的经验是,最好在程序清单导出后顺手生成一个README说明文件,记录系统版本、导出日期、开发类范围、导出人,这样审计或者交接时一份目录包就全部说明了,不用再问“这批代码是什么时候从哪个系统导出的”。
最后分享一个小技巧:如果公司内网有代码平台,不妨把这个批量导出程序做成定时任务,每天晚上把Z开发类源码自动备份一次。备份文件按日期归档,保留最近30天。这样不管是勒索病毒事件还是误删程序,都能快速找回,比事后翻审计交付清单要省心得多。
本文还有配套的精品资源,点击获取