著作權聲明:轉載時請以超連結形式標明文章原始出處和作者資訊及本聲明
http://dreamhead.blogbus.com/logs/3233507.html
GIMP是基於外掛程式的結構,外掛程式結構的特點是核心部分的內容並不多,更多的功能是通過外掛程式完成的。GIMP是一個圖象處理常式,支援很多圖象處理的方法,而這些方法都是以外掛程式的形式出現的。其架構如所示:
GIMP在它的開發人員網站上提供了一套外掛程式編寫教程,引導外掛程式新手入門:
How to write a GIMP plug-in,第一部分,第二部分,第三部分。
我們一起來看一下,GIMP是如何?外掛程式結構的。上面的教程對外掛程式結構有個概要的介紹。
先
來看一下GIMP外掛程式的存在形式。為了輔助構建GIMP外掛程式,GIMP提供了一個工具——gimptool,實際上它就是對構建外掛程式過程的封裝,省去了自
己尋找相應軟體包的煩惱。運行完gimptool,我們會在gimp外掛程式的目錄(通常是~/.gimp-2.0/plug-ins)下發現我們的外掛程式。實
際上,它就是一個標準應用程式,而不是一個庫。它甚至可以單獨運行,當然,在一無所知的情況下運行它,它會告訴你,我要運行在GIMP下。隨便查看一個
GIMP外掛程式,甚至是上面提到的那個教程,你會發現這樣一個宏調用:MAIN()。翻看源碼,在$GIMP/libgimp/gimp.h中,我們找到了
它,簡化之:
# define MAIN() /
int /
main (int argc, char *argv[]) /
{ /
return gimp_main (&PLUG_IN_INFO, argc, argv); /
}
前面說過,外掛程式是一個應用程式,這裡提供了最好的證據。GIMP外掛程式的運行邏輯都隱藏在gimp_main($GIMP/libgimp/gimp.c)中。
接下來,再來看看GIMP的主程式是如何與外掛程式打交道的。
既
然外掛程式以應用程式的形式存在,那麼主程式調用外掛程式功能也就是調用另外一個程式完成工作。完成開啟二者之間通道的工作在
plug_in_open($GIMP/app/plug-in/plug-in.c)中完成。可以看到,這段代碼的主要工作就是為調用外掛程式程式設定相關
參數,之後以g_spawn_async開啟通往外掛程式的通道。g_spawn_async是glib提供的一個函數,用以建立一個新的進程。
通
道開啟了,那接下來的工作就是二者之間如何通訊。通訊自然要有通訊的協議,對於調用程式來說,最簡單的通訊協定就是調用程式的參數。GIMP外掛程式編寫通常
要指定四個函數,分別是用作初始化、退出、查詢和運行用,其中運行部分是整個外掛程式中最核心的部分。回到gimp_main的實現中,我們便不難發現,初始
化和查詢用到的調用方法就是程式參數的形式。這是最為簡單的方式,當然,不足以解決所有的問題,比如查詢的結果如何送回。
其實這是一個進程
間如何通訊的問題。在*nix下,處理序間通訊的方式有很多,GIMP選擇的是管道(pipe)。回到plug_in_open中,在設定參數的時候,開啟
了兩個管道,分別用於讀寫。當然,隨後它又用glib的io_channel對管道進行封裝。隨後,再把兩個管道的檔案描述符(實際上就是個整數)作為調
用參數傳遞給外掛程式程式。外掛程式程式得到描述符之後,在自己這端將管道開啟,於是兩端便建立起串連。注意,管道是個半雙工的,也就是一個管道只能讀或是寫,所
以,通常建立管道時,得到便是兩個描述符,一個用於讀一個用於寫。為了實現主程式和外掛程式之間的雙向通訊,同時開啟了兩個管道,也就是得到了四個描述符,兩
個作為參數傳遞給外掛程式程式,另外兩個留給了主程式自己。同上面的開啟進程相比,管道才是二者之間真正的通訊渠道。
一樣的討論,通道開啟了,
接下來就是通訊協定。二者之間的通訊協定選擇了二進位的方式,這一點在開啟管道時設定了二進位的訪問方式可見一斑。文本化還是二進位是一個設計上的抉擇,
這裡選擇了二進位的方式,也許運行時效率是一個重要的考慮因素。有一個沒有想清楚的問題是這種處理序間通訊的方式是否還需要如網路通訊一樣查看中間流過的數
據,如果需要,又如何做呢?
主程式和外掛程式之間通訊協定被抽象為訊息模型,所以,兩端的處理看上去是一個典型的訊息處理過程:在一個大迴圈
中,取訊息,處理訊息。外掛程式端的處理參見gimp_loop($GIMP/libgimp/gimp.c),主程式端的處理參見
plug_in_handle_message($GIMP/app/plug-in/plug-in-message.c)。在取訊息的程式
(wire_read_msg,$GIMP/libgimpbase/gimpwire.c)中,我們看到也是一個類似於訊息處理的流程,先取出一個訊息
類型,然後,根據訊息類型找到相應的處理函數,把該訊息的其它部分讀出來。對比寫入訊息的過程
(wire_write_msg,$GIMP/libgimpbase/gimpwire.c),整個訊息發送和接收便一目瞭然了。由此,我們可以看出,
如果需要對通訊協定進行擴充,要做的就是添加一個訊息類型,在增加對應的處理函數。
僅僅是訊息級的通訊顯得還是有些低級,所以,在此基礎
上,GIMP還提供了函數級通訊。我們知道GIMP為編寫外掛程式提供了不少API,而有些API需要和主程式互動方能體現其作用,比如,如果需要某個外掛程式處
理一副映像,外掛程式本身並沒有映像,從何而來,它要調用一個函數得到,而這個函數實際上是通過與主程式互動得到。這些函數藉助就是已有的訊息級通訊完成,而
對於外掛程式的編寫者來說,一切都是透明的。如同網路通訊協定的七層模型,這裡也是通過一層層的構建起一個完整的體系,一個清楚的設計。有了函數調用的方法,通訊
的可擴充性便大大增強。
為了相應外掛程式的請求,主程式也應該有所表示。主程式專門為此建立了一個庫,這就是架構圖上的那個PDB(過程資料
庫),每當發來運行過程的請求,主程式便會在PDB中尋找相應的函數進行調用(plug_in_handle_proc_run,$GIMP/app
/plug-in/plug-in-message.c)。這大名鼎鼎的PDB實際上就是一個hash表($GIMP/app/pdb
/procedural_db)。
上面簡要介紹了GIMP外掛程式結構的實現,這便是最近學習GIMP的一些心得,不見得完全準確,原始碼是最
好的答案。跨越時間的程式都有許多值得學習的東西,十多歲的GIMP也在其中。順便說一下,GIMP的實現很清楚,雖然是用C實現,但模組劃分得非常清
楚。語言限制的只是表達能力而已,而非思想。