为什么这篇文章要把 C 语言和 ABAP 放在一起讲,而不是别的语言比如 Java 和 ABAP呢?因为 ABAP 语言底层是基于 C/C++ 实现的,包括其关键字(比如最简单的关键字 WRITE 的 C++ 实现有 2 千多行)和虚拟机(ABAP Runtime)。SAP 内部的一群计算机科学家们发明了 ABAP 这门伟大的语言,由它实现的各种 SAP 应用帮助了全球超过 180 个国家和地区的客户们更好地运行其业务。
通过 Google 我们能搜索到一些关于这些 SAP 计算机科学家们的介绍,比如这个链接:
比如像下图这种用 kernel module
修饰的 sc_km_check_feature_2, 以及每一个 ABAP 关键字,其 C 语言的实现代码在 SAP 内部的 Netweaver 系统可以查看到,但是在客户系统上,则是以二进制目标文件的形式存储,无法查看源代码。
本文的目的是希望通过 C 语言和 ABAP 编译过程的一些介绍,加深 ABAP 顾问们对这门语言的理解。
用C语言写个Hello World程序,另存为study.c:
用命令行 gcc ./study.c --verbose
进行编译,参数 verbose 可供我们查看编译明细。上述命令行在我的Ubuntu系统上产生一串长长的输出:
我们可以一步步分析。首先用参数 -E查看预处理生成的目标文件study.i:
gcc -E study.c -o study.i
可以看到源代码文件只有 78
字节,编译预处理后生成的输出文件有 17116
字节。
为什么膨胀了这么多?原因是因为我源代码文件的第一行,#include<stdio.h>
被预处理器替换成了 stdio.h
的实际内容,而 stdio.h 里如果又存在 #include
其他文件的声明,这个替换过程会递归执行。因此直到 study.i 的末尾部分,我们才能看到在 study.c 里书写的源代码部分。
源代码文件 study.c 里的第一行语句 #include<stdio.h>
, 请大家记住,后面讲 ABAP 还会提到。
用命令行 gcc -S 可以查看 study.c 编译后生成的汇编代码:
看到这些 pushq, popq, %rbp
,Jerry 不由得想起本科汇编程序设计专业课上,我和寝室其他兄弟坐在教室最后一排看体坛周报的时光。
工作十多年后,Jerry 不得不承认,当时本科开设的计算机专业课,像数据结构,操作系统,计算机组成原理,编译原理,汇编程序设计,计算机图形学这些都是有用的,工作后,公司不可能再给你时间去学习这些基础理论知识了。
虽然汇编程序设计这门课 Jerry 当初没有好好学,但至少教材我是妥善保存了的,以防哪天公司的工作安排需要让我把十多年前在学校学的东西重新又捡起来。
下面我们来聊聊 ABAP。
SAP note 1230076 Generation of ABAP loads: Tips for the analysis
介绍了一个工具程序:RSDEPEND. 这个 note 提到,一个即便看起来最简单的 ABAP Hello World 报表,其实也依赖于许多标准的 Repository 对象,这些依赖我们假定称其为A,B,C。假设 A,B,C 其中有任何一个有改动产生,比如 A 是一个 include 程序,里面使用到了一个 DDIC 结构,在某个时刻,系统导入了一个传输请求(Transport Request), 里面包含了针对这个 DDIC 结构的更改,那么此时这个最简单的 Hello World 报表的 load 就成为了 obsolete 状态。在重新执行该报表之前,ABAP Runtime(
中文译成 ABAP 运行时
)会自动做一个 load invalidation 操作,生成一个最新版本的 load.
什么是 ABAP load?看 ABAP help 里的官方定义:
In the ABAP environment, a load describes a binary representation of a repository object which is optimized for fast access, in the memory or on the database.
翻译成中文:ABAP load 是 Repository 对象的二进制表现形式,针对 ABAP 环境的快速访问而做过特别优化,可以存储在数据库表中或者加载于内存里。
我们用一个实际的例子来理解 ABAP 报表激活和运行时发生的事情。
创建一张非常简单的透明表 ZLOADTEST:
写一个简单的报表,命名为 ZTESTLOAD. 报表的源代码以压缩的格式存储在表 REPOSRC 的 DATA 字段里。
测试报表的源代码很简单,把表里的数据全部读取出来:
激活这个简单的报表(是的,在 ABAP 世界里,我们习惯说激活,而不是编译)。激活后生成的 ABAP load 存储在表 REPOLOAD 的字段 LDATA 和 QDATA 里。
这两个字段存储的内容就是前面 ABAP help 提到的 ABAP load 在数据库表中的存储形式。
菜单 Goto->Navigate to->Switch to Classic Debugger
:
Goto->System Areas->Internal Information
:
在 System Area 区域输入 CONT,就能在下图的 NAME 列看到 ABAP load 里包含的指令。当然同开源的 JVM 不同,JVM 字节码指令集在网上能够查到,而这些 ABAP load 的指令是 SAP internal 的,因此不能在这里做解释。
然后执行前面提到的工具报表 RSDEPEND, 输入参数 program name = ZTESTLOAD
, 得到结果,其中测试报表的 ABAP Load 时间戳为 07:21:02, 这个报表依赖的标准 Include 有:
- DB__SSEL
由此看出,每一个标准的 ABAP 报表都自动包含了这些 include. 如果开发人员显式地再包含其中任意一个,会遇到语法错误:
Module %_PF_STATUS is already defined as a OUTPUT module
大家觉得这个 <REPINI>
是不是很像前文 C 语言部分提到的 #include<stdio.h>
?
下面我们再做几轮测试。
测试1
修改透明表的描述信息,然后重新激活透明表。
执行 RSDEPEND, 可以看到只有透明表的 Last Changed 字段发生了变化,ABAP Time Stamp 和 Screen Time Stamp 都不变,这是我们期望的结果,因为我们只是修改了透明表的描述信息,并未修改结构。
再次执行测试报表 ZTESTLOAD, 用 RSDEPEND 检测,发现测试报表的 ABAP Load 时间戳没有发生变化,这说明:即使依赖的透明表的描述信息发生变化,使用了该透明表的 ABAP 报表不需要重新编译,因为透明表描述信息不需要在报表执行期使用。
测试2
给透明表增加新的一列,再次激活。
此时通过 RSDEPEND 发现,透明表的三个时间戳全部发生了变化,如下图蓝色矩形框所示。然而测试报表 ABAP Load 本身的时间戳仍然未变,这也是合理的,因为我们给透明表里增加了新的列后,还未执行测试报表。
再次执行 ZTESTLOAD 后,这次发现它的 ABAP Load 已经被自动 invalidate 了,时间戳从07:21:02 变成了 07:36:02。
这也解释了一个现象:有的朋友们观察到,当系统刚升完级后,或者有一批新的传输请求导入到系统后,第一次使用 SAP 应用时,系统响应速度很慢。原因其实通过前文的两个测试已经说明了:系统在花费时间去做相关 ABAP Load invalidation. 在应用依赖的这些 Load invalidation 没有结束之前,系统无法响应用户请求。
为了避免用户在第一次使用应用时长时间等待,可以使用事务码 SGEN 预先进行 Load invalidation. SGEN 详细的使用方法可以参考下面这篇文章
希望这篇文章能给那些想了解 ABAP 语言底层一些实现细节的顾问朋友们有所帮助。
总结
ABAP 是基于 Netweaver 技术栈的 SAP 产品比如 CRM,S/4HANA 等的业务逻辑和底层系统平台实现采取的编程语言。ABAP 是一门高级面向对象的编程语言,其运行时和内核基于 C/C++ 实现。本文通过一些具体的 ABAP 报表例子,介绍了 ABAP 语言的一些底层实现细节。