1. 程式人生 > 其它 >聊聊 C 語言和 ABAP 這兩門程式語言的關係

聊聊 C 語言和 ABAP 這兩門程式語言的關係

為什麼這篇文章要把 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 語言的一些底層實現細節。