在上一講中,筆者介紹了DirectShow的總體系統框架。從這一講開始,我們要從程序員的角度,進一步深入探討一下DirectShow的應用以及Filter的開發。
          在這之前,筆者首先要特別提一下微軟提供的一個Filter測試工具——GraphEdit,它的路徑在DXSDK\bin\DXUtils\GraphEdit.exe。(如果您還沒有安裝DirectX SDK,請到微軟的網站上去下載。)通過這個工具,我們可以很直觀地看到Filter Graph的運行及處理流程,方便我們進行程序調試。(如果您手邊就有電腦,還等什么,馬上體驗一下吧:運行GraphEdit,執行File->Render Media File…選擇一個媒體文件;當Filter Graph構建成功后,按下工具欄的運行按鈕;您就能看到剛才選擇的媒體文件被回放出來了!看到了吧,寫一個媒體播放器也就這么回事!)
          接下去,我們開講Filter的開發。
          學習DirectShow Filter的開發,不外乎以下幾種方法:看幫助文檔、看示例代碼和看SDK基類源代碼。看幫助文檔,應著重于總體概念上的理解;看示例代碼應與基類源代碼的研究同步進行,因為自己寫Filter,關鍵的第一步是選擇一個合適的Filter基類和Pin的基類。對于Filter的把握,一般認為要掌握以下三方面的內容:Filter之間Pin的連接、Filter之間的數據傳輸以及流媒體的隨機訪問(或者說流的定位)。下面就開始分別進行闡述。
          所謂的Filter Pin之間的連接,實際上是Pin之間Media Type(媒體類型)的一個協商過程。連接總是從輸出Pin指向輸入Pin的。要想深入了解具體的連接過程,就必須認真研讀SDK的基類源代碼(位于DXSDK\samples\Multimedia\DirectShow\BaseClasses\amfilter.cpp,類CBasePin的Connect方法)。連接的大致過程為,枚舉欲連接的輸入Pin上所有的媒體類型,逐一用這些媒體類型與輸出Pin進行連接,如果輸出Pin也接受這種媒體類型,則Pin之間的連接宣告成功;如果所有輸入Pin上枚舉的媒體類型輸出Pin都不支持,則枚舉輸出Pin上的所有媒體類型,并逐一用這些媒體類型與輸入Pin進行連接。如果輸入Pin接受其中的一種媒體類型,則Pin之間的連接到此也宣告成功;如果輸出Pin上的所有媒體類型,輸入Pin都不支持,則這兩個Pin之間的連接過程宣告失敗。
          有一點需要注意的是,上述的輸入Pin與輸出Pin一般不屬于同一個Filter,典型的是上一級Filter(也叫Upstream Filter)的輸出Pin連向下一級Filter(也叫Downstream Filter)的輸入Pin。如下圖所示:



          當Filter的Pin之間連接完成,也就是說,連接雙方通過協商取得了一種大家都支持的媒體類型之后,即開始為數據傳輸做準備。這些準備工作中,最重要的是Pin上的內存分配器的協商,一般也是由輸出Pin發起。在DirectShow Filter之間,數據是通過一個一個數據包傳送的,這個數據包叫做Sample。Sample本身是一個COM對象,擁有一段內存用以裝載數據,Sample就由內存分配器(Allocator)來統一管理。已成功連接的一對輸出、輸入Pin使用同一個內存分配器,所以數據從輸出Pin傳送到輸入Pin上是無需內存拷貝的。而典型的數據拷貝,一般發生在Filter內部,從Filter的輸入Pin上讀取數據后,進行一定意圖的處理,然后在Filter的輸出Pin上填充數據,然后繼續往下傳輸。下面,我們就具體闡述一下Filter之間的數據傳送。
          首先,大家要區分一下Filter的兩種主要的數據傳輸模式:推模式(Push Model)和拉模式(Pull Model)。參考圖如下:


            
          所謂推模式,即源Filter(Source Filter)自己能夠產生數據,并且一般在它的輸出Pin上有獨立的子線程負責將數據發送出去,常見的情況如代表WDM模型的采集卡的Live Source Filter;而所謂拉模式,即源Filter不具有把自己的數據送出去的能力,這種情況下,一般源Filter后緊跟著接一個Parser Filter或Splitter Filter,這種Filter一般在輸入Pin上有個獨立的子線程,負責不斷地從源Filter索取數據,然后經過處理后將數據傳送下去,常見的情況如文件源。推模式下,源Filter是主動的;拉模式下,源Filter是被動的。而事實上,如果將上圖拉模式中的源Filter和Splitter Filter看成另一個虛擬的源Filter,則后面的Filter之間的數據傳輸也與推模式完全相同。
          那么,數據到底是怎么通過連接著的Pin傳輸的呢?首先來看推模式。在源Filter后面的Filter輸入Pin上,一定實現了一個IMemInputPin接口,數據正是通過上一級Filter調用這個接口的Receive方法進行傳輸的。值得注意的是(上面已經提到過),數據從輸出Pin通過Receive方法調用傳輸到輸入Pin上,并沒有進行內存拷貝,它只是一個相當于數據到達的“通知”。再看一下拉模式。拉模式下的源Filter的輸出Pin上,一定實現了一個IAsyncReader接口;其后面的Splitter Filter,就是通過調用這個接口的Request方法或者SyncRead方法來獲得數據。Splitter Filter然后像推模式一樣,調用下一級Filter輸入Pin上的IMemInputPin接口Receive方法實現數據的往下傳送。深入了解這部分內容,請認真研讀SDK的基類源代碼(位于DXSDK\samples\Multimedia\DirectShow\BaseClasses\source.cpp和pullpin.cpp)。
          下面,我們來講一下流的定位(Media Seeking)。在GraphEdit中,當我們成功構建了一個Filter Graph之后,我們就可以播放它。在播放中,我們可以看到進度條也在相應地前進。當然,我們也可以通過拖動進度條,實現隨機訪問。要做到這一點,在應用程序級別應該可以知道Filter Graph總共要播放多長時間,當前播放到什么位置等等。那么,在Filter級別,這一點是怎么實現的呢?
          我們知道,若干個Filter通過Pin的相互連接組成了Filter Graph。而這個Filter Graph是由另一個COM對象Filter Graph Manager來管理的。通過Filter Graph Manager,我們就可以得到一個IMediaSeeking的接口來實現對流媒體的定位。在Filter級別,我們可以看到,Filter Graph Manager首先從最后一個Filter(Renderer Filter)開始,詢問上一級Filter的輸出Pin是否支持IMediaSeeking接口。如果支持,則返回這個接口;如果不支持,則繼續往上一級Filter詢問,直到源Filter。一般在源Filter的輸出Pin上實現IMediaSeeking接口,它告訴調用者總共有多長時間的媒體內容,當前播放位置等信息。(如果是文件源,一般在Parser Filter或Splitter Filter實現這個接口。)對于Filter開發者來說,如果我們寫的是源Filter,我們就要在Filter的輸出Pin上實現IMediaSeeking這個接口;如果寫的是中間的傳輸Filter,只需要在輸出Pin上將用戶的獲得接口請求往上傳遞給上一級Filter的輸出Pin;如果寫的是Renderer Filter,需要在Filter上將用戶的獲得接口請求往上傳遞給上一級Filter的輸出Pin。進一步的了解,請認真研讀SDK的基類源代碼(位于DXSDK\samples\Multimedia\DirectShow\BaseClasses\transfrm.cpp的類方法CTransformOutputPin::NonDelegatingQueryInterface實現和ctlutil.cpp中類CPosPassThru的實現)。
          以上我們介紹了一下如何學習DirectShow Filter開發,以及一些開始寫自己的Filter之前的預備知識。下一講,筆者將根據自己開發Filter的經驗,手把手教你如何寫自己的Filter。

          posted @ 2008-09-19 15:06 小馬歌 閱讀(397) | 評論 (0)編輯 收藏
           
          來源:http://blog.csdn.net/happydeer/archive/2003/01/04/8769.aspx

          流媒體的處理,以其復雜性和技術性,一向廣受工業界的關注。特別伴隨著因特網的普及,流媒體在網絡上的廣泛應用,怎樣使流媒體的處理變得簡單而富有成效逐漸成為了焦點問題。選擇一種合適的應用方案,事半功倍。此時,微軟的DirectShow,給了我們一個不錯的選擇。
          DirectShow是微軟公司提供的一套在Windows平臺上進行流媒體處理的開發包,與DirectX開發包一起發布。目前,DirectX最新版本為9.0。
          那么,DirectShow能夠做些什么呢?且看,DirectShow為多媒體流的捕捉和回放提供了強有力的支持。運用DirectShow,我們可以很方便地從支持WDM驅動模型的采集卡上捕獲數據,并且進行相應的后期處理乃至存儲到文件中。它廣泛地支持各種媒體格式,包括Asf、Mpeg、Avi、Dv、Mp3、Wave等等,使得多媒體數據的回放變得輕而易舉。另外,DirectShow還集成了DirectX其它部分(比如DirectDraw、DirectSound)的技術,直接支持DVD的播放,視頻的非線性編輯,以及與數字攝像機的數據交換。更值得一提的是,DirectShow提供的是一種開放式的開發環境,我們可以根據自己的需要定制自己的組件。
          接下去,我們需要對DirectShow系統有個整體的印象。參見以下DirectShow的系統示意圖:


          圖中央最大的一塊即是DirectShow系統。DirectShow使用一種叫Filter Graph的模型來管理整個數據流的處理過程;參與數據處理的各個功能模塊叫做Filter;各個Filter在Filter Graph中按一定的順序連接成一條“流水線”協同工作。大家可以看到,按照功能來分,Filter大致分為三類:Source Filters、Transform Filters和Rendering Filters。Source Filters主要負責取得數據,數據源可以是文件、因特網、或者計算機里的采集卡、數字攝像機等,然后將數據往下傳輸;Transform Fitlers主要負責數據的格式轉換、傳輸;Rendering Filtes主要負責數據的最終去向,我們可以將數據送給聲卡、顯卡進行多媒體的演示,也可以輸出到文件進行存儲。值得注意的是,三個部分并不是都只有一個Filter去完成功能。恰恰相反,每個部分往往是有幾個Fitler協同工作的。比如,Transform Filters可能包含了一個Mpeg的解碼Filter、以及視頻色彩空間的轉換Filter、音頻采樣頻率轉換Filter等等。除了系統提供的大量Filter外,我們可以定制自己的Filter,以完成我們需要的功能。下圖是一條典型的Avi文件回放Filter Graph鏈路:



          在DirectShow系統之上,我們看到的,即是我們的應用程序(Application)。應用程序要按照一定的意圖建立起相應的Filter Graph,然后通過Filter Graph Manager來控制整個的數據處理過程。DirectShow能在Filter Graph運行的時候接收到各種事件,并通過消息的方式發送到我們的應用程序。這樣,就實現了應用程序與DirectShow系統之間的交互。下圖給出了DirectShow應用程序開發的一般過程:




          以上簡單介紹了DirectShow的系統結構,希望大家對這個強勁的應用框架已經有了大概的認識。如果你有興趣,可以詳細研究DirectX的幫助文檔。DirectShow是一個強大的開發包;另外,它是基于COM的,因此要求程序員具有COM編程的一些基本知識。關于如何深入學習DirectShow應用結構以及開發自己的Filter,請參閱筆者的后續文章。筆者將從編程的角度,詳細講述來源于實際工作中的經驗之談。

          posted @ 2008-09-19 15:05 小馬歌 閱讀(249) | 評論 (0)編輯 收藏
           

          來源:http://blog.csdn.net/happydeer

          小知識:AVI文件格式----摘自《DirectShow實務精選》 作者:陸其明

          AVI(Audio Video Interleaved的縮寫)是一種RIFF(Resource Interchange File Format的縮寫)文件格式,多用于音視頻捕捉、編輯、回放等應用程序中。通常情況下,一個AVI文件可以包含多個不同類型的媒體流(典型的情況下有一個音頻流和一個視頻流),不過含有單一音頻流或單一視頻流的AVI文件也是合法的。AVI可以算是Windows操作系統上最基本的、也是最常用的一種媒體文件格式。


          先來介紹RIFF文件格式。RIFF文件使用四字符碼FOURCC(four-character code)來表征數據類型,比如‘RIFF’、‘AVI ’、‘LIST’等。注意,Windows操作系統使用的字節順序是little-endian,因此一個四字符碼‘abcd’實際的DWORD值應為0x64636261。另外,四字符碼中像‘AVI ’一樣含有空格也是合法的。


          RIFF文件首先含有一個如圖3.31的文件頭結構。

           

          圖3.31 RIFF文件結構

          最開始的4個字節是一個四字符碼‘RIFF’,表示這是一個RIFF文件;緊跟著后面用4個字節表示此RIFF文件的大小;然后又是一個四字符碼說明文件的具體類型(比如AVI、WAVE等);最后就是實際的數據。注意文件大小值的計算方法為:實際數據長度 + 4(文件類型域的大小);也就是說,文件大小的值不包括‘RIFF’域和“文件大小”域本身的大小。


          RIFF文件的實際數據中,通常還使用了列表(List)和塊(Chunk)的形式來組織。列表可以嵌套子列表和塊。其中,列表的結構為:‘LIST’ listSize listType listData ——‘LIST’是一個四字符碼,表示這是一個列表;listSize占用4字節,記錄了整個列表的大小;listType也是一個四字符碼,表示本列表的具體類型;listData就是實際的列表數據。注意listSize值的計算方法為:實際的列表數據長度 + 4(listType域的大小);也就是說listSize值不包括‘LIST’域和listSize域本身的大小。再來看塊的結構:ckID ckSize ckData ——ckID是一個表示塊類型的四字符碼;ckSize占用4字節,記錄了整個塊的大小;ckData為實際的塊數據。注意ckSize值指的是實際的塊數據長度,而不包括ckID域和ckSize域本身的大小。(注意:在下面的內容中,將以LIST ( listType ( listData ) )的形式來表示一個列表,以ckID ( ckData )的形式來表示一個塊,如[ optional element ]中括號中的元素表示為可選項。)


          接下來介紹AVI文件格式。AVI文件類型用一個四字符碼‘AVI ’來表示。整個AVI文件的結構為:一個RIFF頭 + 兩個列表(一個用于描述媒體流格式、一個用于保存媒體流數據) + 一個可選的索引塊。AVI文件的展開結構大致如下:

           

          RIFF (‘AVI ’
                LIST (‘hdrl’
                      ‘avih’(主AVI信息頭數據)
                      LIST (‘strl’
                            ‘strh’ (流的頭信息數據)
                            ‘strf’ (流的格式信息數據)
                            [‘strd’ (可選的額外的頭信息數據) ]
                            [‘strn’ (可選的流的名字) ]
                            ...
                           )
                       ...
                     )
                LIST (‘movi’
                      { SubChunk | LIST (‘rec ’
                                        SubChunk1
                                        SubChunk2
                                        ...
                                       )
                         ...
                      }
                      ...
                     )
                [‘idx1’ (可選的AVI索引塊數據) ]
               )

          首先,RIFF (‘AVI ’…)表征了AVI文件類型。然后就是AVI文件必需的第一個列表——‘hdrl’列表,用于描述AVI文件中各個流的格式信息(AVI文件中的每一路媒體數據都稱為一個流)。‘hdrl’列表嵌套了一系列塊和子列表——首先是一個‘avih’塊,用于記錄AVI文件的全局信息,比如流的數量、視頻圖像的寬和高等,可以使用一個AVIMAINHEADER數據結構來操作:

          typedef struct _avimainheader {
              FOURCC fcc;   // 必須為‘avih’
              DWORD  cb;    // 本數據結構的大小,不包括最初的8個字節(fcc和cb兩個域)
              DWORD  dwMicroSecPerFrame;   // 視頻幀間隔時間(以毫秒為單位)
              DWORD  dwMaxBytesPerSec;     // 這個AVI文件的最大數據率
              DWORD  dwPaddingGranularity; // 數據填充的粒度
              DWORD  dwFlags;         // AVI文件的全局標記,比如是否含有索引塊等
              DWORD  dwTotalFrames;   // 總幀數
              DWORD  dwInitialFrames; // 為交互格式指定初始幀數(非交互格式應該指定為0)
              DWORD  dwStreams;       // 本文件包含的流的個數
              DWORD  dwSuggestedBufferSize; // 建議讀取本文件的緩存大小(應能容納最大的塊)
              DWORD  dwWidth;         // 視頻圖像的寬(以像素為單位)
              DWORD  dwHeight;        // 視頻圖像的高(以像素為單位)
              DWORD  dwReserved[4];   // 保留
          } AVIMAINHEADER;

          然后,就是一個或多個‘strl’子列表。(文件中有多少個流,這里就對應有多少個‘strl’子列表。)每個‘strl’子列表至少包含一個‘strh’塊和一個‘strf’塊,而‘strd’塊(保存編解碼器需要的一些配置信息)和‘strn’塊(保存流的名字)是可選的。首先是‘strh’塊,用于說明這個流的頭信息,可以使用一個AVISTREAMHEADER數據結構來操作:

          typedef struct _avistreamheader {
               FOURCC fcc;  // 必須為‘strh’
               DWORD  cb;   // 本數據結構的大小,不包括最初的8個字節(fcc和cb兩個域)
          FOURCC fccType;    // 流的類型:‘auds’(音頻流)、‘vids’(視頻流)、
                             //‘mids’(MIDI流)、‘txts’(文字流)
               FOURCC fccHandler; // 指定流的處理者,對于音視頻來說就是解碼器
               DWORD  dwFlags;    // 標記:是否允許這個流輸出?調色板是否變化?
               WORD   wPriority;  // 流的優先級(當有多個相同類型的流時優先級最高的為默認流)
               WORD   wLanguage;
               DWORD  dwInitialFrames; // 為交互格式指定初始幀數
               DWORD  dwScale;   // 這個流使用的時間尺度
               DWORD  dwRate;
               DWORD  dwStart;   // 流的開始時間
               DWORD  dwLength;  // 流的長度(單位與dwScale和dwRate的定義有關)
               DWORD  dwSuggestedBufferSize; // 讀取這個流數據建議使用的緩存大小
               DWORD  dwQuality;    // 流數據的質量指標(0 ~ 10,000)
               DWORD  dwSampleSize; // Sample的大小
               struct {
                   short int left;
                   short int top;
                   short int right;
                   short int bottom;
          }  rcFrame;  // 指定這個流(視頻流或文字流)在視頻主窗口中的顯示位置
                       // 視頻主窗口由AVIMAINHEADER結構中的dwWidth和dwHeight決定
          } AVISTREAMHEADER;

          然后是‘strf’塊,用于說明流的具體格式。如果是視頻流,則使用一個BITMAPINFO數據結構來描述;如果是音頻流,則使用一個WAVEFORMATEX數據結構來描述。


          當AVI文件中的所有流都使用一個‘strl’子列表說明了以后(注意:‘strl’子列表出現的順序與媒體流的編號是對應的,比如第一個‘strl’子列表說明的是第一個流(Stream 0),第二個‘strl’子列表說明的是第二個流(Stream 1),以此類推),‘hdrl’列表的任務也就完成了,隨后跟著的就是AVI文件必需的第二個列表——‘movi’列表,用于保存真正的媒體流數據(視頻圖像幀數據或音頻采樣數據等)。那么,怎么來組織這些數據呢?可以將數據塊直接嵌在‘movi’列表里面,也可以將幾個數據塊分組成一個‘rec ’列表后再編排進‘movi’列表。(注意:在讀取AVI文件內容時,建議將一個‘rec ’列表中的所有數據塊一次性讀出。)但是,當AVI文件中包含有多個流的時候,數據塊與數據塊之間如何來區別呢?于是數據塊使用了一個四字符碼來表征它的類型,這個四字符碼由2個字節的類型碼和2個字節的流編號組成。標準的類型碼定義如下:‘db’(非壓縮視頻幀)、‘dc’(壓縮視頻幀)、‘pc’(改用新的調色板)、‘wb’(音縮視頻)。比如第一個流(Stream 0)是音頻,則表征音頻數據塊的四字符碼為‘00wb’;第二個流(Stream 1)是視頻,則表征視頻數據塊的四字符碼為‘00db’或‘00dc’。對于視頻數據來說,在AVI數據序列中間還可以定義一個新的調色板,每個改變的調色板數據塊用‘xxpc’來表征,新的調色板使用一個數據結構AVIPALCHANGE來定義。(注意:如果一個流的調色辦中途可能改變,則應在這個流格式的描述中,也就是AVISTREAMHEADER結構的dwFlags中包含一個AVISF_VIDEO_PALCHANGES標記。)另外,文字流數據塊可以使用隨意的類型碼表征。


          最后,緊跟在‘hdrl’列表和‘movi’列表之后的,就是AVI文件可選的索引塊。這個索引塊為AVI文件中每一個媒體數據塊進行索引,并且記錄它們在文件中的偏移(可能相對于‘movi’列表,也可能相對于AVI文件開頭)。索引塊使用一個四字符碼‘idx1’來表征,索引信息使用一個數據結構來AVIOLDINDEX定義。

           

          typedef struct _avioldindex {
             FOURCC  fcc;  // 必須為‘idx1’
             DWORD   cb;   // 本數據結構的大小,不包括最初的8個字節(fcc和cb兩個域)
             struct _avioldindex_entry {
                DWORD   dwChunkId;   // 表征本數據塊的四字符碼
                DWORD   dwFlags;     // 說明本數據塊是不是關鍵幀、是不是‘rec ’列表等信息
                DWORD   dwOffset;    // 本數據塊在文件中的偏移量
                DWORD   dwSize;      // 本數據塊的大小
            } aIndex[]; // 這是一個數組!為每個媒體數據塊都定義一個索引信息
          } AVIOLDINDEX;

          注意:如果一個AVI文件包含有索引塊,則應在主AVI信息頭的描述中,也就是AVIMAINHEADER結構的dwFlags中包含一個AVIF_HASINDEX標記。


          還有一種特殊的數據塊,用一個四字符碼‘JUNK’來表征,它用于內部數據的隊齊(填充),應用程序應該忽略這些數據塊的實際意義。

          posted @ 2008-09-19 15:03 小馬歌 閱讀(304) | 評論 (0)編輯 收藏
           
          在我的另一篇日志中,說到利用FFmpeg從視頻截圖的命令,那天在找從視頻截取指定幀的圖片的辦法,這么多天沒有進展,原來我從網上找的關于FFmpeg的參數命令列表并不全,少了-ss這么一個參數.于是這個問題也到現在才解決.

              今天利用FFmpeg -h > ffmpeg.txt,把FFmpeg的命令打印出來后,才發現了這一參數:

          -ss time_off set the start time offset

          使用-ss參數,可以從指定的時間開始處理轉換任務.如:

          ffmpeg -i test2.asf -y -f image2 -ss 08.010 -t 0.001 -s 352x240 b.jpg

          那么從任意一幀截圖的問題也就解決了.只要-ss后的時間參數是隨機產生,并且在視頻的有效時間內,就可以了.

           另外,-ss后跟的時間單位為秒。

          2006
          -06-27 補充:


          另外,通過指定
          -ss,和-vframes也可以達到同樣的效果。

          這時候
          -ss參數后跟的時間有兩種寫法,hh:mm:ss 或 直接寫秒數 :

          ffmpeg 
          -i test.asf --f  image2  -ss 00:01:00 -vframes 1  test1.jpg
          or
          ffmpeg 
          -i test.asf --f  image2  -ss 60 -vframes 1  test1.jpg

          這樣輸出的圖片是相同的。
          posted @ 2008-09-19 15:01 小馬歌 閱讀(711) | 評論 (0)編輯 收藏
           
          來源:http://ffmpeg.blogbus.com/logs/445361.html

          在為Linux開發應用程序時,絕大多數情況下使用的都是C語言,因此幾乎每一位Linux程序員面臨的首要問題都是如何靈活運用C編譯器。目前Linux下最常用的C語言編譯器是GCC(GNU Compiler Collection),它是GNU項目中符合ANSI C標準的編譯系統,能夠編譯用C、C++和Object C等語言編寫的程序。GCC不僅功能非常強大,結構也異常靈活。最值得稱道的一點就是它可以通過不同的前端模塊來支持各種語言,如Java、Fortran、Pascal、Modula-3和Ada等。

          開放、自由和靈活是Linux的魅力所在,而這一點在GCC上的體現就是程序員通過它能夠更好地控制整個編譯過程。在使用GCC編譯程序時,編譯過程可以被細分為四個階段:

          ◆ 預處理(Pre-Processing)

          ◆ 編譯(Compiling)

          ◆ 匯編(Assembling)

          ◆ 鏈接(Linking)

          Linux程序員可以根據自己的需要讓GCC在編譯的任何階段結束,以便檢查或使用編譯器在該階段的輸出信息,或者對最后生成的二進制文件進行控制,以便通過加入不同數量和種類的調試代碼來為今后的調試做好準備。和其它常用的編譯器一樣,GCC也提供了靈活而強大的代碼優化功能,利用它可以生成執行效率更高的代碼。

          GCC提供了30多條警告信息和三個警告級別,使用它們有助于增強程序的穩定性和可移植性。此外,GCC還對標準的C和C++語言進行了大量的擴展,提高程序的執行效率,有助于編譯器進行代碼優化,能夠減輕編程的工作量。

          GCC起步

          在學習使用GCC之前,下面的這個例子能夠幫助用戶迅速理解GCC的工作原理,并將其立即運用到實際的項目開發中去。首先用熟悉的編輯器輸入清單1所示的代碼:

          清單1:hello.c

           #include <stdio.h>
           int main(void)
           {
            printf ("Hello world, Linux programming!\n");
            return 0;
           }
           
          然后執行下面的命令編譯和運行這段程序:

           # gcc hello.c -o hello # ./hello Hello world, Linux programming!
           
          從程序員的角度看,只需簡單地執行一條GCC命令就可以了,但從編譯器的角度來看,卻需要完成一系列非常繁雜的工作。首先,GCC需要調用預處理程序cpp,由它負責展開在源文件中定義的宏,并向其中插入“#include”語句所包含的內容;接著,GCC會調用ccl和as將處理后的源代碼編譯成目標代碼;最后,GCC會調用鏈接程序ld,把生成的目標代碼鏈接成一個可執行程序。

          為了更好地理解GCC的工作過程,可以把上述編譯過程分成幾個步驟單獨進行,并觀察每步的運行結果。第一步是進行預編譯,使用-E參數可以讓GCC在預處理結束后停止編譯過程:

           # gcc -E hello.c -o hello.i
           
          此時若查看hello.cpp文件中的內容,會發現stdio.h的內容確實都插到文件里去了,而其它應當被預處理的宏定義也都做了相應的處理。下一步是將hello.i編譯為目標代碼,這可以通過使用-c參數來完成:

           # gcc -c hello.i -o hello.o
           
          GCC默認將.i文件看成是預處理后的C語言源代碼,因此上述命令將自動跳過預處理步驟而開始執行編譯過程,也可以使用-x參數讓GCC從指定的步驟開始編譯。最后一步是將生成的目標文件鏈接成可執行文件:

           # gcc hello.o -o hello
           
          在采用模塊化的設計思想進行軟件開發時,通常整個程序是由多個源文件組成的,相應地也就形成了多個編譯單元,使用GCC能夠很好地管理這些編譯單元。假設有一個由foo1.c和foo2.c兩個源文件組成的程序,為了對它們進行編譯,并最終生成可執行程序foo,可以使用下面這條命令:

           # gcc foo1.c foo2.c -o foo
           
          如果同時處理的文件不止一個,GCC仍然會按照預處理、編譯和鏈接的過程依次進行。如果深究起來,上面這條命令大致相當于依次執行如下三條命令:

           # gcc -c foo1.c -o foo1.o # gcc -c foo2.c -o foo2.o # gcc foo1.o foo2.o -o foo
           
          在編譯一個包含許多源文件的工程時,若只用一條GCC命令來完成編譯是非常浪費時間的。假設項目中有100個源文件需要編譯,并且每個源文件中都包含10000行代碼,如果像上面那樣僅用一條GCC命令來完成編譯工作,那么GCC需要將每個源文件都重新編譯一遍,然后再全部連接起來。很顯然,這樣浪費的時間相當多,尤其是當用戶只是修改了其中某一個文件的時候,完全沒有必要將每個文件都重新編譯一遍,因為很多已經生成的目標文件是不會改變的。要解決這個問題,關鍵是要靈活運用GCC,同時還要借助像Make這樣的工具。

          警告提示功能

          GCC包含完整的出錯檢查和警告提示功能,它們可以幫助Linux程序員寫出更加專業和優美的代碼。先來讀讀清單2所示的程序,這段代碼寫得很糟糕,仔細檢查一下不難挑出很多毛病:

          ◆main函數的返回值被聲明為void,但實際上應該是int;

          ◆使用了GNU語法擴展,即使用long long來聲明64位整數,不符合ANSI/ISO C語言標準;

          ◆main函數在終止前沒有調用return語句。

          清單2:illcode.c

           #include <stdio.h>
           void main(void)
           {
            long long int var = 1;
            printf("It is not standard C code!\n");
           }

          下面來看看GCC是如何幫助程序員來發現這些錯誤的。當GCC在編譯不符合ANSI/ISO C語言標準的源代碼時,如果加上了-pedantic選項,那么使用了擴展語法的地方將產生相應的警告信息:

           # gcc -pedantic illcode.c -o illcode illcode.c: In function `main': illcode.c:9: ISO C89 does not support `long long' illcode.c:8: return type of `main' is not `int'

          需要注意的是,-pedantic編譯選項并不能保證被編譯程序與ANSI/ISO C標準的完全兼容,它僅僅只能用來幫助Linux程序員離這個目標越來越近。或者換句話說,-pedantic選項能夠幫助程序員發現一些不符合ANSI/ISO C標準的代碼,但不是全部,事實上只有ANSI/ISO C語言標準中要求進行編譯器診斷的那些情況,才有可能被GCC發現并提出警告。

          除了-pedantic之外,GCC還有一些其它編譯選項也能夠產生有用的警告信息。這些選項大多以-W開頭,其中最有價值的當數-Wall了,使用它能夠使GCC產生盡可能多的警告信息:

           # gcc -Wall illcode.c -o illcode illcode.c:8: warning: return type of `main' is not `int' illcode.c: In function `main': illcode.c:9: warning: unused variable `var'

          GCC給出的警告信息雖然從嚴格意義上說不能算作是錯誤,但卻很可能成為錯誤的棲身之所。一個優秀的Linux程序員應該盡量避免產生警告信息,使自己的代碼始終保持簡潔、優美和健壯的特性

          在處理警告方面,另一個常用的編譯選項是-Werror,它要求GCC將所有的警告當成錯誤進行處理,這在使用自動編譯工具(如Make等)時非常有用。如果編譯時帶上-Werror選項,那么GCC會在所有產生警告的地方停止編譯,迫使程序員對自己的代碼進行修改。只有當相應的警告信息消除時,才可能將編譯過程繼續朝前推進。執行情況如下:

           # gcc -Wall -Werror illcode.c -o illcode cc1: warnings being treated as errors illcode.c:8: warning: return type of `main' is not `int' illcode.c: In function `main': illcode.c:9: warning: unused variable `var'

          對Linux程序員來講,GCC給出的警告信息是很有價值的,它們不僅可以幫助程序員寫出更加健壯的程序,而且還是跟蹤和調試程序的有力工具。建議在用GCC編譯源代碼時始終帶上-Wall選項,并把它逐漸培養成為一種習慣,這對找出常見的隱式編程錯誤很有幫助。

          庫依賴

          在Linux下開發軟件時,完全不使用第三方函數庫的情況是比較少見的,通常來講都需要借助一個或多個函數庫的支持才能夠完成相應的功能。從程序員的角度看,函數庫實際上就是一些頭文件(.h)和庫文件(.so或者.a)的集合。雖然Linux下的大多數函數都默認將頭文件放到/usr/include/目錄下,而庫文件則放到/usr/lib/目錄下,但并不是所有的情況都是這樣。正因如此,GCC在編譯時必須有自己的辦法來查找所需要的頭文件和庫文件。

          GCC采用搜索目錄的辦法來查找所需要的文件,-I選項可以向GCC的頭文件搜索路徑中添加新的目錄。例如,如果在/home/xiaowp/include/目錄下有編譯時所需要的頭文件,為了讓GCC能夠順利地找到它們,就可以使用-I選項:

           # gcc foo.c -I /home/xiaowp/include -o foo

          同樣,如果使用了不在標準位置的庫文件,那么可以通過-L選項向GCC的庫文件搜索路徑中添加新的目錄。例如,如果在/home/xiaowp/lib/目錄下有鏈接時所需要的庫文件libfoo.so,為了讓GCC能夠順利地找到它,可以使用下面的命令:

           # gcc foo.c -L /home/xiaowp/lib -lfoo -o foo

          值得好好解釋一下的是-l選項,它指示GCC去連接庫文件libfoo.so。Linux下的庫文件在命名時有一個約定,那就是應該以lib三個字母開頭,由于所有的庫文件都遵循了同樣的規范,因此在用-l選項指定鏈接的庫文件名時可以省去lib三個字母,也就是說GCC在對-lfoo進行處理時,會自動去鏈接名為libfoo.so的文件。

          Linux下的庫文件分為兩大類分別是動態鏈接庫(通常以.so結尾)和靜態鏈接庫(通常以.a結尾),兩者的差別僅在程序執行時所需的代碼是在運行時動態加載的,還是在編譯時靜態加載的。默認情況下,GCC在鏈接時優先使用動態鏈接庫,只有當動態鏈接庫不存在時才考慮使用靜態鏈接庫,如果需要的話可以在編譯時加上-static選項,強制使用靜態鏈接庫。例如,如果在/home/xiaowp/lib/目錄下有鏈接時所需要的庫文件libfoo.so和libfoo.a,為了讓GCC在鏈接時只用到靜態鏈接庫,可以使用下面的命令:

           # gcc foo.c -L /home/xiaowp/lib -static -lfoo -o foo
           
          代碼優化

          代碼優化指的是編譯器通過分析源代碼,找出其中尚未達到最優的部分,然后對其重新進行組合,目的是改善程序的執行性能。GCC提供的代碼優化功能非常強大,它通過編譯選項-On來控制優化代碼的生成,其中n是一個代表優化級別的整數。對于不同版本的GCC來講,n的取值范圍及其對應的優化效果可能并不完全相同,比較典型的范圍是從0變化到2或3。

          編譯時使用選項-O可以告訴GCC同時減小代碼的長度和執行時間,其效果等價于-O1。在這一級別上能夠進行的優化類型雖然取決于目標處理器,但一般都會包括線程跳轉(Thread Jump)和延遲退棧(Deferred Stack Pops)兩種優化。選項-O2告訴GCC除了完成所有-O1級別的優化之外,同時還要進行一些額外的調整工作,如處理器指令調度等。選項-O3則除了完成所有-O2級別的優化之外,還包括循環展開和其它一些與處理器特性相關的優化工作。通常來說,數字越大優化的等級越高,同時也就意味著程序的運行速度越快。許多Linux程序員都喜歡使用-O2選項,因為它在優化長度、編譯時間和代碼大小之間,取得了一個比較理想的平衡點。

          下面通過具體實例來感受一下GCC的代碼優化功能,所用程序如清單3所示。

          清單3:optimize.c

           #include <stdio.h>
           int main(void)
           {
            double counter;
            double result;
            double temp;
            
            for (counter = 0; counter < 2000.0 * 2000.0 * 2000.0 / 20.0 + 2020; counter += (5 - 1) / 4)
            {
              temp = counter / 1979; result = counter;
            }
            printf("Result is %lf\n", result); return 0;
           }

          首先不加任何優化選項進行編譯:

           # gcc -Wall optimize.c -o optimize
           
          借助Linux提供的time命令,可以大致統計出該程序在運行時所需要的時間:

           # time ./optimize Result is 400002019.000000 real 0m14.942s user 0m14.940s sys 0m0.000s
           
          接下去使用優化選項來對代碼進行優化處理:

           # gcc -Wall -O optimize.c -o optimize
           
          在同樣的條件下再次測試一下運行時間:

           # time ./optimize Result is 400002019.000000 real 0m3.256s user 0m3.240s sys 0m0.000s
           
          對比兩次執行的輸出結果不難看出,程序的性能的確得到了很大幅度的改善,由原來的14秒縮短到了3秒。這個例子是專門針對GCC的優化功能而設計的,因此優化前后程序的執行速度發生了很大的改變。盡管GCC的代碼優化功能非常強大,但作為一名優秀的Linux程序員,首先還是要力求能夠手工編寫出高質量的代碼。如果編寫的代碼簡短,并且邏輯性強,編譯器就不會做更多的工作,甚至根本用不著優化。

          優化雖然能夠給程序帶來更好的執行性能,但在如下一些場合中應該避免優化代碼:

          ◆ 程序開發的時候 優化等級越高,消耗在編譯上的時間就越長,因此在開發的時候最好不要使用優化選項,只有到軟件發行或開發結束的時候,才考慮對最終生成的代碼進行優化。

          ◆ 資源受限的時候 一些優化選項會增加可執行代碼的體積,如果程序在運行時能夠申請到的內存資源非常緊張(如一些實時嵌入式設備),那就不要對代碼進行優化,因為由這帶來的負面影響可能會產生非常嚴重的后果。

          ◆ 跟蹤調試的時候 在對代碼進行優化的時候,某些代碼可能會被刪除或改寫,或者為了取得更佳的性能而進行重組,從而使跟蹤和調試變得異常困難。

          調試

          一個功能強大的調試器不僅為程序員提供了跟蹤程序執行的手段,而且還可以幫助程序員找到解決問題的方法。對于Linux程序員來講,GDB(GNU Debugger)通過與GCC的配合使用,為基于Linux的軟件開發提供了一個完善的調試環境。

          默認情況下,GCC在編譯時不會將調試符號插入到生成的二進制代碼中,因為這樣會增加可執行文件的大小。如果需要在編譯時生成調試符號信息,可以使用GCC的-g或者-ggdb選項。GCC在產生調試符號時,同樣采用了分級的思路,開發人員可以通過在-g選項后附加數字1、2或3來指定在代碼中加入調試信息的多少。默認的級別是2(-g2),此時產生的調試信息包括擴展的符號表、行號、局部或外部變量信息。級別3(-g3)包含級別2中的所有調試信息,以及源代碼中定義的宏。級別1(-g1)不包含局部變量和與行號有關的調試信息,因此只能夠用于回溯跟蹤和堆棧轉儲之用。回溯跟蹤指的是監視程序在運行過程中的函數調用歷史,堆棧轉儲則是一種以原始的十六進制格式保存程序執行環境的方法,兩者都是經常用到的調試手段。

          GCC產生的調試符號具有普遍的適應性,可以被許多調試器加以利用,但如果使用的是GDB,那么還可以通過-ggdb選項在生成的二進制代碼中包含GDB專用的調試信息。這種做法的優點是可以方便GDB的調試工作,但缺點是可能導致其它調試器(如DBX)無法進行正常的調試。選項-ggdb能夠接受的調試級別和-g是完全一樣的,它們對輸出的調試符號有著相同的影響。

          需要注意的是,使用任何一個調試選項都會使最終生成的二進制文件的大小急劇增加,同時增加程序在執行時的開銷,因此調試選項通常僅在軟件的開發和調試階段使用。調試選項對生成代碼大小的影響從下面的對比過程中可以看出來:

           # gcc optimize.c -o optimize # ls optimize -l -rwxrwxr-x 1 xiaowp xiaowp 11649 Nov 20 08:53 optimize (未加調試選項) # gcc -g optimize.c -o optimize # ls optimize -l -rwxrwxr-x 1 xiaowp xiaowp 15889 Nov 20 08:54 optimize (加入調試選項)
           
          雖然調試選項會增加文件的大小,但事實上Linux中的許多軟件在測試版本甚至最終發行版本中仍然使用了調試選項來進行編譯,這樣做的目的是鼓勵用戶在發現問題時自己動手解決,是Linux的一個顯著特色。

          下面還是通過一個具體的實例說明如何利用調試符號來分析錯誤,所用程序見清單4所示。

          清單4:crash.c

           #include <stdio.h>
           int main(void)
           {
            int input =0;
            
            printf("Input an integer:");
            scanf("%d", input);
            printf("The integer you input is %d\n", input);
            return 0;
           }
           
          編譯并運行上述代碼,會產生一個嚴重的段錯誤(Segmentation fault)如下:

           # gcc -g crash.c -o crash # ./crash Input an integer:10 Segmentation fault
           
          為了更快速地發現錯誤所在,可以使用GDB進行跟蹤調試,方法如下:

           # gdb crash GNU gdb Red Hat Linux (5.3post-0.20021129.18rh) …… (gdb)
           
          當GDB提示符出現的時候,表明GDB已經做好準備進行調試了,現在可以通過run命令讓程序開始在GDB的監控下運行:

           (gdb) run Starting program: /home/xiaowp/thesis/gcc/code/crash Input an integer:10 Program received signal SIGSEGV, Segmentation fault. 0x4008576b in _IO_vfscanf_internal () from /lib/libc.so.6
           
          仔細分析一下GDB給出的輸出結果不難看出,程序是由于段錯誤而導致異常中止的,說明內存操作出了問題,具體發生問題的地方是在調用_IO_vfscanf_internal ( )的時候。為了得到更加有價值的信息,可以使用GDB提供的回溯跟蹤命令backtrace,執行結果如下:

           (gdb) backtrace #0 0x4008576b in _IO_vfscanf_internal () from /lib/libc.so.6 #1 0xbffff0c0 in ?? () #2 0x4008e0ba in scanf () from /lib/libc.so.6 #3 0x08048393 in main () at crash.c:11 #4 0x40042917 in __libc_start_main () from /lib/libc.so.6
           
          跳過輸出結果中的前面三行,從輸出結果的第四行中不難看出,GDB已經將錯誤定位到crash.c中的第11行了。現在仔細檢查一下:

           (gdb) frame 3 #3 0x08048393 in main () at crash.c:11 11 scanf("%d", input);
           
          使用GDB提供的frame命令可以定位到發生錯誤的代碼段,該命令后面跟著的數值可以在backtrace命令輸出結果中的行首找到。現在已經發現錯誤所在了,應該將

           scanf("%d", input); 改為 scanf("%d", &input);
           
          完成后就可以退出GDB了,命令如下:

           (gdb) quit
           
          GDB的功能遠遠不止如此,它還可以單步跟蹤程序、檢查內存變量和設置斷點等。

          調試時可能會需要用到編譯器產生的中間結果,這時可以使用-save-temps選項,讓GCC將預處理代碼、匯編代碼和目標代碼都作為文件保存起來。如果想檢查生成的代碼是否能夠通過手工調整的辦法來提高執行性能,在編譯過程中生成的中間文件將會很有幫助,具體情況如下:

           # gcc -save-temps foo.c -o foo # ls foo* foo foo.c foo.i foo.s
           
          GCC支持的其它調試選項還包括-p和-pg,它們會將剖析(Profiling)信息加入到最終生成的二進制代碼中。剖析信息對于找出程序的性能瓶頸很有幫助,是協助Linux程序員開發出高性能程序的有力工具。在編譯時加入-p選項會在生成的代碼中加入通用剖析工具(Prof)能夠識別的統計信息,而-pg選項則生成只有GNU剖析工具(Gprof)才能識別的統計信息。

          最后提醒一點,雖然GCC允許在優化的同時加入調試符號信息,但優化后的代碼對于調試本身而言將是一個很大的挑戰。代碼在經過優化之后,在源程序中聲明和使用的變量很可能不再使用,控制流也可能會突然跳轉到意外的地方,循環語句有可能因為循環展開而變得到處都有,所有這些對調試來講都將是一場噩夢。建議在調試的時候最好不使用任何優化選項,只有當程序在最終發行的時候才考慮對其進行優化。

          上次的培訓園地中介紹了GCC的編譯過程、警告提示功能、庫依賴、代碼優化和程序調試六個方面的內容。這期是最后的一部分內容。

          加速

          在將源代碼變成可執行文件的過程中,需要經過許多中間步驟,包含預處理、編譯、匯編和連接。這些過程實際上是由不同的程序負責完成的。大多數情況下GCC可以為Linux程序員完成所有的后臺工作,自動調用相應程序進行處理。

          這樣做有一個很明顯的缺點,就是GCC在處理每一個源文件時,最終都需要生成好幾個臨時文件才能完成相應的工作,從而無形中導致處理速度變慢。例如,GCC在處理一個源文件時,可能需要一個臨時文件來保存預處理的輸出、一個臨時文件來保存編譯器的輸出、一個臨時文件來保存匯編器的輸出,而讀寫這些臨時文件顯然需要耗費一定的時間。當軟件項目變得非常龐大的時候,花費在這上面的代價可能會變得很沉重。

          解決的辦法是,使用Linux提供的一種更加高效的通信方式—管道。它可以用來同時連接兩個程序,其中一個程序的輸出將被直接作為另一個程序的輸入,這樣就可以避免使用臨時文件,但編譯時卻需要消耗更多的內存。

          在編譯過程中使用管道是由GCC的-pipe選項決定的。下面的這條命令就是借助GCC的管道功能來提高編譯速度的:

           # gcc -pipe foo.c -o foo
           
          在編譯小型工程時使用管道,編譯時間上的差異可能還不是很明顯,但在源代碼非常多的大型工程中,差異將變得非常明顯。

          文件擴展名

          在使用GCC的過程中,用戶對一些常用的擴展名一定要熟悉,并知道其含義。為了方便大家學習使用GCC,在此將這些擴展名羅列如下:

          .c C原始程序;

          .C C++原始程序;

          .cc C++原始程序;

          .cxx C++原始程序;

          .m Objective-C原始程序;

          .i 已經過預處理的C原始程序;

          .ii 已經過預處理之C++原始程序;

          .s 組合語言原始程序;

          .S 組合語言原始程序;

          .h 預處理文件(標頭文件);

          .o 目標文件;

          .a 存檔文件。

          GCC常用選項

          GCC作為Linux下C/C++重要的編譯環境,功能強大,編譯選項繁多。為了方便大家日后編譯方便,在此將常用的選項及說明羅列出來如下:

          -c 通知GCC取消鏈接步驟,即編譯源碼并在最后生成目標文件;

          -Dmacro 定義指定的宏,使它能夠通過源碼中的#ifdef進行檢驗;

          -E 不經過編譯預處理程序的輸出而輸送至標準輸出;

          -g3 獲得有關調試程序的詳細信息,它不能與-o選項聯合使用;

          -Idirectory 在包含文件搜索路徑的起點處添加指定目錄;

          -llibrary 提示鏈接程序在創建最終可執行文件時包含指定的庫;

          -O、-O2、-O3 將優化狀態打開,該選項不能與-g選項聯合使用;

          -S 要求編譯程序生成來自源代碼的匯編程序輸出;

          -v 啟動所有警報;

          -Wall 在發生警報時取消編譯操作,即將警報看作是錯誤;

          -Werror 在發生警報時取消編譯操作,即把報警當作是錯誤;

          -w 禁止所有的報警。

          小結

          GCC是在Linux下開發程序時必須掌握的工具之一。本文對GCC做了一個簡要的介紹,主要講述了如何使用GCC編譯程序、產生警告信息、調試程序和加快GCC的編譯速度。對所有希望早日跨入Linux開發者行列的人來說,GCC就是成為一名優秀的Linux程序員的起跑線。

          posted @ 2008-09-19 14:55 小馬歌 閱讀(307) | 評論 (0)編輯 收藏
           
          來源:http://ffmpeg.blogbus.com/logs/578829.html

          2004年4月20日最新版本的GCC編譯器3.4.0發布了。目前,GCC可以用來編譯C/C++、FORTRAN、JAVA、OBJC、ADA等語言的程序,可根據需要選擇安裝支持的語言。GCC 3.4.0比以前版本更好地支持了C++標準。本文以在Redhat Linux上安裝GCC3.4.0為例,介紹了GCC的安裝過程。

            安裝之前,系統中必須要有cc或者gcc等編譯器,并且是可用的,或者用環境變量CC指定系統上的編譯器。如果系統上沒有編譯器,不能安裝源代碼形式的GCC 3.4.0。如果是這種情況,可以在網上找一個與你系統相適應的如RPM等二進制形式的GCC軟件包來安裝使用。本文介紹的是以源代碼形式提供的GCC軟件包的安裝過程,軟件包本身和其安裝過程同樣適用于其它Linux和Unix系統。

            系統上原來的GCC編譯器可能是把gcc等命令文件、庫文件、頭文件等分別存放到系統中的不同目錄下的。與此不同,現在GCC建議我們將一個版本的GCC安裝在一個單獨的目錄下。這樣做的好處是將來不需要它的時候可以方便地刪除整個目錄即可(因為GCC沒有uninstall功能);缺點是在安裝完成后要做一些設置工作才能使編譯器工作正常。在本文中我采用這個方案安裝GCC 3.4.0,并且在安裝完成后,仍然能夠使用原來低版本的GCC編譯器,即一個系統上可以同時存在并使用多個版本的GCC編譯器。

            按照本文提供的步驟和設置選項,即使以前沒有安裝過GCC,也可以在系統上安裝上一個可工作的新版本的GCC編譯器。

            1. 下載

            在GCC網站上(http://gcc.gnu.org/)或者通過網上搜索可以查找到下載資源。目前GCC的最新版本為 3.4.0。可供下載的文件一般有兩種形式:gcc-3.4.0.tar.gz和gcc-3.4.0.tar.bz2,只是壓縮格式不一樣,內容完全一致,下載其中一種即可。

            2. 解壓縮

            根據壓縮格式,選擇下面相應的一種方式解包(以下的“%”表示命令行提示符):

            % tar xzvf gcc-3.4.0.tar.gz
            或者
            % bzcat gcc-3.4.0.tar.bz2 | tar xvf -

            新生成的gcc-3.4.0這個目錄被稱為源目錄,用${srcdir}表示它。以后在出現${srcdir}的地方,應該用真實的路徑來替換它。用pwd命令可以查看當前路徑。

            在${srcdir}/INSTALL目錄下有詳細的GCC安裝說明,可用瀏覽器打開index.html閱讀。

            3. 建立目標目錄
           
            目標目錄(用${objdir}表示)是用來存放編譯結果的地方。GCC建議編譯后的文件不要放在源目錄${srcdir]中(雖然這樣做也可以),最好單獨存放在另外一個目錄中,而且不能是${srcdir}的子目錄。

            例如,可以這樣建立一個叫 gcc-build 的目標目錄(與源目錄${srcdir}是同級目錄):

            % mkdir gcc-build
            % cd gcc-build

            以下的操作主要是在目標目錄 ${objdir} 下進行。

            4. 配置
           
            配置的目的是決定將GCC編譯器安裝到什么地方(${destdir}),支持什么語言以及指定其它一些選項等。其中,${destdir}不能與${objdir}或${srcdir}目錄相同。

            配置是通過執行${srcdir}下的configure來完成的。其命令格式為(記得用你的真實路徑替換${destdir}):

            % ${srcdir}/configure --prefix=${destdir} [其它選項]

            例如,如果想將GCC 3.4.0安裝到/usr/local/gcc-3.4.0目錄下,則${destdir}就表示這個路徑。

            在我的機器上,我是這樣配置的:

            % ../gcc-3.4.0/configure --prefix=/usr/local/gcc-3.4.0 --enable-threads=posix --disable-checking --enable--long-long --host=i386-redhat-linux --with-system-zlib --enable-languages=c,c++,java

            將GCC安裝在/usr/local/gcc-3.4.0目錄下,支持C/C++和JAVA語言,其它選項參見GCC提供的幫助說明。

            5. 編譯

            % make

            這是一個漫長的過程。在我的機器上(P4-1.6),這個過程用了50多分鐘。

            6. 安裝

            執行下面的命令將編譯好的庫文件等拷貝到${destdir}目錄中(根據你設定的路徑,可能需要管理員的權限):

            % make install

            至此,GCC 3.4.0安裝過程就完成了。

            6. 其它設置

            GCC 3.4.0的所有文件,包括命令文件(如gcc、g++)、庫文件等都在${destdir}目錄下分別存放,如命令文件放在bin目錄下、庫文件在lib下、頭文件在include下等。由于命令文件和庫文件所在的目錄還沒有包含在相應的搜索路徑內,所以必須要作適當的設置之后編譯器才能順利地找到并使用它們。

            6.1 gcc、g++、gcj的設置

            要想使用GCC 3.4.0的gcc等命令,簡單的方法就是把它的路徑${destdir}/bin放在環境變量PATH中。我不用這種方式,而是用符號連接的方式實現,這樣做的好處是我仍然可以使用系統上原來的舊版本的GCC編譯器。

            首先,查看原來的gcc所在的路徑:

            % which gcc

            在我的系統上,上述命令顯示:/usr/bin/gcc。因此,原來的gcc命令在/usr/bin目錄下。我們可以把GCC 3.4.0中的gcc、g++、gcj等命令在/usr/bin目錄下分別做一個符號連接:

            % cd /usr/bin
            % ln -s ${destdir}/bin/gcc gcc34
            % ln -s ${destdir}/bin/g++ g++34
            % ln -s ${destdir}/bin/gcj gcj34

            這樣,就可以分別使用gcc34、g++34、gcj34來調用GCC 3.4.0的gcc、g++、gcj完成對C、C++、JAVA程序的編譯了。同時,仍然能夠使用舊版本的GCC編譯器中的gcc、g++等命令。

            6.2 庫路徑的設置

            將${destdir}/lib路徑添加到環境變量LD_LIBRARY_PATH中,最好添加到系統的配置文件中,這樣就不必要每次都設置這個環境變量了。

            例如,如果GCC 3.4.0安裝在/usr/local/gcc-3.4.0目錄下,在RH Linux下可以直接在命令行上執行或者在文件/etc/profile中添加下面一句:

            setenv LD_LIBRARY_PATH /usr/local/gcc-3.4.0/lib:$LD_LIBRARY_PATH

            7. 測試
           
            用新的編譯命令(gcc34、g++34等)編譯你以前的C、C++程序,檢驗新安裝的GCC編譯器是否能正常工作。

            8. 根據需要,可以刪除或者保留${srcdir}和${objdir}目錄。

          posted @ 2008-09-19 14:53 小馬歌 閱讀(288) | 評論 (0)編輯 收藏
           

          dwr.xml中的簽名(Signatures)
          signatures段使DWR能確定集合中存放的數據類型。例如下面的定義中我們無法知道list中存放的是什么類型。

          public class Check
          {
            public void setLotteryResults(List nos)
            {
                ...
            }
          }
          signatures段允許我們暗示DWR應該用什么類型去處理。格式對以了解JDK5的泛型的人來說很容易理解。

          <signatures>
            <![CDATA[
            import java.util.List;
            import com.example.Check;
            Check.setLotteryResults(List<Integer> nos);
            ]]>
          </signatures>
          DWR中又一個解析器專門來做這件事,所以即便你的環境時JDK1.3 DWR也能正常工作。

          解析規則基本上會和你預想規則的一樣(有兩個例外),所以java.lang下面的類型會被默認import。

          第一個是DWR1.0中解析器的bug,某些環境下不能返回正確類型。所以你也不用管它了。

          第二個是這個解析器時"陽光(sunny day)"解析器。就是說它非常寬松,不想編譯器那樣嚴格的保證你一定正確。所以有時它也會允許你丟失import:

          <signatures>
            <![CDATA[
            import java.util.List;
            Check.setLotteryResults(List<Integer>);
            ]]>
          </signatures>
          將來的DWR版本會使用一個更正式的解析器,這個編譯器會基于官方Java定義,所以你最好不要使用太多這個不嚴格的東西。

          signatures段只是用來確定泛型參數中的類型參數。DWR會自己使用反射機制或者運行時類型確定類型,或者假設它是一個String類型。所以:

          不需要signatures - 沒有泛型參數:

          public void method(String p);
          public void method(String[] p);
          需要signatures - DWR不能通過反射確定:

          public void method(List<Date> p);
          public void method(Map<String, WibbleBean> p);
          不需要signatures - DWR能正確的猜出:

          public void method(List<String> p);
          public void method(Map<String, String> p);
          不需要signatures - DWR可以通過運行時類型確定:

          public List<Date> method(String p);
          沒有必要讓Javascript中的所有對象的key都是String類型 - 你可以使用其他類型作為key。但是他們在使用之前會被轉換成String類型。DWR1.x用Javascript的特性把key轉換成String。DWR2.0可能會用toString()方法,在服務段進行這一轉換。


          Comments  (Hide)
          如果對于遠程接口都包含public void method(Map<String, WibbleBean> p);和public void method(Map<String, String> p);的情況該怎么設置?DWR貌似會使用后面的設置,也就是好像不能夠兩種接口都成功設置。

          posted @ 2008-09-17 12:51 小馬歌 閱讀(459) | 評論 (1)編輯 收藏
           

          轉換器
          轉換器在客戶端和服務器之間轉換數據.

          下面這些轉換器有單獨章節介紹

          Array Converter
          Bean and Object Converters
          Collection Converter
          Enum Converter
          DOM Objects
          Hibernate整合
          Servlet Objects (HttpServletRequest, HttpSession, etc)
          基礎的轉換器
          原生類型,String,像BigDecimal這樣的簡單對象的轉換器已經有了。你不需要在dwr.xml中<allow>部分的<convert>中定義。它們默認支持。

          默認支持的類型包括: boolean, byte, short, int, long, float, double, char, java.lang.Boolean, java.lang.Byte, java.lang.Short, java.lang.Integer, java.lang.Long, java.lang.Float, java.lang.Double, java.lang.Character, java.math.BigInteger, java.math.BigDecimal 和 java.lang.String

          Date轉換器
          Date轉換器負責在Javascript的Date類型與Java中的Date類型(java.util.Date, java.sql.Date, java.sql.Times or java.sql.Timestamp)之間進行轉換。同基礎的轉換器一樣,DateConverter默認是支持的。

          如果你有一個Javascript的字符串 (例如"01 Jan 2010") ,你想把它轉換成Java的Date類型有兩個辦法:在javascript中用Date.parse()把它解析成Date類型,然后用DWR的DateConverter傳遞給服務器;或者把它作為字符串傳遞給Server,再用Java中的SimpleDateFormat(或者類似的)來解析。

          同樣,如果你有個Java的Date類型并且希望在HTML使用它。你可以先用SimpleDateFormat把它轉換成字符串再使用。也可以直接傳Date給Javascript,然后用Javascript格式化。第一種方式簡單一些,盡管浪費了你的轉換器,而且這樣做也會是瀏覽器上的顯示邏輯受到限制。其實后面的方法更好,也有一些工具可以幫你,例如:

          The Javascript Toolbox Date formatter
          Web Developers Notes on Date formatting
          其他對象
          其實創建自己的轉換器也很簡單。Converter接口的Javadoc包含了信息。其實這種需要很少出現。在你寫自己的Converter之前先看看BeanConverter,它有可能就是你要的。

          posted @ 2008-09-17 12:50 小馬歌 閱讀(170) | 評論 (0)編輯 收藏
           

          Bean 和 Object 轉換器
          兩個沒有默認打開的轉換器是Bean 和 Object 轉換器。Bean轉換器可以把POJO轉換成Javascript的接合數組(類似與Java中的Map),或者反向轉換。這個轉換器默認情況下是沒打開的,因為DWR要獲得你的允許才能動你的代碼。

          Object轉換器很相似,不同的是它直接應用于對象的成員,而不是通過getter和setter方法。下面的例子都是可以用object來替換bean的來直接訪問對象成員。

          如果你有一個在 <create ...> 中聲明的遠程調用Bean。它有個一參數也是一個bean,并且這個bean有一個setter存在一些安全隱患,那么攻擊者就可能利用這一點。

          你可以為某一個單獨的類打開轉換器:

          <convert converter="bean" match="your.full.package.BeanName"/>
          如果要允許轉換一個包或者子包下面的所有類,可以這樣寫:

          <convert converter="bean" match="your.full.package.*"/>
          顯而易見,這樣寫是允許轉換所有的JavaBean:

          <convert converter="bean" match="*"/>
          BeanConverter 和 JavaBeans 規范
          用于被BeanConverter轉換的Bean必須符合JavaBeans的規范,因為轉換器用的是Introspection,而不是Reflection。這就是說屬性要符合一下條件:有getter和setter,setter有一個參數,并且這個參數的類型是getter的返回類型。setter應該返回void,getter應該沒有任何參數。setter沒有重載。以上這些屬于常識。如果你用的不是JavaBean,那么你應該用ObjectConverter.

          設置Javascript變量
          DWR可以把Javascript對象(又名maps,或聯合數組)轉換成JavaBean或者Java對象。

          一個簡單的例子可以幫助你。假設你有下面的Java代碼:

          public class Remoted {
            public void setPerson(Person p) {
              // ...
            }
          }

          public class Person {
            public void setName(String name) { ... }
            public void setAge(int age) { ... }
            // ...
          }
          如果這個Remoted已經被配置成Creator了,Persion類也定義了BeanConverter,那么你可以通過下面的方式調用Java代碼:

          var p = { name:"Fred", age:21 };
          Remoted.setPerson(p);
          限制屬性轉換
          就像你可以在creator的定義中剔出一些方法一樣,converter也有類似的定義。

          限制屬性轉換僅僅對于Bean有意義,很明顯原生類型是不要需要這個功能的,所以只有BeanConverter及其子類型(HibernateBeanConverter))有這個功能。

          語法是這樣的:

          <convert converter="bean" match="com.example.Fred">
            <param name="exclude" value="property1, property2"/>
          </convert>
          這就保證了DWR不會調用 fred.getProperty1() 和fred.getProperty2兩個方法。另外如果你喜歡"白名單"而不是"黑名單"的話:

          <convert converter="bean" match="com.example.Fred">
            <param name="include" value="property1, property2"/>
          </convert>
          安全上比較好的設計是使用"白名單"而不是"黑名單"。

          對象的私有成員
          通過'object'轉換器的參數的一個名為force的參數,可以讓DWR通過反射來訪問對象私有成員。

          語法是這樣的:

          <convert converter="object" match="com.example.Fred">
            <param name="force" value="true"/>
          </convert>
          直到DWR1.1.3,這里有一個bug,public的field反而不能被發現,所以你需要在public成員上設置force=true。

          posted @ 2008-09-17 12:47 小馬歌 閱讀(864) | 評論 (0)編輯 收藏
           
          util.js包含一些有用的函數function,用于在客戶端頁面調用.

          主要功能如下:

          代碼

          1、$() 獲得頁面參數值   
          2、addOptions and removeAllOptions 初始化下拉框   
          3、addRows and removeAllRows   填充表格   
          4、getText   取得text屬性值   
          5、getValue 取得form表單值   
          6、getValues 取得form多個值   
          7、onReturn     
          8、selectRange   
          9、setValue   
          10、setValues   
          11、toDescriptiveString   
          12、useLoadingMessage   
          13、Submission box  

          代碼

          1、$()函數   
             IE5.0 不支持   
             $ = document.getElementById   
             取得form表單值   
             var name = $("name");  

          代碼

          a、如果你想在更新select 時,想保存原來的數據,即在原來的select中添加新的option:   
               var sel = DWRUtil.getValue(id);   
               DWRUtil.removeAllOptions(id);   
               DWRUtil.addOptions(id,...);   
               DWRUtil.setValue(id,sel);   
               demo:比如你想添加一個option:“--請選擇--”   
          DWRUtil.addOptions(id,["--請選擇--"]);   
            
              DWRUtil.addOptions()有5中方式:  

          代碼

          @ Simple Array Example: 簡單數組   
               例如:   
               Array array = new Array[ 'Africa', 'America', 'Asia', 'Australasia', 'Europe' ];   
               DWRUtil.addOptions("demo1",array);  

          代碼

          @ Simple Object Array Example 簡單數組,元素為beans   
                 這種情況下,你需要指定要顯示 beans 的 property 以及 對應的 bean 值   
                 例如:   
                  public class Person {   
                private String name;   
                private Integer id;   
                pirvate String address;   
                public void set(){……}   
                public String get(){……}   
                  }   
                  DWRUtil.addOptions("demo2",array,'id','name');   
                  其中id指向及bean的id屬性,在optiong中對應value,name指向bean的name屬性,對應下拉框中顯示的哪個值.  

          代碼

          @ Advanced Object Array Example 基本同上   
               DWRUtil.addOptions( "demo3",   
                           [{ name:'Africa', id:'AF' },   
                            { name:'America', id:'AM' },   
                            { name:'Asia', id:'AS' },   
                            { name:'Australasia', id:'AU' },   
                            { name:'Europe', id:'EU' }   
                   ],'id','name');  

          代碼

          @ Map Example 用制定的map來填充 options:   
                  如果 server 返回 Map,呢么這樣處理即可:   
                  DWRUtil.addOptions( "demo3",map);   
                  其中 value 對應 map keys,text 對應 map values;  

          代碼

          @ <ul> and <ol> list editing   
                  
                  DWRUtil.addOptions() 函數不但可以填出select,開可以填出<ul>和<ol>這樣的heml元素  

          3、addRows and removeAllRows 填充表格

          DWR 提供2個函數來操作 table;

          ----------------------------

          DWRUtil.addRows(); 添加行

          ----------------------------

          DWRUtil.removeAllRows(id); 刪除指定id的table

          ----------------------------

          下面著重看一下 addRows() 函數:

          DWRUtil.addRows(id, array, cellfuncs, [options]);

          其中id 對應 table 的 id(更適合tbodye,推薦使用 tbodye)

          array 是server端服務器的返回值,比如list,map等等

          cellfuncs 及用返回值來天春表格

          [options] 用來設置表格樣式,它有2個內部函數來設置單元格樣式(rowCreator、cellCreator)。

          比如: server端返回list,而list中存放的是下面這個 bean:

          代碼

                 public class Person {   
          private String name;   
          private Integer id;   
          pirvate String address;   
          public void set(){……}   
          public String get(){……}   
                }  

          下面用 DWRUtil.addRows();

          代碼

              function userList(data){   
               //var delButton = "<input type='button'/>";   
               //var editButton = "<input type='button'/>";   
               var cellfuncs = [   
                   function(data){return data.id;},   
                   function(data){return data.userName;},   
                   function(data){return data.userTrueName;},   
                   function(data){return data.birthday;},   
                   function(data){   
                   var idd = data.id;   
          var delButton = document.createElement("<INPUT TYPE='button' onclick='delPerson("+ idd +")'>");   
                       delButton.setAttribute("id","delete");   
                       delButton.setAttribute("value","delete");   
                       return delButton;   
                   },   
                   function(data){   
                       var idd = data.id;   
                       var editButton = document.createElement("<INPUT TYPE='button' onclick='editPerson("+ idd +")'>");   
                       editButton.setAttribute("name","edit");   
                       editButton.setAttribute("value","edit");               
                       return editButton;   
                   }   
               ];   
               DWRUtil.removeAllRows('tabId');   
               DWRUtil.addRows('tabId', data,cellfuncs,{   
               rowCreator:function(options) {   
                   var row = document.createElement("tr");   
                   var index = options.rowIndex * 50;   
                   row.setAttribute("id",options.rowData.id);   
                   row.style.collapse = "separate";   
                   row.style.color = "rgb(" + index + ",0,0)";   
                   return row;   
               },   
               cellCreator:function(options) {   
                   var td = document.createElement("td");   
                   var index = 255 - (options.rowIndex * 50);   
                   //td.style.backgroundColor = "rgb(" + index + ",255,255)";   
                   td.style.backgroundColor = "menu";   
                   td.style.fontWeight = "bold";   
                   td.style.align = "center";   
                   return td;   
               }          
               });   
               document.getElementById("bt").style.display = "none";   
                }  

          4、getText 取得text屬性值

          DWRUtil.getText(id): 用來獲得 option 中的文本

          比如:

          代碼

                 <select id="select">  
          <option   value="1"> 蘋果 </option>  
          <option   value="2" select> 香蕉 </option>  
          <option   value="3"> 鴨梨 </option>  
                 </select>  

          調用 DWRUtil.getText("select"); 將返回 "香蕉" 字段;

          DWRUtil.getText(id);僅僅是用來獲得 select 文本值,其他不適用。

          5、DWRUtil.getValue(id): 用來獲得 form 表單值

          有如下幾種情況:

          代碼

                Text area (id="textarea"): DWRUtil.getValue("textarea")將返回 Text area的值;   
          Selection list (id="select"): DWRUtil.getValue("select") 將返回 Selection list 的值;   
          Text input (id="text"): DWRUtil.getValue("text") 將返回 Text input 的值;   
          Password input (id="password"): DWRUtil.getValue("text") 將返回 Password input 的值;   
          Form button (id="formbutton"): DWRUtil.getValue("formbutton") 將返回 Form button 的值;   
          Fancy button (id="button"): DWRUtil.getValue("formbutton") 將返回 Fancy button 的值;  

          6、getValues 取得form多個值

          批量獲得頁面表單的值,組合成數組的形式,返回 name/value;

          例如: form():

          代碼

               <input type="textarea" id="textarea" value="1111"/>  
                <input type="text" id="text" value="2222"/>  
                <input type="password" id= "password" value="3333"/>  
                <select id="select">  
          <option   value="1"> 蘋果 </option>  
          <option   value="4444" select> 香蕉 </option>  
          <option   value="3"> 鴨梨 </option>  
                 </select>  
                <input type="button" id="button" value="5555"/>  
                  
                那么: DWRUtil.getValues({textarea:null,select:null,text:null,password:null,button:null})   
                將返回   ^^^^^^^^^^^^^^^^{textarea:1111,select:4444,text:2222,password:3333,button:5555}  

          7、DWRUtil.onReturn 防止當在文本框中輸入后,直接按回車就提交表單。

          <input type="text" onkeypress="DWRUtil.onReturn(event, submitFunction)"/>

          <input type="button" onclick="submitFunction()"/>

          8、DWRUtil.selectRange(ele, start, end);

          在一個input box里選一個范圍

          代碼

          DWRUtil.selectRange("sel-test", $("start").value, $("end").value);   
            
          比如:<input type="text" id="sel-test" value="012345678901234567890">   
            
          DWRUtil.selectRange("sel-test", 2, 15);  

          9、DWRUtil.setValue(id,value);

          為指定的id元素,設置一個新值;

          10、DWRUtil.setValues({

          name: "fzfx88",

          password: "1234567890"

          }

          ); 同上,批量更新表單值.

          /***********************************************************************/

          11、DWRUtil.toDescriptiveString()

          帶debug信息的toString,第一個為將要debug的對象,第二個參數為處理等級。等級如下:

          0: Single line of debug 單行調試

          1: Multi-line debug that does not dig into child objects 不分析子元素的多行調試

          2: Multi-line debug that digs into the 2nd layer of child objects 最多分析到第二層子元素的多行調試

          <input type="text" id="text">

          DWRUtil。toDescriptiveString("text",0);

          /******************************************************************************/

          12、DWRUtil.useLoadingMessage();

          當發出ajax請求后,頁面顯示的提示等待信息;

          代碼

              function searchUser(){   
          var loadinfo = "loading....."  
          try{   
               regUser.queryAllUser(userList);   
               DWRUtil.useLoadingMessage(loadinfo);           
          }catch(e){   
            
          }   
              }
          posted @ 2008-09-17 12:45 小馬歌 閱讀(857) | 評論 (0)編輯 收藏
          僅列出標題
          共95頁: First 上一頁 80 81 82 83 84 85 86 87 88 下一頁 Last 
           
          主站蜘蛛池模板: 南投县| 芜湖县| 延津县| 屯留县| 庄河市| 井陉县| 会同县| 缙云县| 大邑县| 社会| 阿拉善盟| 六盘水市| 凤山市| 濉溪县| 石狮市| 聊城市| 江津市| 庆云县| 大安市| 二连浩特市| 宜都市| 宾阳县| 电白县| 石城县| 苏尼特左旗| 温宿县| 宜君县| 南雄市| 富裕县| SHOW| 北碚区| 镶黄旗| 扶绥县| 新化县| 卓尼县| 集贤县| 天长市| 丹棱县| 关岭| 光泽县| 广丰县|