1. 程式人生 > >聊聊C語言和ABAP

聊聊C語言和ABAP

這個公眾號之前的文章,分享的都是Jerry和SAP成都研究院的同事在工作中學到的一些知識和感受。而今天這篇文章,寫作的由來是因為最近我又參與了SAP成都數字創新空間應聘者的面試,和一些朋友聊了一些關於用不同的程式語言寫Hello World程式的話題,突然才發現,自己從2007年畢業之後,再沒有使用過C語言進行程式設計了。因此想做一個簡單的回憶。對C語言不感興趣的ABAP開發顧問,可以直接跳到本文講ABAP的章節。

 

為什麼這篇文章要把C語言和ABAP放在一起講,而不是別的語言比如Java和ABAP呢?因為ABAP語言底層是基於C/C++實現的,包括其關鍵字(比如最簡單的關鍵字WRITE的C++實現有2千多行)和虛擬機器(ABAP Runtime)。SAP內部的一群電腦科學家們發明了ABAP這門偉大的語言,由它實現的各種SAP應用幫助了全球超過180個國家和地區的客戶們更好地執行其業務。

通過Google我們能搜尋到一些關於這些SAP電腦科學家們的介紹,比如這個連結:

http://sapexperts.wispubs.com/SAP-Professional-Journal/Articles/From-XML-to-ABAP-Data-Structures-and-Back-Bridging-the-Gap-with-XSLT?id=2CA6B036062F42C5B7A76A772A934911#.WmGiiaiWbdM

 

比如像下圖這種用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有:

  • <REPINI>

  • <SYSINI>

  • <SYSSEL>

  • 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語言底層一些實現細節的顧問朋友們有所幫助。

更多閱讀

要獲取更多Jerry的原創文章,請關注公眾號"汪子熙":