|
#include <stdio.h> int main() { ?char c1,c2,c3,c4,c5; ?c1='C';c2='h';c3='i';c4='n';c5='a'; ?c1+=4; c2+=4; c3+=4; c4+=4; c5+=4; ?printf("%c%c%c%c%c",c1,c2,c3,c4,c5); ?return 0; }
原來是China,譯完后是Glmre
這是一個用C寫的解二階行列式的程序,減少了自己手動寫的麻煩。決定將《線性代數》中的一些算法都以程序的方式寫出來,既提高程序水平,也加強對線性代數的了解,同時也加深對這門課的興趣。
#include <stdio.h> int main() { ?int a1,a2,a3,a4,a5,a6; ?int m,n,p; ?printf("求解方程組-二階行列式/n"); ?printf("請輸入第一個方程式的前三個元素,中間以空格分開:/n"); ?scanf("%d%d%d",&a1,&a2,&a3); ?printf("請輸入第二個方程式的前三個元素,中間以空格分開:/n"); ?scanf("%d%d%d",&a4,&a5,&a6); ?m=a1*a5-a2*a4; ?n=a3*a5-a2*a6; ?p=a1*a6-a3*a4; ?printf("分析數據:/n"); ?printf("D=%d/n",m); ?printf("D1=%d/n",n); ?printf("D2=%d/n",p); ?printf("最終結果:/n"); ?printf("x1=%d/n",n/m); ?printf("x2=%d/n",p/m); ?return 0; }
摘要: 1990年,山東省某市中學生齊某參加中專考試,被一學校錄取為90級財會專業委培生。但是,齊所在的中學既未將考試成績告知齊,也未將錄取通知書送給齊本人,而是送給了與齊同一屆的另一名學生陳某。陳即以齊的名義讀完中專,被分配到金融單位工作,其人事檔案中也一直使用齊某的姓名。 此事被掩蓋多年后終于東窗事發。1999年1月29日,齊某以陳某和她的父親以及原所在學校等數家單位侵害其姓名權和受教育權為由訴至法院,請求責令被告停止侵害、賠禮道歉并賠償經濟損失16萬元和精神損失40萬元。 這起看似簡單的民事案件,卻給司法機關提出了一個“棘手”的問題— ——侵犯姓名權問題在民法通則中有詳細的規定,無須贅述;侵害受教育權卻在民法中沒有規定——換句話說,受教育權屬于公民的憲法權利,而不是民事權利。但是,我國各級審判機關在審理具體案件時,慣例是不能直接引用憲法。因此,在一般情況下,這一訴求可能會以“沒有法律依據為由”而不予受理。 據此,最高人民法院根據山東省高級人民法院的請示,于2001年8月13日作出[2001]法釋25號《關于以侵犯姓名權的手段侵害憲法保護的公民受教育的基本權利是否應當承擔民事責任的批復》,指出“陳××侵犯姓名權的手段,侵犯了齊××依據憲法所享有的公民受教育的基本權利,并造成了具體損害,應承擔相應的民事責任”。 最高司法機關對公民因憲法規定享有的基本權利受到侵害而產生糾紛的法律適用問題進行司法解釋,這在我國尚屬首例。 關鍵字:憲法司法化,憲法 正文: 本案屬公民提起的受教育權訴訟,在現有的《民法通則》中雖然對公民的姓名權有規定,但對公民的受教育權卻僅在我國的憲法的第40條規定公民有受教育的權利和義務,而在《民法通則》及其它部門法當中并沒有具體的體現。這就涉及到憲法是否可以像其它法律法規一樣,直接進入司法程序加以引用并做出相應的裁判,也即所謂的“憲法司法化”。此案倍受各界關注,最高人民法院于就本案所作的《批復》中稱:“根據本案事實,陳曉琪等以侵犯姓名權的手段,侵犯了齊玉玲依據憲法規定所享有的受教育的基本權利,并造成了具體的損害后果,應承擔相應的民事責任”。隨后,有關法院根據上述批復做出了判決,此案即被稱之為“憲法第一案”。 在該案件之前,因為憲法所具有的抽象性,一直不作為各項訴訟所直接參考的依據。但是,在一些基本法律沒有體現到的地方,憲法卻給出我們基本的精神。也就是從這個案子開始,在一些訴訟上面憲法也正式被司法化,成為司法中依據的條例。 一、憲法司法化內涵及其發展首先,憲法司化這一話語在純理論意義上具有兩個維度:一是當沒有具體法律將公民的憲法基本權利落實時,司法機關能否直接適用或引用憲法條文作為判決依據?在這種意義上,憲法司化法意味著憲法司法化適用性。這個命題建立在公民基本權利之充分保障的憲政理論之上,即認為憲法是公民權利的保障書,如果憲法權利沒有得到具體法律落實,司法機關又不適用,憲法條文作為判決依據,無疑權利保障成為一紙空文。因此,憲法的司法適用有最后屏障之功效。憲法司法化的第二層面是:在司法機關對個案審理過程中,能否對有違憲疑義的法律規范的合憲性問題進行審查并作出判斷。這涉及到司法機關是否有違憲審查權問題。這無疑已經不是一個技術性命題,它涉及到一個國家的憲政理論和政治制度的基本構架,甚至包括歷史傳統和文化觀念等層面。雖然司法審查這種制度在現代受到廣泛的推崇,但它本來并非一條不證身明的公理。實際上,司法審查制度始終受到本身兩個方面的嚴峻挑戰:因此憲法司法化內涵主要是指憲法可以像其它法律一樣嚴格地進入司法程序,作為裁判案件的法律依據,并依照憲法進行司法審查的一種制度。 二、我國憲法實施中存在的問題憲法是國家的根本大法,具有最高的法律效力,從1949年9月制定起臨時憲法作用的《共同綱領》至今,我國憲法制度的發展已走過了風風雨雨50年,有些人認為,我國憲法被視為“閑法”,人民法院判案不得引用憲法條文;人們意識中也有“寧可違憲,不可違法”的思想,在民意調查中,公民認為與切身有最大利益關系的法律是民法、刑法等等,根本找不到憲法的影子。還有些人認為,憲法是“鏡中花,霧中月”好看不中用,但隨著人們權利意識和法治觀念的日益增強,將憲法請下“神壇”,使其真正發揮根本大法的實際效力,在概嘆人們憲法意識談謨的同時,應該對我國的憲法制度及司法實踐進行反思。第一,憲法的頻繁變更削弱了憲法的穩定性和權威性。從1949年9月至今,我國先后制定了一部臨時憲法(即《共同綱領》)、四部憲法,并頒布了三次憲法修正案,無論是全身手術還是局部手術,所修改及確定的內容皆是當時歷史階段黨要完成的任務及實現的目標的政策。修憲就是將黨的政策法律化的過程。憲法的頻繁變更和修改,嚴重削弱了憲法的穩定性和權威性,這似乎不能完全歸咎于立憲者的短視,癥結所在是憲法在我國政治生活中所扮演的角色在一定程度上是給執政黨的政策披上一層法律的外衣,使其上升為國家的意志,人民的意志,因為“黨是代表廣大人民群眾的根本利益”。“法律必須是穩定的,但不可一成不變”。在我國每一次黨的代表大會召開伴隨著一個時期內方針政策的修改又帶來了一次憲法的修改,憲法經常性地被其政策而溫柔地改變,那么,就意味著整個國家的權力秩序已納入到憲法的管轄之內,“政治權力的憲法化”就很難充分地得以實現,憲政秩序也就失去了必要的基礎。這也就難怪人們對憲法的認識還不如看一下黨的紅頭文件來的容易,也難免讓人心悅誠服地維護憲法的權威性呢?第二,“法治”與“人大至上性”的矛盾,使憲法司法化在現行體制上不能完全行使。——所謂“人大至上”就是說,人民代表大會具有至高無上的權力或者說具有“決定一切職權的職權”;法治的最低標準就是保持國家法律在憲法框架內的統一,就是保證法律的合憲性,就是所謂的“治法”,如果法治排除了“治法”的硬核,那么法治的最終含義的就是在于“治人”了;一旦統治者打著法治的旗號而行“治人”之時,人也就變成了奴隸,“法治”也就走向了它的反面。在我國,一方面全國人大有立法權,可以制定他“認為合適”的法律;另一方面全國人大有權對憲法進行修改,全國人大常委會有權解釋憲法。假如有人指控全國人大立法有違憲之嫌,全國人大常委會就可能通過解釋憲法而不是修改法律來“自圓其說”,以保證“憲法”的合法性,而不是法律的合憲性;即使不能“自圓其說”,“人大”還可使用"修憲"的殺手锏來保證其所制定的法律“合憲性”。在這種體制下,除非“人大”自覺的進行其立法的合憲性監督,否則,法律違憲問題是斷然不可能存在的。第三,憲法的不直接適用性削弱了憲法的權威性。造成這一現象的原因是多方面的。首先,憲法本身具有高度的抽象性。憲法規定的是國家的根本制度和根本任務,是對國家政治結構、經濟制度,社會制度以及公民的基本權利與義務的規定。憲法規范具有原則性、概括性,其假定、處理、制裁三個方面的區分并不完全,造成其懲罰性,制裁性不強,因此,憲法規范本身缺乏可訴性和可操作性。在司法實踐中,法院一般將依據憲法制定的普遍法律作為法律適用的依據,而不將憲法直接引入訴訟程序。其次,人們對憲法認識的觀念問題。長期以來,人們對憲法性質主要著眼于政治性。往往和國家的大政方針聯系在一起,因而很久以來,我們一直沒有樹立憲法為法的觀念,讓根本大法降格去解決刑事、民事等小問題在絕大多數人看來實在是荒唐之舉。最后,最高人民法院以往的司法解釋捆住了自己的手腳。其一是1955年ii最高人民法院在給新疆自治區高級人民法院的批復中認為,在刑事訴訟中不宜援引憲法作定罪科刑的依據。其二是1986年最高人民法院在給江蘇省高院的批復中對是否引用憲法條文進行裁判采取了回避的態度。第四,違憲事件經常發生削弱了憲法的根本大法地位,影響了社會穩定。《憲法》第五條規定:“一切國家機關……都必須遵守憲法和法律。一切違反憲法和法律的行為,必須予以追究。”“任何組織或者個人都不得有超越憲法和法律的特權。”這是對國家機關依法行使職權原則的具體規定。國家機關違憲情況大致包括以下幾種:首先,不履行憲法職責,職務行為違反法律規定。包括違反實體法和程序法的規定,如濫用權力等。其次,職務行為沒有法律根據,認定這類行為違法,是法治原則的必然要求,這類行為若給相對人一方科以義務使其因此而遭受了損失,國家要負賠償責任。最后,國家制定的法規,規章等抽象行政行為違憲,引起社會廣泛關注的湖北青年孫志剛在廣州收容致死一案讓人反思,1991年國務院發出48號文將收容對象擴大到“無合法證件,無固定住所,無穩定經濟來源”的三無人員,而在執行中,“三無”往往變成無身份證、暫住證、務工證,“三證”缺一不可。也就是說最初制度設計上,收容制度是一種救濟制度,但在特定歷史條件下,它演變成了一項限制公民基本憲法權利的制度。《中華人民共和國立法法》第8條規定:對公民政治權利剝奪、限制人身自己的強制措施和處罰只能制定法律。《中華人民共和國憲法》第37條規定中華人民共和國公民的人身自由不受侵犯……禁止非法拘禁和以其他方法非法剝奪或者限制公民的人身自由。可見,《收容遣送辦法》與《立法法》相矛盾,同時也違背了憲法。 三、憲法司法化不同類型之比較世界上現存的憲法司法化大致分為兩種類型——美國模式與歐陸模式;表現在審查主體上是分權的與集權的,表現在審查時機上是事后審查的與預防審查的,表現在審查方法上是附帶審查的與主要審查的,表現在審查結果上是個案效力與普及效力的等等①。以下分別對兩大類型的主要構成進行簡單比較、說明和分析。 美國模式承認各級法院都有權進行合憲性審查。但這種審查只針對已經生效的法律,只能在處理各類普通訴訟案件的程序當中采取所謂的“附帶審查”(即憲法問題只能作為具體爭議內容的一部分而不能作為主要爭議提出來)的方式。法院僅僅解決具體的問題而不作抽象性判斷,因此審查結果的效力只限于本案當事人。這樣做的目的是要盡量避免由法官來制定法律的事態。當然,遵循先例原則使判決的效力有機會涉及其他同類案件,實際上合憲性審查的結果還是有普遍性的,法律的安定也不會因而遭到破壞。然而,這種普及效力在形式上還是仍然局限于具體案件的當事人之間的具體爭議。 與美國模式不同,在歐洲大陸法系各國中,合憲性審查職能被限定在單一司法性機關如憲法法院,憲法評議委員會等集中履行,普遍的各級法院以及最高法院則無權過問。憲法訴訟在多數場合是由國家機關(包括政府部門,國會議員以及受理具體訴訟案件的普通法院)。按照特別程序來提起,因此合憲性審查與具體訴訟案件的審理是分別進行的。以合憲性審查的政治性為前提,憲法法院的構成以及人事選任方法都必須反映政治勢力的分布狀況,審查內容也往往包括政治問題和統治行為。另外,尚未生效的法律,條約也被列入審查范圍之內。二戰后,英美和歐陸這兩種不同的模式在司法審查制度出現了趨同化的發展①。其中最典型實例是屬于大陸法系的日本。根據1947年憲法第81條的規定,日本導入了美式附帶合憲性審查制度。盡管如此,由于社會和制度的背景不同,日本的實際做法最終表現出明顯的特色,例如分權化的合憲性審查到1975年就名存實亡,最高法院實際上一直發揮憲法法院的作用,但卻沒有采取抽象性審查的方式,而是通過具體訴訟案件的判決進行部門問題的審查,另外,審查的重點從立法轉移到防止行政權力對人僅的侵犯方面,在整體上的傾向于司法消級主義等等,似乎介于美國模式和戰后德國模式之間。 四、我國憲法司法化的重要意義及其適用范圍 首先,憲法司法化有助于保障人權。現實中,憲法規定的公民所享有的基本權利往往因為缺乏普通法律、法規的具體化,量化而長期處于休眠狀態,無法得到實現。由于憲法具有高度的原則性和概括性,一般能夠適應社會關系不斷發展變化的要求,因此憲法司法化能夠彌補普通法律、法規的缺陷和漏洞,使憲法規范從靜態走向動態,將憲法規定權利落到實處。其次,憲法司法化有助于實現法治。憲法規定了國家政治生活和社會生活等具有全局意義的問題,在整個法律體系中處于母法地位,具有最高法律效力和權威。因此,實現法治、依法治國首先是依憲治國,樹立法律權威首先是樹立憲法的權威。而依憲治國樹立憲法權威不能停留在紙面上,對于違憲事件和違憲爭議,憲法不應沉默,而應將其納入司法軌道。最后,憲法司法化有助于推動憲政。長期以來,現實生活中違憲現象可司空見慣,而由于我國長期以來形成的憲法不能作裁判依據的司法慣例與思維定勢,有關國家機關對此只能束手無策,如果實行憲法司法化,那么就能激活紙面上的憲法,在司法過程中凸顯憲法的最高法律效力和權威,使憲法確定的公民的基本權利再無具體法律法規規定或規定不明確的情況下變成現實,對國家機關、組織或者個人的違憲行為進行有效的追究與糾正。只有這樣,徒具口號意義的憲政才能轉變為活生生的現實。 雖然,憲法司法化在我國的實現有著重大意義,但是適用范圍是有限的。如果不對憲法司法化的范圍,進行合理架構,那么會導致憲法的濫訴現象,果真如此的話憲法的根本大法就會降格。我國必須堅持普通的民事審判的私法領域不能直接適用憲法權利條款。憲法權利僅直接適用在公法領域中的,適用在反映公民與國家權力關系的領域中。“憲法中的權利條款僅僅保護其不受“國家行為”的侵犯,而將私人對他人的權利的侵犯留給侵權法”①。刑事審判程序是確定公民是否犯罪以及對犯罪行為人處以何種刑罰的程序,即定罪量刑的程序,刑事審判所處理的案件在性質上屬于公法領域案件,但是,由于刑事審判屬于定罪量刑性質,依據罪行法定原則,應當直接適用刑法規范,而不宜直接以憲法規范作為其法律依據。在非刑事審判的其他公法領域,也并非都需要直接適用,如果立法符合憲法立法體現了權利的價值,通過立法構建的法律秩序促進了基本權利的實現,或者說一般法律權利已是基本權利的具體化則可以直接適用一般法律權利。在執法尤其行政執法領域,基本權利受到公權力的侵害,可以直接適用一般權利規范進行救濟,在窮盡這種救濟之后,再適用憲法權利規范進行救濟。 五、對我國憲法司法化的設想 隨著我國法治化的進程,擴大公民憲法權利的適用性也是當務之急。鑒于中國法治環境不斷改善。在現階段實現憲法司法化的條件已經日趨成熟。 首先,必須改變對憲法的認識觀念,憲法不是“神法”,也不是“閑法”。憲法是我國的根本大法,憲法的主要任務在于規定國家機構的設置,權限運作以及公民的基本權利。為了維護憲法的穩定性和權威性,對于憲法中要規定的基本國策要有條件限制,只有那些帶有根本性的國家理念和基本國策,才有必要在憲法中作出規定。 其次,司法審查制度可以分幾步走。第一步,在現行體制下,全國人大常務委員會應當切實履行憲法賦予的職責,維護憲法的權威及尊嚴,我國是社會主義國家,全國人民代表大會是我國最高權力機關,由于全國人民代表大會由省、自治區、直轄市和軍隊選出的代表組成,每年舉行一次會議,所以本人認為應當強化它的常設機關全國人民代表大會常務員會職權。委員應當具備較高的政治、經濟、法律素質,委員應當實行全職化、年輕化。修改《全國人民代表大會常務委員會議事規則》這樣才能履行憲法67條規定:解釋憲法,監督憲法的實施;撤銷國務院制定的同憲法法律相抵觸的行政法規、決定和命令。憲法37條規定:中華人民共和國公民的人身自由不受侵犯。孫志剛案的發生使人們不得不思考憲法的權威。公民要求司法救濟權利的呼聲越來越高。而現在公民只能對具體行政行為違法提起行政訴訟,對于抽象行政行為違法問題還是解決不了。收容制度審查就是一個抽象行政行為。人們迫切希望憲法司法化時代到來,如果沒有一個最終的司法救濟渠道,憲法賦予公民合法的人身權利及其它權利就得不到根本的保障。 第二步借鑒美國模式和歐陸模式,根據我國現今社會制度、歷史文化傳統及法官素質不高等客觀原因,在重新修改《中華人民共和國憲法》、《中華人民共和國立法法》、《中華人民共和國行政訴訟法》、《最高人民法院組織法》的基礎上,實行分級違憲審查的制度模式。即在人大常委會內設立憲政委員會,在最高人民法院、省級人民法院內設憲法審判庭,憲政委員會由13名知名法律家和政治家組成,最高法院審判庭由9名憲法大法官組成,這些組成人員由國家主席在與全國人大常委會委員長,最高人民法院院長協商提名,由人國人大選舉產生,對憲法和人大負責。由憲政委會員重點審查法律、法規的合憲性以及國家領導人的違憲訴訟案,憲政委員會履行憲法第六十七條第一款、第七款、第八款職權,法律在提交全國人民代表大會及常務委員會表決之前,憲政委員會可以提前預防審查。《收容遣送辦法》行政法規違背憲法時,憲政委員會可直接撤銷上述辦法,也可以提請啟動特別調查程序、組織特定的問題調查委員會議案,調查委員會由全國人大代表擔任,調查委員會可聘請專家參加調查工作,調查委員會的組成要遵循回避原則。對有關國家機關調查處理孫志剛案的情況,可以在司法機關依照法定程序辦結以后,調取案卷審查,發現疑點時召開聽證會,聽取有關國家機關匯報,并進行必要的詢問和質詢,在調查過程中,調查委員會視具體情況決定是否公布調查情況和材料,在調查過程以后,調查委員會應向全國人大常委會報告調查結果,并向全國人民公開。由憲法審判庭重點審查規章及其他規范性文件的合憲性,以及侵犯公民憲法權利的案件,但要明確兩種審查機關之間以及與權力機關之間的關系。還應合理劃分最高法院與憲政委員會的管轄權限,并建立相應憲法訴訟制度。 第三步,在具備條件和重新修憲的基礎上,設立憲法委員會或者憲法法院,統一行使原來由全國人大享有改變或撤銷常務委員會的決定和權限,由全國人大常委會享有的撤銷行政法規和地方法規以及進行憲法解釋和立法解釋權限,憲法委員會由13名或15名資深望重的法學家和政治家作為委員組成,憲法委員會對我國的憲法負責。與此同時,通過司法改革進一步落實審判獨立原則,以提高職業法官群體的社會威性減少法官的數量,提高法官素質。憲法委員會大法官們按照憲法規定的權限和程序對一切已經生效的基本法律、決議、行政法規、地方法規自治條例,單行條例,司法解釋以及各種規章進行抽象性審查并能夠直接否定違憲規范效力,也可以批準并宣告已經生效法院判決的撤銷。除有關國家機關外,任何公民也都可以由普通法院向憲法委員會或憲法法院提起憲法訴訟,在特殊情況下憲法委員會或憲法法院還可以直接受理已經窮盡一切普通司法救濟手段的個人的控訴或申訴。 最后,隨著國際交往的日益頻繁,我國已加入了WTO,我國每年在國際上締結的各種類型條約有二三百個,我們不能排除會出現國務院締結的條約與全國人大常委會制定的法律相沖突,從國家主權原則出發,從憲法是國家根本法的要求出發,憲法應當對條約的適用問題作出規定,對抽象行政行為也要具有司法審查權。 主要參考書目: (1) 湯維建:《美國民事司法制度與民事訴訟程序》。 (2) 張志銘:《也談憲法的司法化》。 (3) 周菁 王超:《憲法司法化散論-從我國憲法司法第一案談起》。 (4) 劉武俊:《以訴訟激活彌合憲法鴻溝》。
Visual Assist X 10
增強Microsoft開發環境下的編輯能力。
Visual Assist X具有強大的編輯特色,提高了生產效率,可以完全集成到您的Microsoft開發環境中,升級了您的IDE,在不改變編程習慣的同時就可以感受到Visual Assist X為您帶來的好處。
?
Visual Assist X是一款非常好的Visual Studio .NET 2003、2002插件,支持C/C++,C#,Visual Basic等語言,能自動識別各種關鍵字、系統函數、成員變量、自動給出輸入提示、自動更正大小寫錯誤、自動標示錯誤等,有助于提高開發過程的自動化和開發效率。
?
結構體系
從Microsoft Visual C/C++ 6.0開始,所有的Microsoft IDE都為您的編輯環境提供了輕巧的剖析器,為您的代碼編寫提供方便,他們不生成目標碼,這種默認幫助形式稱為Intellisense。其特性包括成員列表框、完成列表框、停駐工具提示、自動參數信息。您可以在IDE對話框選項內設定每個組件的工作狀態。
Visual Assist X去除了默認Intellisense的幾個不足之處,采用一系列新的特性對其進行了補充,提高了IDE的可用性。其中,幫助的數量決定于IDE、所用編程語言和您設定的選項。
如同默認Intellisense,Visual Assist X也包括剖析器,運行于您的編輯過程中。該培訓器同默認剖析器的不同之處在于,他們不裝載預先設置的符號數據庫,因此擴大了Intellisense的范圍。Visual Assist X只收集您工程中的頭文件和對象信息,這些頭文件和對象可以包含在系統、第三方庫、MFC或ATL/WTL/STL中。這意味著Intellisence的活動更加頻繁,結果也更加準確。
不止是C/C++,對于所有編程語言,Visual Assist X都含有可執行的輕載默認剖析器。對于C/C++,您可以選擇Visual Assist X專有Intellisense,也可以首選默認剖析器,只有當其不可用時,再采用Visual Assist X,這些工作方式都可以通過專門設置來實現。
Visual Assist X作為一種低優先度的后臺線程插件,不影響您的IDE環境性能。該線程在idle期間活動,當您開始輸入代碼時,又回復為靜默狀態。并且它的剖析器不生成目標碼,因此,同傳統剖析器相比,占用更少的資源。
Visual Assist X提高了工作的自動化,簡化了瀏覽,并且顯示您開發過程的關鍵信息。其功能完全集成到IDE中,實現了二者的充分融合。為Microsoft Visual Studio .NET 2003和2002的所有編程語言提供編程助手,包括C/C++, C#, ASP, Visual Basic, Java 和 HTML,另外,還為Microsoft Visual C++ 6.0和 5.0的C/C++用戶提供編程幫助。
產品特點
改進了Intellisense:成員和完成列表框的出現更加頻繁、迅速,并且結果更加準確。參數信息更加完善,并帶有注釋。含有所有符號的停駐工具提示。
代碼輸入更迅速:輸入時觀察suggestion列表框,其中將根據您的輸入提供相應的備選字符。為了更加方便的選擇字符,還可以提前定義Atuotext和代碼模板。
錯誤自動校正:監控您的IDE,對那些簡單但耗時的錯誤進行即時校正。
信息獲取更加快速:更加迅速了解代碼信息,在新的VA View中觀察當前的停駐類瀏覽器,可以獲得當前符號的更多信息。除此,資源窗口中還添加了小的內容和定義項,可以獲取信息快照。
增加了色彩和格式:采用了更多的色彩和格式選項,代碼解譯更加迅速。增強了IDE的基本語法色彩,在您輸入代碼的同時,突出匹配和不匹配條目。另外,還添加了column indicator和print in color,將RTF置于剪切版內。
簡化了查找和瀏覽:查找和瀏覽更加輕松。通過內容查找可以快速跳到相同名稱的符號處,在您工作臺的任何地方都可以找到符號定義,還可以轉入您代碼中的符號執行處。選擇您文件的列表方式,鎖定頭文件和相應的cpp文件。從您的工作臺文件列表中打開文件。含有最近行為列表,可以在代碼的活動部分之間相互轉換。Move scope可以到達下一個方法,還包含往返瀏覽。
拼寫檢查:在您輸入代碼的同時進行檢查,并且可以看到同Microsoft Word相似的紅色下劃線。含有Spell check comments and strings,另外,Spell check code可以檢查錯誤的輸入符號。
拓展了基本編輯:對編輯器進行了增強,編輯代碼更加迅速。含有Surround selections,multiple clipboards. Sort lines。
適合您個人風格的配置特色:細化選項對話框,定義Visual Assist X特性以適應您的編程習慣。內容菜單中含多個命令,設置快捷方式可以加快訪問您所偏好的命令。可以禁止或允許Visual Assist X,或者強制其重新剖析從而更加智能化。
版本新特色
支持多種編程語言:Microsoft Visual Studio .NET的所有編程語言,包括C#, ASP, Java, Javascript, Basic 和 VBScript等。
單一安裝,適合所有IDE:去除了針對不同IDE的單個產品,下載后,Visual Assist X可以安裝到您的全部Microsoft IDE中,如果又安裝了新的IDE,只需重新安裝一次Visual Assist X即可。 緊密集成:Visual Assist X更加緊密的集成到了您的IDE中,利用Microsoft的Add-In和VSIP接口,無需離開IDE或改變編碼習慣就可以運行Visual Assist X。為了更好的集成,還包含新的菜單條目、更多的鍵捆綁,并去除了冗余選項。
更多的特色:包含建議列表框(Suggestion listboxes)、停駐類瀏覽器(hovering class browser)、語法上色(syntax coloring)、訪問最近應用的文件和符號等。 更好的文檔:產品含有優秀的文檔說明,選項對話框中包含信息工具提示,另外,在我們的網站上也含有關于Visual Assist X的充分信息。
系統需求
Visual Assist X可以工作于任何Microsoft IDE和操作系統。
開發環境:Visual Assist X兼容于以下開發環境,當您改變或新增了IDE時,需要重新安裝Visual Assist X: Microsoft Visual Studio .NET 2003 Microsoft Visual Studio .NET 2002 Microsoft Visual C++ .NET 2003 Microsoft Visual C++ .NET
Microsoft Visual C++ 6.0 Microsoft Visual Studio 6.0 Microsoft Visual C++ 5.0 Microsoft Visual Studio 97 Microsoft eMbedded Visual Tools 4.0 Microsoft eMbedded Visual Tools 3.0
操作系統:
Windows XP Server 2003 Windows XP Professional Windows XP Home Edition Windows 2000 Professional Windows 2000 Server Windows NT 4.0
硬盤空間:依賴于您的工程大小。建議最小為50MB,大的工程需要100MB以上。
下載地址:http://www.ttdown.com/SoftView/SoftView_21625.html?(破解版)
虛函數聯系到多態,多態聯系到繼承。所以本文中都是在繼承層次上做文章。沒了繼承,什么都沒得談。
?
下面是小弟對C++的虛函數這玩意兒的理解。
?
一,? 什么是虛函數(如果不知道虛函數為何物,但有急切的想知道,那你就應該從這里開始)
?
簡單地說,那些被virtual關鍵字修飾的成員函數,就是虛函數。虛函數的作用,用專業術語來解釋就是實現多態性(Polymorphism),多態性是將接口與實現進行分離;用形象的語言來解釋就是實現以共同的方法,但因個體差異而采用不同的策略。下面來看一段簡單的代碼
?
class A{
?
public:
?
??? void print(){ cout<<”This is A”<<endl;}
?
};
?
class B:public A{
?
public:
?
??? void print(){ cout<<”This is B”<<endl;}
?
};
?
int main(){?? //為了在以后便于區分,我這段main()代碼叫做main1
?
?? A a;
?
?? B b;
?
?? a.print();
?
?? b.print();
?
}
?
通過class A和class B的print()這個接口,可以看出這兩個class因個體的差異而采用了不同的策略,輸出的結果也是我們預料中的,分別是This is A和This is B。但這是否真正做到了多態性呢?No,多態還有個關鍵之處就是一切用指向基類的指針或引用來操作對象。那現在就把main()處的代碼改一改。
?
int main(){?? //main2
?
??? A a;
?
??? B b;
?
??? A* p1=&a;
?
??? A* p2=&b;
?
??? p1->print();
?
??? p2->print();
?
}
?
運行一下看看結果,喲呵,驀然回首,結果卻是兩個This is A。問題來了,p2明明指向的是class B的對象但卻是調用的class A的print()函數,這不是我們所期望的結果,那么解決這個問題就需要用到虛函數
?
class A{
?
public:
?
??? virtual void print(){ cout<<”This is A”<<endl;}? //現在成了虛函數了
?
};
?
class B:public A{
?
public:
?
??? void print(){ cout<<”This is B”<<endl;}? //這里需要在前面加上關鍵字virtual嗎?
?
};
?
毫無疑問,class A的成員函數print()已經成了虛函數,那么class B的print()成了虛函數了嗎?回答是Yes,我們只需在把基類的成員函數設為virtual,其派生類的相應的函數也會自動變為虛函數。所以,class B的print()也成了虛函數。那么對于在派生類的相應函數前是否需要用virtual關鍵字修飾,那就是你自己的問題了。
?
現在重新運行main2的代碼,這樣輸出的結果就是This is A和This is B了。
?
現在來消化一下,我作個簡單的總結,指向基類的指針在操作它的多態類對象時,會根據不同的類對象,調用其相應的函數,這個函數就是虛函數。
?
二,? 虛函數是如何做到的(如果你沒有看過《Inside The C++ Object Model》這本書,但又急切想知道,那你就應該從這里開始)
?
虛函數是如何做到因對象的不同而調用其相應的函數的呢?現在我們就來剖析虛函數。我們先定義兩個類
?
class A{?? //虛函數示例代碼
?
public:
?
?? virtual void fun(){cout<<1<<endl;}
?
?? virtual void fun2(){cout<<2<<endl;}
?
};
?
class B:public A{
?
public:
?
?? void fun(){cout<<3<<endl;}
?
?? void fun2(){cout<<4<<endl;}
?
};
?
由于這兩個類中有虛函數存在,所以編譯器就會為他們兩個分別插入一段你不知道的數據,并為他們分別創建一個表。那段數據叫做vptr指針,指向那個表。那個表叫做vtbl,每個類都有自己的vtbl,vtbl的作用就是保存自己類中虛函數的地址,我們可以把vtbl形象地看成一個數組,這個數組的每個元素存放的就是虛函數的地址,請看圖
?
?
?
通過上圖,可以看到這兩個vtbl分別為class A和class B服務。現在有了這個模型之后,我們來分析下面的代碼
?
A *p=new A;
?
p->fun();
?
毫無疑問,調用了A::fun(),但是A::fun()是如何被調用的呢?它像普通函數那樣直接跳轉到函數的代碼處嗎?No,其實是這樣的,首先是取出vptr的值,這個值就是vtbl的地址,再根據這個值來到vtbl這里,由于調用的函數A::fun()是第一個虛函數,所以取出vtbl第一個slot里的值,這個值就是A::fun()的地址了,最后調用這個函數。現在我們可以看出來了,只要vptr不同,指向的vtbl就不同,而不同的vtbl里裝著對應類的虛函數地址,所以這樣虛函數就可以完成它的任務。
?
而對于class A和class B來說,他們的vptr指針存放在何處呢?其實這個指針就放在他們各自的實例對象里。由于class A和class B都沒有數據成員,所以他們的實例對象里就只有一個vptr指針。通過上面的分析,現在我們來實作一段代碼,來描述這個帶有虛函數的類的簡單模型。
?
#include<iostream>
?
using namespace std;
?
//將上面“虛函數示例代碼”添加在這里
?
int main(){
?
? void (*fun)(A*);
?
? A *p=new B;
?
? long lVptrAddr;
?
? memcpy(&lVptrAddr,p,4);
?
? memcpy(&fun,reinterpret_cast<long*>(lVptrAddr),4);
?
? fun(p);
?
? delete p;
?
? system("pause");
?
}
?
用VC或Dev-C++編譯運行一下,看看結果是不是輸出3,如果不是,那么太陽明天肯定是從西邊出來。現在一步一步開始分析
?
void (*fun)(A*);? 這段定義了一個函數指針名字叫做fun,而且有一個A*類型的參數,這個函數指針待會兒用來保存從vtbl里取出的函數地址
?
A* p=new B;? 這個我不太了解,算了,不解釋這個了
?
long lVptrAddr;? 這個long類型的變量待會兒用來保存vptr的值
?
memcpy(&lVptrAddr,p,4);? 前面說了,他們的實例對象里只有vptr指針,所以我們就放心大膽地把p所指的4bytes內存里的東西復制到lVptrAddr中,所以復制出來的4bytes內容就是vptr的值,即vtbl的地址
?
現在有了vtbl的地址了,那么我們現在就取出vtbl第一個slot里的內容
?
memcpy(&fun,reinterpret_cast<long*>(lVptrAddr),4);? 取出vtbl第一個slot里的內容,并存放在函數指針fun里。需要注意的是lVptrAddr里面是vtbl的地址,但lVptrAddr不是指針,所以我們要把它先轉變成指針類型
?
fun(p);? 這里就調用了剛才取出的函數地址里的函數,也就是調用了B::fun()這個函數,也許你發現了為什么會有參數p,其實類成員函數調用時,會有個this指針,這個p就是那個this指針,只是在一般的調用中編譯器自動幫你處理了而已,而在這里則需要自己處理。
?
delete p;和system("pause");? 這個我不太了解,算了,不解釋這個了
?
如果調用B::fun2()怎么辦?那就取出vtbl的第二個slot里的值就行了
?
memcpy(&fun,reinterpret_cast<long*>(lVptrAddr+4),4); 為什么是加4呢?因為一個指針的長度是4bytes,所以加4。或者memcpy(&fun,reinterpret_cast<long*>(lVptrAddr)+1,4); 這更符合數組的用法,因為lVptrAddr被轉成了long*型別,所以+1就是往后移sizeof(long)的長度
?
三,? 以一段代碼開始
?
#include<iostream>
?
using namespace std;
?
class A{?? //虛函數示例代碼2
?
public:
?
? virtual void fun(){ cout<<"A::fun"<<endl;}
?
? virtual void fun2(){cout<<"A::fun2"<<endl;}
?
};
?
class B:public A{
?
public:
?
? void fun(){ cout<<"B::fun"<<endl;}
?
? void fun2(){ cout<<"B::fun2"<<endl;}
?
};? //end//虛函數示例代碼2
?
int main(){
?
void (A::*fun)();? //定義一個函數指針
?
A *p=new B;
?
fun=&A::fun;
?
(p->*fun)();
?
fun = &A::fun2;
?
(p->*fun)();
?
delete p;
?
system("pause");
?
}
?
你能估算出輸出結果嗎?如果你估算出的結果是A::fun和A::fun2,呵呵,恭喜恭喜,你中圈套了。其實真正的結果是B::fun和B::fun2,如果你想不通就接著往下看。給個提示,&A::fun和&A::fun2是真正獲得了虛函數的地址嗎?
?
首先我們回到第二部分,通過段實作代碼,得到一個“通用”的獲得虛函數地址的方法
?
#include<iostream>
?
using namespace std;
?
//將上面“虛函數示例代碼2”添加在這里
?
void CallVirtualFun(void* pThis,int index=0){
?
? void (*funptr)(void*);
?
? long lVptrAddr;
?
? memcpy(&lVptrAddr,pThis,4);
?
? memcpy(&funptr,reinterpret_cast<long*>(lVptrAddr)+index,4);
?
? funptr(pThis); //調用
?
}
?
int main(){
?
?? A* p=new B;
?
?? CallVirtualFun(p);? //調用虛函數p->fun()
?
?? CallVirtualFun(p,1);//調用虛函數p->fun2()
?
?? system("pause");
?
}
?
現在我們擁有一個“通用”的CallVirtualFun方法。
?
這個通用方法和第三部分開始處的代碼有何聯系呢?聯系很大。由于A::fun()和A::fun2()是虛函數,所以&A::fun和&A::fun2獲得的不是函數的地址,而是一段間接獲得虛函數地址的一段代碼的地址,我們形象地把這段代碼看作那段CallVirtualFun。編譯器在編譯時,會提供類似于CallVirtualFun這樣的代碼,當你調用虛函數時,其實就是先調用的那段類似CallVirtualFun的代碼,通過這段代碼,獲得虛函數地址后,最后調用虛函數,這樣就真正保證了多態性。同時大家都說虛函數的效率低,其原因就是,在調用虛函數之前,還調用了獲得虛函數地址的代碼。
?
?
最后的說明:本文的代碼可以用VC6和Dev-C++4.9.8.0通過編譯,且運行無問題。其他的編譯器小弟不敢保證。其中,里面的類比方法只能看成模型,因為不同的編譯器的低層實現是不同的。例如this指針,Dev-C++的gcc就是通過壓棧,當作參數傳遞,而VC的編譯器則通過取出地址保存在ecx中。所以這些類比方法不能當作具體實現。
1、 引言
Linux的興起可以說是Internet創造的一個奇跡。Linux作為一個完全開放其原代碼的免費的自由軟件,兼容了各種UNIX標準(如POSIX、UNIX System V 和 BSD UNIX 等)的多用戶、多任務的具有復雜內核的操作系統。在中國,隨著Internet的普及,一批主要以高等院校的學生和ISP的技術人員組成的Linux愛好者隊伍已經蓬勃成長起來。越來越多的編程愛好者也逐漸酷愛上這個優秀的自由軟件。本文介紹了Linux下Socket的基本概念和函數調用。
2、 什么是Socket
Socket(套接字)是通過標準的UNIX文件描述符和其它程序通訊的一個方法。每一個套接字都用一個半相關描述:{協議,本地地址、本地端口}來表示;一個完整的套接字則用一個相關描述:{協議,本地地址、本地端口、遠程地址、遠程端口},每一個套接字都有一個本地的由操作系統分配的唯一的套接字號。
3、 Socket的三種類型
(1) 流式Socket(SOCK_STREAM)
流式套接字提供可靠的、面向連接的通信流;它使用TCP協議,從而保證了數據傳輸的正確性和順序的。
(2) 數據報Socket(SOCK_DGRAM)
數據報套接字定義了一種無連接的服務,數據通過相互獨立的報文進行傳輸,是無序的,并且不保證可靠、無差錯。它使用數據報協議UDP
(3) 原始Socket
原始套接字允許對底層協議如IP或ICMP直接訪問,它功能強大但使用較為不便,主要用于一些協議的開發。
4、 利用套接字發送數據
1、 對于流式套接字用系統調用send()來發送數據。
2、 對于數據報套接字,則需要自己先加一個信息頭,然后調用sendto()函數把數據發送出去。
5、 Linux中Socket的數據結構
(1) struct sockaddr { //用于存儲套接字地址
unsigned short sa_family;//地址類型
char sa_data[14]; //14字節的協議地址
};
(2) struct sockaddr_in{ //in 代表internet
short int sin_family; //internet協議族
unsigned short int sin_port;//端口號,必須是網絡字節順序
struct in_addr sin_addr;//internet地址,必須是網絡字節順序
unsigned char sin_zero;//添0(和struct sockaddr一樣大小
};
(3) struct in_addr{
unsigned long s_addr;
};
6、 網絡字節順序及其轉換函數
(1) 網絡字節順序
每一臺機器內部對變量的字節存儲順序不同,而網絡傳輸的數據是一定要統一順序的。所以對內部字節表示順序與網絡字節順序不同的機器,一定要對數據進行轉換,從程序的可移植性要求來講,就算本機的內部字節表示順序與網絡字節順序相同也應該在傳輸數據以前先調用數據轉換函數,以便程序移植到其它機器上后能正確執行。真正轉換還是不轉換是由系統函數自己來決定的。
(2) 有關的轉換函數
* unsigned short int htons(unsigned short int hostshort):
主機字節順序轉換成網絡字節順序,對無符號短型進行操作4bytes
* unsigned long int htonl(unsigned long int hostlong):
主機字節順序轉換成網絡字節順序,對無符號長型進行操作8bytes
* unsigned short int ntohs(unsigned short int netshort):
網絡字節順序轉換成主機字節順序,對無符號短型進行操作4bytes
* unsigned long int ntohl(unsigned long int netlong):
網絡字節順序轉換成主機字節順序,對無符號長型進行操作8bytes
注:以上函數原型定義在netinet/in.h里
7、 IP地址轉換
有三個函數將數字點形式表示的字符串IP地址與32位網絡字節順序的二進制形式的IP地址進行轉換
(1) unsigned long int inet_addr(const char * cp):該函數把一個用數字和點表示的IP地址的字符串轉換成一個無符號長整型,如:struct sockaddr_in ina
ina.sin_addr.s_addr=inet_addr("202.206.17.101")
該函數成功時:返回轉換結果;失敗時返回常量INADDR_NONE,該常量=-1,二進制的無符號整數-1相當于255.255.255.255,這是一個廣播地址,所以在程序中調用iner_addr()時,一定要人為地對調用失敗進行處理。由于該函數不能處理廣播地址,所以在程序中應該使用函數inet_aton()。
(2)int inet_aton(const char * cp,struct in_addr * inp):此函數將字符串形式的IP地址轉換成二進制形式的IP地址;成功時返回1,否則返回0,轉換后的IP地址存儲在參數inp中。
(3) char * inet_ntoa(struct in-addr in):將32位二進制形式的IP地址轉換為數字點形式的IP地址,結果在函數返回值中返回,返回的是一個指向字符串的指針。
8、 字節處理函數
Socket地址是多字節數據,不是以空字符結尾的,這和C語言中的字符串是不同的。Linux提供了兩組函數來處理多字節數據,一組以b(byte)開頭,是和BSD系統兼容的函數,另一組以mem(內存)開頭,是ANSI C提供的函數。
以b開頭的函數有:
(1) void bzero(void * s,int n):將參數s指定的內存的前n個字節設置為0,通常它用來將套接字地址清0。
(2) void bcopy(const void * src,void * dest,int n):從參數src指定的內存區域拷貝指定數目的字節內容到參數dest指定的內存區域。
(3) int bcmp(const void * s1,const void * s2,int n):比較參數s1指定的內存區域和參數s2指定的內存區域的前n個字節內容,如果相同則返回0,否則返回非0。
注:以上函數的原型定義在strings.h中。
以mem開頭的函數有:
(1) void * memset(void * s,int c,size_t n):將參數s指定的內存區域的前n個字節設置為參數c的內容。
(2) void * memcpy(void * dest,const void * src,size_t n):功能同bcopy(),區別:函數bcopy()能處理參數src和參數dest所指定的區域有重疊的情況,memcpy()則不能。
(4) int memcmp(const void * s1,const void * s2,size_t n):比較參數s1和參數s2指定區域的前n個字節內容,如果相同則返回0,否則返回非0。
注:以上函數的原型定義在string.h中。
9、 基本套接字函數
(1) socket()
#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain,int type,int protocol)
參數domain指定要創建的套接字的協議族,可以是如下值:
AF_UNIX //UNIX域協議族,本機的進程間通訊時使用
AF_INET //Internet協議族(TCP/IP)
AF_ISO //ISO協議族
參數type指定套接字類型,可以是如下值:
SOCK_STREAM //流套接字,面向連接的和可靠的通信類型
SOCK_DGRAM //數據報套接字,非面向連接的和不可靠的通信類型
SOCK_RAW //原始套接字,只對Internet協議有效,可以用來直接訪問IP協議
參數protocol通常設置成0,表示使用默認協議,如Internet協議族的流套接字使用TCP協議,而數據報套接字使用UDP協議。當套接字是原始套接字類型時,需要指定參數protocol,因為原始套接字對多種協議有效,如ICMP和IGMP等。
Linux系統中創建一個套接字的操作主要是:在內核中創建一個套接字數據結構,然后返回一個套接字描述符標識這個套接字數據結構。這個套接字數據結構包含連接的各種信息,如對方地址、TCP狀態以及發送和接收緩沖區等等,TCP協議根據這個套接字數據結構的內容來控制這條連接。
(2) 函數connect()
#include<sys/types.h>
#include<sys/socket.h>
int connect(int sockfd,struct sockaddr * servaddr,int addrlen)
參數sockfd是函數socket返回的套接字描述符;參數servaddr指定遠程服務器的套接字地址,包括服務器的IP地址和端口號;參數addrlen指定這個套接字地址的長度。成功時返回0,否則返回-1,并設置全局變量為以下任何一種錯誤類型:ETIMEOUT、ECONNREFUSED、EHOSTUNREACH或ENETUNREACH。
在調用函數connect之前,客戶機需要指定服務器進程的套接字地址。客戶機一般不需要指定自己的套接字地址(IP地址和端口號),系統會自動從1024至5000的端口號范圍內為它選擇一個未用的端口號,然后以這個端口號和本機的IP地址填充這個套接字地址。
客戶機調用函數connect來主動建立連接。這個函數將啟動TCP協議的3次握手過程。在建立連接之后或發生錯誤時函數返回。連接過程可能出現的錯誤情況有:
(1) 如果客戶機TCP協議沒有接收到對它的SYN數據段的確認,函數以錯誤返回,錯誤類型為ETIMEOUT。通常TCP協議在發送SYN數據段失敗之后,會多次發送SYN數據段,在所有的發送都高中失敗之后,函數以錯誤返回。
注:SYN(synchronize)位:請求連接。TCP用這種數據段向對方TCP協議請求建立連接。在這個數據段中,TCP協議將它選擇的初始序列號通知對方,并且與對方協議協商最大數據段大小。SYN數據段的序列號為初始序列號,這個SYN數據段能夠被確認。當協議接收到對這個數據段的確認之后,建立TCP連接。
(2) 如果遠程TCP協議返回一個RST數據段,函數立即以錯誤返回,錯誤類型為ECONNREFUSED。當遠程機器在SYN數據段指定的目的端口號處沒有服務進程在等待連接時,遠程機器的TCP協議將發送一個RST數據段,向客戶機報告這個錯誤。客戶機的TCP協議在接收到RST數據段后不再繼續發送SYN數據段,函數立即以錯誤返回。
注:RST(reset)位:表示請求重置連接。當TCP協議接收到一個不能處理的數據段時,向對方TCP協議發送這種數據段,表示這個數據段所標識的連接出現了某種錯誤,請求TCP協議將這個連接清除。有3種情況可能導致TCP協議發送RST數據段:(1)SYN數據段指定的目的端口處沒有接收進程在等待;(2)TCP協議想放棄一個已經存在的連接;(3)TCP接收到一個數據段,但是這個數據段所標識的連接不存在。接收到RST數據段的TCP協議立即將這條連接非正常地斷開,并向應用程序報告錯誤。
(3) 如果客戶機的SYN數據段導致某個路由器產生“目的地不可到達”類型的ICMP消息,函數以錯誤返回,錯誤類型為EHOSTUNREACH或ENETUNREACH。通常TCP協議在接收到這個ICMP消息之后,記錄這個消息,然后繼續幾次發送SYN數據段,在所有的發送都告失敗之后,TCP協議檢查這個ICMP消息,函數以錯誤返回。
注:ICMP:Internet 消息控制協議。Internet的運行主要是由Internet的路由器來控制,路由器完成IP數據包的發送和接收,如果發送數據包時發生錯誤,路由器使用ICMP協議來報告這些錯誤。ICMP數據包是封裝在IP數據包的數據部分中進行傳輸的,其格式如下:
類型
碼
校驗和
數據
0 8 16 24 31
類型:指出ICMP數據包的類型。
代碼:提供ICMP數據包的進一步信息。
校驗和:提供了對整個ICMP數據包內容的校驗和。
ICMP數據包主要有以下類型:
(1) 目的地不可到達:A、目的主機未運行;B、目的地址不存在;C、路由表中沒有目的地址對應的條目,因而路由器無法找到去往目的主機的路由。
(2) 超時:路由器將接收到的IP數據包的生存時間(TTL)域減1,如果這個域的值變為0,路由器丟棄這個IP數據包,并且發送這種ICMP消息。
(3) 參數出錯:當IP數據包中有無效域時發送。
(4) 重定向:將一條新的路徑通知主機。
(5) ECHO請求、ECHO回答:這兩條消息用語測試目的主機是否可以到達。請求者向目的主機發送ECHO請求ICMP數據包,目的主機在接收到這個ICMP數據包之后,返回ECHO回答ICMP數據包。
(6) 時戳請求、時戳回答:ICMP協議使用這兩種消息從其他機器處獲得其時鐘的當前時間。
調用函數connect的過程中,當客戶機TCP協議發送了SYN數據段的確認之后,TCP狀態由CLOSED狀態轉為SYN_SENT狀態,在接收到對SYN數據段的確認之后,TCP狀態轉換成ESTABLISHED狀態,函數成功返回。如果調用函數connect失敗,應該用close關閉這個套接字描述符,不能再次使用這個套接字描述符來調用函數connect。
注:TCP協議狀態轉換圖:
被動OPEN CLOSE 主動OPEN
(建立TCB) (刪除TCB) (建立TCB,
發送SYN)
接收SYN SEND
(發送SYN,ACK) (發送SYN)
接收SYN的ACK(無動作)
接收SYN的ACK 接收SYN,ACK
(無動作) (發送ACK)
CLOSE
(發送FIN) CLOSE 接收FIN
(發送FIN) (發送FIN)
接收FIN
接收FIN的ACK(無動作) (發送ACK) CLOSE(發送FIN)
接收FIN 接收FIN的ACK 接收FIN的ACK
(發送ACK) (無動作) (無動作)
2MSL超時(刪除TCB)
(3) 函數bind()
函數bind將本地地址與套接字綁定在一起,其定義如下:
#include<sys/types.h>
#include<sys/socket.h>
int bind(int sockfd,struct sockaddr * myaddr,int addrlen);
參數sockfd是函數sockt返回的套接字描述符;參數myaddr是本地地址;參數addrlen是套接字地址結構的長度。執行成功時返回0,否則,返回-1,并設置全局變量errno為錯誤類型EADDRINUSER。
服務器和客戶機都可以調用函數bind來綁定套接字地址,但一般是服務器調用函數bind來綁定自己的公認端口號。綁定操作一般有如下幾種組合方式:
表1
程序類型
IP地址
端口號
說明
服務器
INADDR_ANY
非零值
指定服務器的公認端口號
服務器
本地IP地址
非零值
指定服務器的IP地址和公認端口號
客戶機
INADDR_ANY
非零值
指定客戶機的連接端口號
客戶機
本地IP地址
非零值
指定客戶機的IP地址連接端口號
客戶機
本地IP地址
零
指定客戶機的IP地址
分別說明如下:
(1) 服務器指定套接字地址的公認端口號,不指定IP地址:即服務器調用bind時,設置套接字的IP地址為特殊的INADDE-ANY,表示它愿意接收來自任何網絡設備接口的客戶機連接。這是服務器最常用的綁定方式。
(2) 服務器指定套接字地址的公認端口號和IP地址:服務器調用bind時,如果設置套接字的IP地址為某個本地IP地址,這表示這臺機器只接收來自對應于這個IP地址的特定網絡設備接口的客戶機連接。當服務器有多塊網卡時,可以用這種方式來限制服務器的接收范圍。
(3) 客戶機指定套接字地址的連接端口號:一般情況下,客戶機調用connect函數時不用指定自己的套接字地址的端口號。系統會自動為它選擇一個未用的端口號,并且用本地的IP地址來填充套接字地址中的相應項。但有時客戶機需要使用一個特定的端口號(比如保留端口號),而系統不會未客戶機自動分配一個保留端口號,所以需要調用函數bind來和一個未用的保留端口號綁定。
(4) 指定客戶機的IP地址和連接端口號:表示客戶機使用指定的網絡設備接口和端口號進行通信。
(5) 指定客戶機的IP地址:表示客戶機使用指定的網絡設備接口和端口號進行通信,系統自動為客戶機選一個未用的端口號。一般只有在主機有多個網絡設備接口時使用。
我們一般不在客戶機上使用固定的客戶機端口號,除非是必須使用的情況。在客戶機上使用固定的端口號有以下不利:
(1) 服務器執行主動關閉操作:服務器最后進入TIME_WAIT狀態。當客戶機再次與這個服務器進行連接時,仍使用相同的客戶機端口號,于是這個連接與前次連接的套接字對完全一樣,但是一呢、為前次連接處于TIME_WAIT狀態,并未消失,所以這次連接請求被拒絕,函connect以錯誤返回,錯誤類型為ECONNREFUSED
(2) 客戶機執行主動關閉操作:客戶機最后進入TIME_WAIT狀態。當馬上再次執行這個客戶機程序時,客戶機將繼續與這個固定客戶機端口號綁定,但因為前次連接處于TIME_WAIT狀態,并未消失,系統會發現這個端口號仍被占用,所以這次綁定操作失敗,函數bind以錯誤返回,錯誤類型為EADDRINUSE。
(4) 函數listen()
函數listen將一個套接字轉換為征聽套接字,定義如下;
#include<sys/socket,h>
int listen(int sockfd,int backlog)
參數sockfd指定要轉換的套接字描述符;參數backlog設置請求隊列的最大長度;執行成功時返回0, 否則返回-1。函數listen功能有兩個:
(1) 將一個尚未連接的主動套接字(函數socket創建的可以用來進行主動連接但不能接受連接請求的套接字)轉換成一個被動連接套接字。執行listen之后,服務器的TCP狀態由CLOSED轉為LISTEN狀態。
(2) TCP協議將到達的連接請求隊列,函數listen的第二個參數指定這個隊列的最大長度。
注:參數backlog的作用:
TCP協議為每一個征聽套接字維護兩個隊列:
(1) 未完成連接隊列:每個尚未完成3次握手操作的TCP連接在這個隊列中占有一項。TCP希望儀在接收到一個客戶機SYN數據段之后,在這個隊列中創建一個新條目,然后發送對客戶機SYN數據段的確認和自己的SYN數據段(ACK+SYN數據段),等待客戶機對自己的SYN數據段的確認。此時,套接字處于SYN_RCVD狀態。這個條目將保存在這個隊列中,直到客戶機返回對SYN數據段的確認或者連接超時。
(2) 完成連接隊列:每個已經完成3次握手操作,但尚未被應用程序接收(調用函數accept)的TCP連接在這個隊列中占有一項。當一個在未完成連接隊列中的連接接收到對SYN數據段的確認之后,完成3次握手操作,TCP協議將它從未完成連接隊列移到完成連接隊列中。此時,套接字處于ESTABLISHED狀態。這個條目將保存在這個隊列中,直到應用程序調用函數accept來接收它。
參數backlog指定某個征聽套接字的完成連接隊列的最大長度,表示這個套接字能夠接收的最大數目的未接收連接。如果當一個客戶機的SYN數據段到達時,征聽套接字的完成隊列已經滿了,那么TCP協議將忽略這個SYN數據段。對于不能接收的SYN數據段,TCP協議不發送RST數據段,
(5) 函數accept()
函數accept從征聽套接字的完成隊列中接收一個已經建立起來的TCP連接。如果完成連接隊列為空,那么這個進程睡眠。
#include<sys/socket.h>
int accept(int sockfd,struct sockaddr * addr,int * addrlen)
參數sockfd指定征聽套接字描述符;參數addr為指向一個Internet套接字地址結構的指針;參數addrlen為指向一個整型變量的指針。執行成功時,返回3個結果:函數返回值為一個新的套接字描述符,標識這個接收的連接;參數addr指向的結構變量中存儲客戶機地址;參數addrlen指向的整型變量中存儲客戶機地址的長度。失敗時返回-1。
征聽套接字專為接收客戶機連接請求,完成3次握手操作而用的,所以TCP協議不能使用征聽套接字描述符來標識這個連接,于是TCP協議創建一個新的套接字來標識這個要接收的連接,并將它的描述符發揮給應用程序。現在有兩個套接字,一個是調用函數accept時使用的征聽套接字,另一個是函數accept返回的連接套接字(connected socket)。一個服務器通常只需創建一個征聽套接字,在服務器進程的整個活動期間,用它來接收所有客戶機的連接請求,在服務器進程終止前關閉這個征聽套接字;對于沒一個接收的(accepted)連接,TCP協議都創建一個新的連接套接字來標識這個連接,服務器使用這個連接套接字與客戶機進行通信操作,當服務器處理完這個客戶機請求時,關閉這個連接套接字。
當函數accept阻塞等待已經建立的連接時,如果進程捕獲到信號,函數將以錯誤返回,錯誤類型為EINTR。對于這種錯誤,一般重新調用函數accept來接收連接。
(6) 函數close()
函數close關閉一個套接字描述符。定義如下:
#include<unistd.h>
int close(int sockfd);
執行成功時返回0,否則返回-1。與操作文件描述符的close一樣,函數close將套接字描述符的引用計數器減1,如果描述符的引用計數大于0,則表示還有進程引用這個描述符,函數close正常返回;如果為0,則啟動清除套接字描述符的操作,函數close立即正常返回。
調用close之后,進程將不再能夠訪問這個套接字,但TCP協議將繼續使用這個套接字,將尚未發送的數據傳遞到對方,然后發送FIN數據段,執行關閉操作,一直等到這個TCP連接完全關閉之后,TCP協議才刪除該套接字。
(7) 函數read()和write()
用于從套接字讀寫數據。定義如下:
int read(int fd,char * buf,int len)
int write(int fd,char * buf,int len)
函數執行成功時,返回讀或寫的數據量的大小,失敗時返回-1。
每個TCP套接字都有兩個緩沖區:套接字發送緩沖區、套接字接收緩沖區,分別處理發送和接收任務。從網絡讀、寫數據的操作是由TCP協議在內核中完成的:TCP協議將從網絡上接收到的數據保存在相應套接字的接收緩沖區中,等待用戶調用函數將它們從接收緩沖區拷貝到用戶緩沖區;用戶將要發送的數據拷貝到相應套接字的發送緩沖區中,然后由TCP協議按照一定的算法處理這些數據。
讀寫連接套接字的操作與讀寫文件的操作類似,也可以使用函數read和write。函數read完成將數據從套接字接收緩沖區拷貝到用戶緩沖區:當套接字接收緩沖區有數據可讀時,1:可讀數據量大于函數read指定值,返回函數參數len指定的數據量;2:了度數據量小于函數read指定值,函數read不等待請求的所有數據都到達,而是立即返回實際讀到的數據量;當無數據可讀時,函數read將阻塞不返回,等待數據到達。
當TCP協議接收到FIN數據段,相當于給讀操作一個文件結束符,此時read函數返回0,并且以后所有在這個套接字上的讀操作均返回0,這和普通文件中遇到文件結束符是一樣的。
當TCP協議接收到RST數據段,表示連接出現了某種錯誤,函數read將以錯誤返回,錯誤類型為ECONNERESET。并且以后所有在這個套接字上的讀操作均返回錯誤。錯誤返回時返回值小于0。
函數write完成將數據從用戶緩沖區拷貝到套接字發送緩沖區的任務:到套接字發送緩沖區有足夠拷貝所有用戶數據的空間時,函數write將數據拷貝到這個緩沖區中,并返回老輩的數量大小,如果可用空間小于write參數len指定的大小時,函數write將阻塞不返回,等待緩沖區有足夠的空間。
當TCP協議接收到RST數據段(當對方已經關閉了這條連接之后,繼續向這個套接字發送數據將導致對方TCP協議返回RST數據段),TCP協議接收到RST數據段時,函數write將以錯誤返回,錯誤類型為EINTR。以后可以繼續在這個套接字上寫數據。
(8) 函數getsockname()和getpeername()
函數getsockname返回套接字的本地地址;函數getpeername返回套接字對應的遠程地址。
10、 結束語
網絡程序設計全靠套接字接收和發送信息。上文主要講述了Linux 下Socket的基本概念、Sockets API以及Socket所涉及到的TCP常識。
一、ADO簡介 ADO(ActiveX?Data?Object)是Microsoft數據庫應用程序開發的新接口,是建立在OLE?DB之上的高層數據庫訪問技術,請不必為此擔心,即使你對OLE?DB,COM不了解也能輕松對付ADO,因為它非常簡單易用,甚至比你以往所接觸的ODBC?API、DAO、RDO都要容易使用,并不失靈活性。本文將詳細地介紹在VC下如何使用ADO來進行數據庫應用程序開發,并給出示例代碼。 本文示例代碼?
二、基本流程 萬事開頭難,任何一種新技術對于初學者來說最重要的還是“入門”,掌握其要點。讓我們來看看ADO數據庫開發的基本流程吧! (1)初始化COM庫,引入ADO庫定義文件 (2)用Connection對象連接數據庫 (3)利用建立好的連接,通過Connection、Command對象執行SQL命令,或利用Recordset對象取得結果記錄集進行查詢、處理。 (4)使用完畢后關閉連接釋放對象。
準備工作: 為了大家都能測試本文提供的例子,我們采用Access數據庫,您也可以直接在我們提供的示例代碼中找到這個test.mdb。 下面我們將詳細介紹上述步驟并給出相關代碼。 【1】COM庫的初始化 我們可以使用AfxOleInit()來初始化COM庫,這項工作通常在CWinApp::InitInstance()的重載函數中完成,請看如下代碼:
BOOL?CADOTest1App::InitInstance() ??{ ??AfxOleInit(); ??......?
【2】用#import指令引入ADO類型庫 我們在stdafx.h中加入如下語句:(stdafx.h這個文件哪里可以找到?你可以在FileView中的Header?Files里找到) #import?"c:\program?files\common?files\system\ado\msado15.dll"?no_namespace?rename("EOF","adoEOF") 這一語句有何作用呢?其最終作用同我們熟悉的#include類似,編譯的時候系統會為我們生成msado15.tlh,ado15.tli兩個C++頭文件來定義ADO庫。
幾點說明: (1)?您的環境中msado15.dll不一定在這個目錄下,請按實際情況修改 (2)?在編譯的時候肯能會出現如下警告,對此微軟在MSDN中作了說明,并建議我們不要理會這個警告。 msado15.tlh(405)?:?warning?C4146:?unary?minus?operator?applied?to?unsigned?type,?result?still?unsigned?
【3】創建Connection對象并連接數據庫 首先我們需要添加一個指向Connection對象的指針: _ConnectionPtr?m_pConnection; 下面的代碼演示了如何創建Connection對象實例及如何連接數據庫并進行異常捕捉。
BOOL?CADOTest1Dlg::OnInitDialog() ??{ ??CDialog::OnInitDialog(); ??HRESULT?hr; ??try ??{ ??hr?=?m_pConnection.CreateInstance("ADODB.Connection");///創建Connection對象 ??if(SUCCEEDED(hr)) ??{ ??hr?=?m_pConnection->Open("Provider=Microsoft.Jet.OLEDB.4.0;Data?Source=test.mdb","","",adModeUnknown);///連接數據庫 ??///上面一句中連接字串中的Provider是針對ACCESS2000環境的,對于ACCESS97,需要改為:Provider=Microsoft.Jet.OLEDB.3.51;??} ??} ??catch(_com_error?e)///捕捉異常 ??{ ??CString?errormessage; ??errormessage.Format("連接數據庫失敗!\r\n錯誤信息:%s",e.ErrorMessage()); ??AfxMessageBox(errormessage);///顯示錯誤信息 ??}?
在這段代碼中我們是通過Connection對象的Open方法來進行連接數據庫的,下面是該方法的原型 HRESULT?Connection15::Open?(?_bstr_t?ConnectionString,?_bstr_t?UserID,?_bstr_t?Password,?long?Options?) ConnectionString為連接字串,UserID是用戶名,?Password是登陸密碼,Options是連接選項,用于指定Connection對象對數據的更新許可權, Options可以是如下幾個常量: adModeUnknown:缺省。當前的許可權未設置 adModeRead:只讀 adModeWrite:只寫 adModeReadWrite:可以讀寫 adModeShareDenyRead:阻止其它Connection對象以讀權限打開連接 adModeShareDenyWrite:阻止其它Connection對象以寫權限打開連接 adModeShareExclusive:阻止其它Connection對象打開連接 adModeShareDenyNone:允許其它程序或對象以任何權限建立連接
我們給出一些常用的連接方式供大家參考: (1)通過JET數據庫引擎對ACCESS2000數據庫的連接
m_pConnection->Open("Provider=Microsoft.Jet.OLEDB.4.0;Data?Source=C:\\test.mdb","","",adModeUnknown);
(2)通過DSN數據源對任何支持ODBC的數據庫進行連接: m_pConnection->Open("Data?Source=adotest;UID=sa;PWD=;","","",adModeUnknown);
(3)不通過DSN對SQL?SERVER數據庫進行連接:?m_pConnection->Open("driver={SQL?Server};Server=127.0.0.1;DATABASE=vckbase;UID=sa;PWD=139","","",adModeUnknown);
其中Server是SQL服務器的名稱,DATABASE是庫的名稱
Connection對象除Open方法外還有許多方法,我們先介紹Connection對象中兩個有用的屬性ConnectionTimeOut與State ConnectionTimeOut用來設置連接的超時時間,需要在Open之前調用,例如:?m_pConnection->ConnectionTimeout?=?5;///設置超時時間為5秒 m_pConnection->Open("Data?Source=adotest;","","",adModeUnknown);
State屬性指明當前Connection對象的狀態,0表示關閉,1表示已經打開,我們可以通過讀取這個屬性來作相應的處理,例如: if(m_pConnection->State) ?????m_pConnection->Close();?///如果已經打開了連接則關閉它
【4】執行SQL命令并取得結果記錄集 為了取得結果記錄集,我們定義一個指向Recordset對象的指針:_RecordsetPtr?m_pRecordset; 并為其創建Recordset對象的實例:?m_pRecordset.CreateInstance("ADODB.Recordset"); SQL命令的執行可以采用多種形式,下面我們一進行闡述。
(1)利用Connection對象的Execute方法執行SQL命令 Execute方法的原型如下所示: _RecordsetPtr?Connection15::Execute?(?_bstr_t?CommandText,?VARIANT?*?RecordsAffected,?long?Options?)?其中CommandText是命令字串,通常是SQL命令。參數RecordsAffected是操作完成后所影響的行數,?參數Options表示CommandText中內容的類型,Options可以取如下值之一: adCmdText:表明CommandText是文本命令 adCmdTable:表明CommandText是一個表名 adCmdProc:表明CommandText是一個存儲過程 adCmdUnknown:未知
Execute執行完后返回一個指向記錄集的指針,下面我們給出具體代碼并作說明。???_variant_t?RecordsAffected; ??///執行SQL命令:CREATE?TABLE創建表格users,users包含四個字段:整形ID,字符串username,整形old,日期型birthday ??m_pConnection->Execute("CREATE?TABLE?users(ID?INTEGER,username?TEXT,old?INTEGER,birthday?DATETIME)",&RecordsAffected,adCmdText); ??///往表格里面添加記錄 ??m_pConnection->Execute("INSERT?INTO?users(ID,username,old,birthday)?valueS?(1,?''''''''Washington'''''''',25,''''''''1970/1/1'''''''')",&RecordsAffected,adCmdText); ??///將所有記錄old字段的值加一 ??m_pConnection->Execute("UPDATE?users?SET?old?=?old+1",&RecordsAffected,adCmdText); ??///執行SQL統計命令得到包含記錄條數的記錄集 ??m_pRecordset?=??m_pConnection->Execute("SELECT?COUNT(*)?FROM?users",&RecordsAffected,adCmdText); ??_variant_t?vIndex?=?(long)0; ??_variant_t?vCount?=?m_pRecordset->GetCollect(vIndex);///取得第一個字段的值放入vCount變量 ??m_pRecordset->Close();///關閉記錄集 ??CString?message; ??message.Format("共有%d條記錄",vCount.lVal); ??AfxMessageBox(message);///顯示當前記錄條數
(2)利用Command對象來執行SQL命令 ??_CommandPtr?m_pCommand; ??m_pCommand.CreateInstance("ADODB.Command"); ??_variant_t?vNULL; ??vNULL.vt?=?VT_ERROR; ??vNULL.scode?=?DISP_E_PARAMNOTFOUND;///定義為無參數 ??m_pCommand->ActiveConnection?=?m_pConnection;///非常關鍵的一句,將建立的連接賦值給它 ??m_pCommand->CommandText?=?"SELECT?*?FROM?users";///命令字串 ??m_pRecordset?=?m_pCommand->Execute(&vNULL,&vNULL,adCmdText);///執行命令,取得記錄集
在這段代碼中我們只是用Command對象來執行了SELECT查詢語句,Command對象在進行存儲過程的調用中能真正體現它的作用。下次我們將詳細介紹。?
(3)直接用Recordset對象進行查詢取得記錄集? 例如?
??m_pRecordset->Open("SELECT?*?FROM?users",_variant_t((IDispatch?*)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText);
Open方法的原型是這樣的: HRESULT?Recordset15::Open?(?const?_variant_t?&?Source,?const?_variant_t?&?ActiveConnection,?enum?CursorTypeEnum?CursorType,?enum?LockTypeEnum?LockType,?long?Options?)? 其中: ①Source是數據查詢字符串 ②ActiveConnection是已經建立好的連接(我們需要用Connection對象指針來構造一個_variant_t對象)? ③CursorType光標類型,它可以是以下值之一,請看這個枚舉結構: enum?CursorTypeEnum { adOpenUnspecified?=?-1,///不作特別指定 adOpenForwardOnly?=?0,///前滾靜態光標。這種光標只能向前瀏覽記錄集,比如用MoveNext向前滾動,這種方式可以提高瀏覽速度。但諸如BookMark,RecordCount,AbsolutePosition,AbsolutePage都不能使用 adOpenKeyset?=?1,///采用這種光標的記錄集看不到其它用戶的新增、刪除操作,但對于更新原有記錄的操作對你是可見的。 adOpenDynamic?=?2,///動態光標。所有數據庫的操作都會立即在各用戶記錄集上反應出來。 adOpenStatic?=?3///靜態光標。它為你的記錄集產生一個靜態備份,但其它用戶的新增、刪除、更新操作對你的記錄集來說是不可見的。 }; ④LockType鎖定類型,它可以是以下值之一,請看如下枚舉結構: enum?LockTypeEnum { adLockUnspecified?=?-1,///未指定 adLockReadOnly?=?1,///只讀記錄集 adLockPessimistic?=?2,悲觀鎖定方式。數據在更新時鎖定其它所有動作,這是最安全的鎖定機制 adLockOptimistic?=?3,樂觀鎖定方式。只有在你調用Update方法時才鎖定記錄。在此之前仍然可以做數據的更新、插入、刪除等動作 adLockBatchOptimistic?=?4,樂觀分批更新。編輯時記錄不會鎖定,更改、插入及刪除是在批處理模式下完成。 };? ⑤Options請參考本文中對Connection對象的Execute方法的介紹
【5】記錄集的遍歷、更新 根據我們剛才通過執行SQL命令建立好的users表,它包含四個字段:ID,username,old,birthday 以下的代碼實現:打開記錄集,遍歷所有記錄,刪除第一條記錄,添加三條記錄,移動光標到第二條記錄,更改其年齡,保存到數據庫。
_variant_t?vUsername,vBirthday,vID,vOld; _RecordsetPtr?m_pRecordset; m_pRecordset.CreateInstance("ADODB.Recordset"); m_pRecordset->Open("SELECT?*?FROM?users",_variant_t((IDispatch*)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText); while(!m_pRecordset->adoEOF)///這里為什么是adoEOF而不是EOF呢?還記得rename("EOF","adoEOF")這一句嗎? { ?vID?=?m_pRecordset->GetCollect(_variant_t((long)0));///取得第1列的值,從0開始計數,你也可以直接給出列的名稱,如下一行 ?vUsername?=?m_pRecordset->GetCollect("username");///取得username字段的值 ?vOld?=?m_pRecordset->GetCollect("old"); ?vBirthday?=?m_pRecordset->GetCollect("birthday"); ?///在DEBUG方式下的OUTPUT窗口輸出記錄集中的記錄 ?if(vID.vt?!=?VT_NULL?&&?vUsername.vt?!=?VT_NULL?&&?vOld.vt?!=?VT_NULL?&&?vBirthday.vt?!=?VT_NULL) ??TRACE("id:%d,姓名:%s,年齡:%d,生日:%s\r\n",vID.lVal,(LPCTSTR)(_bstr_t)vUsername,vOld.lVal,(LPCTSTR)(_bstr_t)vBirthday); ?m_pRecordset->MoveNext();///移到下一條記錄 } m_pRecordset->MoveFirst();///移到首條記錄 m_pRecordset->Delete(adAffectCurrent);///刪除當前記錄 ///添加三條新記錄并賦值 for(int?i=0;i<3;i++) { ?m_pRecordset->AddNew();///添加新記錄 ?m_pRecordset->PutCollect("ID",_variant_t((long)(i+10))); ?m_pRecordset->PutCollect("username",_variant_t("葉利欽")); ?m_pRecordset->PutCollect("old",_variant_t((long)71)); ?m_pRecordset->PutCollect("birthday",_variant_t("1930-3-15")); } m_pRecordset->Move(1,_variant_t((long)adBookmarkFirst));///從第一條記錄往下移動一條記錄,即移動到第二條記錄處 m_pRecordset->PutCollect(_variant_t("old"),_variant_t((long)45));///修改其年齡 m_pRecordset->Update();///保存到庫中
【6】關閉記錄集與連接? 記錄集或連接都可以用Close方法來關閉 ??????m_pRecordset->Close();///關閉記錄集 ??????m_pConnection->Close();///關閉連接
至此,我想您已經熟悉了ADO操作數據庫的大致流程,也許您已經胸有成竹,也許您還有點胡涂,不要緊!建議你嘗試寫幾個例子,這樣會更好地熟悉ADO,最后我給大家寫了一個小例子,例子中讀出所有記錄放到列表控件中、并可以添加、刪除、修改記錄。 點這里下載示例代碼
后記:限于篇幅ADO中的許多內容還沒有介紹,下次我們將詳細介紹Recordset對象的屬性、方法并解決幾個關鍵的技術:綁定方式處理記錄集數據、存儲過程的調用、事務處理、圖象在數據庫中的保存與讀取、與表格控件的配合使用等。? 下次再見吧!
Linux 操作系統從一開始就對串行口提供了很好的支持,本文就 Linux 下的串行口通訊編程進行簡單的介紹。
串口簡介
串行口是計算機一種常用的接口,具有連接線少,通訊簡單,得到廣泛的使用。常用的串口是 RS-232-C 接口(又稱 EIA RS-232-C)它是在 1970 年由美國電子工業協會(EIA)聯合貝爾系統、 調制解調器廠家及計算機終端生產廠家共同制定的用于串行通訊的標準。它的全名是"數據終端設備(DTE)和數據通訊設備(DCE)之間串行二進制數據交換接口技術標準"該標準規定采用一個 25 個腳的 DB25 連接器,對連接器的每個引腳的信號內容加以規定,還對各種信號的電平加以規定。傳輸距離在碼元畸變小于 4% 的情況下,傳輸電纜長度應為 50 英尺。
Linux 操作系統從一開始就對串行口提供了很好的支持,本文就 Linux 下的串行口通訊編程進行簡單的介紹,如果要非常深入了解,建議看看本文所參考的 《Serial Programming Guide for POSIX Operating Systems》
計算機串口的引腳說明
序號 |
信號名稱 |
符號 |
流向 |
功能 |
2 |
發送數據 |
TXD |
DTE→DCE |
DTE發送串行數據 |
3 |
接收數據 |
RXD |
DTE←DCE |
DTE 接收串行數據 |
4 |
請求發送 |
RTS |
DTE→DCE |
DTE 請求 DCE 將線路切換到發送方式 |
5 |
允許發送 |
CTS |
DTE←DCE |
DCE 告訴 DTE 線路已接通可以發送數據 |
6 |
數據設備準備好 |
DSR |
DTE←DCE |
DCE 準備好 |
7 |
信號地 |
|
|
信號公共地 |
8 |
載波檢測 |
DCD |
DTE←DCE |
表示 DCE 接收到遠程載波 |
20 |
數據終端準備好 |
DTR |
DTE→DCE |
DTE 準備好 |
22 |
振鈴指示 |
RI |
DTE←DCE |
表示 DCE 與線路接通,出現振鈴 |
串口操作
串口操作需要的頭文件
#include <stdio.h> /*標準輸入輸出定義*/
#include <stdlib.h> /*標準函數庫定義*/
#include <unistd.h> /*Unix 標準函數定義*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h> /*文件控制定義*/
#include <termios.h> /*PPSIX 終端控制定義*/
#include <errno.h> /*錯誤號定義*/
|
打開串口
在 Linux 下串口文件是位于 /dev 下的
串口一 為 /dev/ttyS0
串口二 為 /dev/ttyS1
打開串口是通過使用標準的文件打開函數操作:
int fd;
/*以讀寫方式打開串口*/
fd = open( "/dev/ttyS0", O_RDWR);
if (-1 == fd){
/* 不能打開串口一*/
perror(" 提示錯誤!");
}
|
設置串口
最基本的設置串口包括波特率設置,效驗位和停止位設置。
串口的設置主要是設置 struct termios 結構體的各成員值。
struct termio
{ unsigned short c_iflag; /* 輸入模式標志 */
unsigned short c_oflag; /* 輸出模式標志 */
unsigned short c_cflag; /* 控制模式標志*/
unsigned short c_lflag; /* local mode flags */
unsigned char c_line; /* line discipline */
unsigned char c_cc[NCC]; /* control characters */
};
|
設置這個結構體很復雜,我這里就只說說常見的一些設置:
波特率設置
下面是修改波特率的代碼:
struct termios Opt;
tcgetattr(fd, &Opt);
cfsetispeed(&Opt,B19200); /*設置為19200Bps*/
cfsetospeed(&Opt,B19200);
tcsetattr(fd,TCANOW,&Opt);
|
設置波特率的例子函數:
/**
*@brief 設置串口通信速率
*@param fd 類型 int 打開串口的文件句柄
*@param speed 類型 int 串口速度
*@return void
*/
int speed_arr[] = { B38400, B19200, B9600, B4800, B2400, B1200, B300,
B38400, B19200, B9600, B4800, B2400, B1200, B300, };
int name_arr[] = {38400, 19200, 9600, 4800, 2400, 1200, 300, 38400,
19200, 9600, 4800, 2400, 1200, 300, };
void set_speed(int fd, int speed){
int i;
int status;
struct termios Opt;
tcgetattr(fd, &Opt);
for ( i= 0; i < sizeof(speed_arr) / sizeof(int); i++) {
if (speed == name_arr[i]) {
tcflush(fd, TCIOFLUSH);
cfsetispeed(&Opt, speed_arr[i]);
cfsetospeed(&Opt, speed_arr[i]);
status = tcsetattr(fd1, TCSANOW, &Opt);
if (status != 0) {
perror("tcsetattr fd1");
return;
}
tcflush(fd,TCIOFLUSH);
}
}
}
|
效驗位和停止位的設置:
無效驗 |
8位 |
Option.c_cflag &= ~PARENB; Option.c_cflag &= ~CSTOPB; Option.c_cflag &= ~CSIZE; Option.c_cflag |= ~CS8; |
奇效驗(Odd) |
7位 |
Option.c_cflag |= ~PARENB; Option.c_cflag &= ~PARODD; Option.c_cflag &= ~CSTOPB; Option.c_cflag &= ~CSIZE; Option.c_cflag |= ~CS7; |
偶效驗(Even) |
7位 |
Option.c_cflag &= ~PARENB; Option.c_cflag |= ~PARODD; Option.c_cflag &= ~CSTOPB; Option.c_cflag &= ~CSIZE; Option.c_cflag |= ~CS7; |
Space效驗 |
7位 |
Option.c_cflag &= ~PARENB; Option.c_cflag &= ~CSTOPB; Option.c_cflag &= &~CSIZE; Option.c_cflag |= CS8; |
設置效驗的函數:
/**
*@brief 設置串口數據位,停止位和效驗位
*@param fd 類型 int 打開的串口文件句柄
*@param databits 類型 int 數據位 取值 為 7 或者8
*@param stopbits 類型 int 停止位 取值為 1 或者2
*@param parity 類型 int 效驗類型 取值為N,E,O,,S
*/
int set_Parity(int fd,int databits,int stopbits,int parity)
{
struct termios options;
if ( tcgetattr( fd,&options) != 0) {
perror("SetupSerial 1");
return(FALSE);
}
options.c_cflag &= ~CSIZE;
switch (databits) /*設置數據位數*/
{
case 7:
options.c_cflag |= CS7;
break;
case 8:
options.c_cflag |= CS8;
break;
default:
fprintf(stderr,"Unsupported data size\n"); return (FALSE);
}
switch (parity)
{
case 'n':
case 'N':
options.c_cflag &= ~PARENB; /* Clear parity enable */
options.c_iflag &= ~INPCK; /* Enable parity checking */
break;
case 'o':
case 'O':
options.c_cflag |= (PARODD | PARENB); /* 設置為奇效驗*/
options.c_iflag |= INPCK; /* Disnable parity checking */
break;
case 'e':
case 'E':
options.c_cflag |= PARENB; /* Enable parity */
options.c_cflag &= ~PARODD; /* 轉換為偶效驗*/
options.c_iflag |= INPCK; /* Disnable parity checking */
break;
case 'S':
case 's': /*as no parity*/
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;break;
default:
fprintf(stderr,"Unsupported parity\n");
return (FALSE);
}
/* 設置停止位*/
switch (stopbits)
{
case 1:
options.c_cflag &= ~CSTOPB;
break;
case 2:
options.c_cflag |= CSTOPB;
break;
default:
fprintf(stderr,"Unsupported stop bits\n");
return (FALSE);
}
/* Set input parity option */
if (parity != 'n')
options.c_iflag |= INPCK;
tcflush(fd,TCIFLUSH);
options.c_cc[VTIME] = 150; /* 設置超時15 seconds*/
options.c_cc[VMIN] = 0; /* Update the options and do it NOW */
if (tcsetattr(fd,TCSANOW,&options) != 0)
{
perror("SetupSerial 3");
return (FALSE);
}
return (TRUE);
}
|
需要注意的是:
如果不是開發終端之類的,只是串口傳輸數據,而不需要串口來處理,那么使用原始模式(Raw Mode)方式來通訊,設置方式如下:
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); /*Input*/
options.c_oflag &= ~OPOST; /*Output*/
|
讀寫串口
設置好串口之后,讀寫串口就很容易了,把串口當作文件讀寫就是。
關閉串口
關閉串口就是關閉文件。
例子
下面是一個簡單的讀取串口數據的例子,使用了上面定義的一些函數和頭文件
/**********************************************************************代碼說明:使用串口二測試的,發送的數據是字符,
但是沒有發送字符串結束符號,所以接收到后,后面加上了結束符號。我測試使用的是單片機發送數據到第二個串口,測試通過。
**********************************************************************/
#define FALSE -1
#define TRUE 0
/*********************************************************************/
int OpenDev(char *Dev)
{
int fd = open( Dev, O_RDWR ); //| O_NOCTTY | O_NDELAY
if (-1 == fd)
{
perror("Can't Open Serial Port");
return -1;
}
else
return fd;
}
int main(int argc, char **argv){
int fd;
int nread;
char buff[512];
char *dev = "/dev/ttyS1"; //串口二
fd = OpenDev(dev);
set_speed(fd,19200);
if (set_Parity(fd,8,1,'N') == FALSE) {
printf("Set Parity Error\n");
exit (0);
}
while (1) //循環讀取數據
{
while((nread = read(fd, buff, 512))>0)
{
printf("\nLen %d\n",nread);
buff[nread+1] = '\0';
printf( "\n%s", buff);
}
}
//close(fd);
// exit (0);
}
|
作者:未知 來源:未知 加入時間:2004-7-20 天新軟件園
|
/*==========================*/ /*本程序由sunny編寫,如有傳載*/ /*請注明http://sunny1979.icpcn.com */ /*或者http://tchome.icpcn.com??*/ #include <dos.h> #include <bios.h> #include <stdio.h> #include <math.h> #include <conio.h> #include <graphics.h> #ifdef __cplusplus ????#define __CPPARGS ... #else ????#define __CPPARGS #endif #define SER_RBF????????0??? #define SER_THR????????0??? #define SER_IER????????1???? #define SER_IIR????????2???? #define SER_LCR????????3??? #define SER_MCR????????4???? #define SER_LSR????????5???? #define SER_MSR????????6???? #define SER_DLL????????0???? #define SER_DLH????????1????
#define SER_BAUD_1200??96??? #define SER_BAUD_2400??48 #define SER_BAUD_9600??12 #define SER_BAUD_19200??6 #define SER_GP02????????8????? #define COM_1???????????0x3F8 #define COM_2???????????0x2F8 /*/ base port address of port 1*/ #define SER_STOP_1??????0?????/*/ 1 stop bit per character*/ #define SER_STOP_2??????4?????/*/ 2 stop bits per character*/ #define SER_BITS_5??????0?????/*/ send 5 bit characters*/ #define SER_BITS_6??????1?????/*/ send 6 bit characters*/ #define SER_BITS_7??????2?????/*/ send 7 bit characters*/ #define SER_BITS_8??????3?????/*/ send 8 bit characters*/ #define SER_PARITY_NONE 0?????/*/ no parity*/ #define SER_PARITY_ODD??8?????/*/ odd parity*/ #define SER_PARITY_EVEN 24????/*/ even parity*/ #define SER_DIV_LATCH_ON 128??/*/ used to turn reg 0,1 into divisor latch*/ #define PIC_IMR????0x21???/*/ pic's interrupt mask reg.*/ #define PIC_ICR????0x20???/*/ pic's interupt control reg.*/ #define INT_SER_PORT_0????0x0C??/*/ port 0 interrupt com 1 & 3*/ #define INT_SER_PORT_1????0x0B??/*/ port 0 interrupt com 2 & 4*/ #define SERIAL_BUFF_SIZE 128????/*/ current size of circulating receive buffer*/
void interrupt far (*Old_Isr)(__CPPARGS);??/*/ holds old com port interrupt handler*/
char ser_buffer[SERIAL_BUFF_SIZE];??/*/ the receive buffer*/
int ser_end = -1,ser_start=-1;??????/*/ indexes into receive buffer*/ int ser_ch, char_ready=0;???????????/*/ current character and ready flag*/ int old_int_mask;???????????????????/*/ the old interrupt mask on the PIC*/ int open_port;??????????????????????/*/ the currently open port*/ int serial_lock = 0;????????????????/*/ serial ISR semaphore so the buffer*/ ????????/*/ isn't altered will it is being written*/ ????????????????????????????????????/*/ to by the ISR*/
/*-------------寫串口-----------------*/?? void interrupt far Serial_Isr(__CPPARGS) { serial_lock = 1; ser_ch = inp(open_port + SER_RBF); if (++ser_end > SERIAL_BUFF_SIZE-1) ????ser_end = 0; ser_buffer[ser_end] = ser_ch;
++char_ready; outp(PIC_ICR,0x20); serial_lock = 0;
}
int Ready_Serial() { return(char_ready);
}
/*--------------讀串口--------------*/
int Serial_Read() { int ch; while(serial_lock){} if (ser_end != ser_start) ???{ ???if (++ser_start > SERIAL_BUFF_SIZE-1) ???????ser_start = 0; ???ch = ser_buffer[ser_start]; ??if (char_ready > 0) ???????--char_ready; ???return(ch);
???} else ???return(0);
}
/*--------------寫串口-----------------*/ Serial_Write(char ch) { while(!(inp(open_port + SER_LSR) & 0x20)){} asm cli outp(open_port + SER_THR, ch); asm sti }
/*-----------初始化串口---------------*/ Open_Serial(int port_base, int baud, int configuration) { open_port = port_base; outp(port_base + SER_LCR, SER_DIV_LATCH_ON); outp(port_base + SER_DLL, baud); outp(port_base + SER_DLH, 0); outp(port_base + SER_LCR, configuration); outp(port_base + SER_MCR, SER_GP02); outp(port_base + SER_IER, 1); if (port_base == COM_1) ???{ ???Old_Isr = _dos_getvect(INT_SER_PORT_0); ???_dos_setvect(INT_SER_PORT_0, Serial_Isr); ???printf("\nOpening Communications Channel Com Port #1...\n");
???} else ???{ ???Old_Isr = _dos_getvect(INT_SER_PORT_1); ???_dos_setvect(INT_SER_PORT_1, Serial_Isr); ???printf("\nOpening Communications Channel Com Port #2...\n"); ???} old_int_mask = inp(PIC_IMR); outp(PIC_IMR, (port_base==COM_1) ? (old_int_mask & 0xEF) : (old_int_mask & 0xF7 )); } /*-------------關閉串口--------------*/ Close_Serial(int port_base) { outp(port_base + SER_MCR, 0); outp(port_base + SER_IER, 0); outp(PIC_IMR, old_int_mask ); if (port_base == COM_1) ???{ ???_dos_setvect(INT_SER_PORT_0, Old_Isr); ???printf("\nClosing Communications Channel Com Port #1.\n"); ???} else ???{ ???_dos_setvect(INT_SER_PORT_1, Old_Isr); ???printf("\nClosing Communications Channel Com Port #2.\n"); ???}
}
/*-------------發送應用----------------*/
void main(int argc,char *argv[]) {
char ch,press; int done=0; FILE *fp; argc=2; argv[1]="test.cpp"; if(argc<2) { ??printf("\nUsage:display filename.wav!!!"); ??exit(0); } if((fp=fopen(argv[1],"r+b"))==NULL) { ??printf("cannot open the file\n"); ??exit(0); } fseek(fp, 0, SEEK_SET); Open_Serial(COM_1,SER_BAUD_9600,SER_PARITY_NONE | SER_BITS_8 | SER_STOP_1); printf("press any key to begin sending"); getch(); Serial_Write(' '); while(!done&&ch != EOF) ?????{ ch = fgetc(fp); if(ch==EOF) Serial_Write(27); Serial_Write(ch); if (kbhit()) { ??press=getch(); ??if (press==27) ??{ ???Serial_Write(27); ???done=1; ??} } ?????}?? Close_Serial(COM_1); fclose(fp); }
|
一、基本知識
Win32下串口通信與16位串口通信有很大的區別。在Win32下,可以使用兩種編程方式實現串口通信,其一是調用的Windows的API函數,其二是使用ActiveX控件。使用API 調用,可以清楚地掌握串口通信的機制,熟悉各種配置和自由靈活采用不同的流控進行串口通信。下面介紹串口操作的基本知識。
打開串口:使用CreateFile()函數,可以打開串口。有兩種方法可以打開串口,一種是同步方式(NonOverlapped),另外一種異步方式(Overlapped)。使用Overlapped打開時,適當的方法是:
HANDLE hComm; hComm = CreateFile( gszPort, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0); if (hComm == INVALID_HANDLE_value) // error opening port; abort 配置串口:
1.DCB配置
DCB(Device Control Block)結構定義了串口通信設備的控制設置。許多重要設置都是在DCB結構中設置的,有三種方式可以初始化DCB。
(1)通過GetCommState()函數得DCB的初始值,其使用方式為:
DCB dcb = {0}; if (!GetCommState(hComm, &dcb)) // Error getting current DCB settings else // DCB is ready for use.
(2)用BuildCommDCB()函數初始化DCB結構,該函數填充 DCB的波特率、奇偶校驗類型、數據位、停止位。對于流控成員函數設置了缺省值。其用法是:
DCB dcb; FillMemory(&dcb, sizeof(dcb), 0); dcb.DCBlength = sizeof(dcb); if (!BuildCommDCB(“9600,n,8,1", &dcb)) { // Couldn't build the DCB. Usually a problem // with the communications specification string. return FALSE; } else // DCB is ready for use.
(3)用SetCommState()函數手動設置DCB初值。用法如下:
DCB dcb; FillMemory(&dcb, sizeof(dcb), 0); if (!GetCommState(hComm, &dcb)) // get current DCB // Error in GetCommState return FALSE; // Update DCB rate. dcb.BaudRate = CBR_9600 ; // Set new state. if (!SetCommState(hComm, &dcb)) // Error in SetCommState. Possibly a problem with the communications // port handle or a problem with the DCB structure itself.
手動設置DCB值時,DCB的結構的各成員的含義,可以參看MSDN幫助。
2.流控設置
硬件流控:串口通信中的硬件流控有兩種,DTE/DSR方式和RTS/CTS方式,這與DCB結構的初始化有關系,DCB結構中的OutxCtsFlow、 fOutxDsrFlow、fDsrSensitivity、fRtsControl、fDtrControl幾個成員的初始值很關鍵,不同的值代表不同流控,也可以自己設置流控,但建議采用標準流行的流控方式。采用硬件流控時,DTE、DSR、RTS、CTS的邏輯位直接影響到數據的讀寫及收發數據的緩沖區控制。
軟件流控:串口通信中采用特殊字符XON和XOFF作為控制串口數據的收發。與此相關的DCB成員是:fOut、fInX、XoffChar、XonChar、 XoffLim和XonLim。具體含義參見MSDN幫助。
串口讀寫操作:串口讀寫有兩種方式:同步方式(NonOverlapped)和異步方式(Overlapped)。同步方式是指必須完成了讀寫操作,函數才返回,這可能造成程序死掉,因為如果在讀寫時發生了錯誤,永遠不返回就會出錯,可能線程將永遠等待在那兒。而異步方式則靈活得多,一旦讀寫不成功,就將讀寫掛起,函數直接返回,可以通過GetLastError函數得知讀寫未成功的原因,所以常常采用異步方式操作。
讀操作:ReadFile()函數用于完成讀操作。異步方式的讀操作為:
DWORD dwRead; BOOL fWaitingOnRead = FALSE; OVERLAPPED osReader = {0}; // Create the overlapped event. Must be closed before exiting // to avoid a handle leak. osReader.hEvent = CreateEvent (NULL, TRUE, FALSE, NULL); if (osReader.hEvent == NULL) // Error creating overlapped event; abort. if (!fWaitingOnRead) { // Issue read operation. if (!ReadFile(hComm, lpBuf, READ_BUF_SIZE, &dwRead, &osReader)) { if (GetLastError() != ERROR_IO_PENDING) // read not delayed? // Error in communications; report it. else fWaitingOnRead = TRUE; } else { // read completed immediately HandleASuccessfulRead(lpBuf, dwRead); } }
如果讀操作被掛起,可以調用WaitForSingleObject()函數或WaitForMuntilpleObjects()函數等待讀操作完成或者超時發生,再調用 GetOverlappedResult()得到想要的信息。
寫操作:與讀操作相似,故不詳述,調用的API函數是: WriteFile函數。
串口狀態:
(1)通信事件:用SetCommMask()函數設置想要得到的通信事件的掩碼,再調用WaitCommEvent()函數檢測通信事件的發生。可設置的通信事件標志(即SetCommMask()函數所設置的掩碼)可以有EV_BREAK、EV_CTS、EV_DSR、 EV_ERR、EV_RING、EV_RLSD、EV_RXCHAR、EV_RXFLAG、EV_TXEMPTY。
注意:1對于EV_RING標志的設置,WIN95是不會返回EV_RING事件的,因為WIN95不檢測該事件。2設置EV_RXCHAR,可以檢測到字符到達,但是在綁定此事件和ReadFile()函數一起讀取串口接收數據時,可能會出現錯誤,造成少讀字節數,具體原因查看MSDN幫助。可以采用循環讀的辦法,另外一個比較好的解決辦法是調用ClearCommError()函數,確定在一次讀操作中在緩沖區中等待被讀的字節數。
(2)錯誤處理和通信狀態:在串口通信中,可能會產生很多的錯誤,使用ClearCommError()函數可以檢測錯誤并且清除錯誤條件。
(3)Modem狀態:用SetcommMask()可以包含很多事件標志,但是這些事件標志只指示在串口線路上的電壓變化情況。而調用 GetCommModemStatus()函數可以獲得線路上真正的電壓狀態。
擴展函數:如果應用程序想用自己的流控,可以使用 EscapeCommFunction()函數設置DTR和RTS線路的電平。
通信超時:在通信中,超時是個很重要的考慮因素,因為如果在數據接收過程中由于某種原因突然中斷或停止,如果不采取超時控制機制,將會使得I/O線程被掛起或無限阻塞。串口通信中的超時設置分為兩步,首先設置 COMMTIMEOUTS結構的五個變量,然后調用SetcommTimeouts()設置超時值。對于使用異步方式讀寫的操作,如果操作掛起后,異步成功完成了讀寫,WaitForSingleObject()或 WaitForMultipleObjects()函數將返回WAIT_OBJECT_0,GetOverlappedResult()返回TRUE。其實還可以用GetCommTimeouts()得到系統初始值。
關閉串口:程序結束或需要釋放串口資源時,應該正確關閉串口,關閉串口比較簡單,使用API調用CloseHandle()關閉串口的句柄就可以了。
調用方法為:CloseHandle(hComm);
但是值得注意的是在關閉串口之前必須保證讀寫串口線程已經退出,否則會引起誤操作,一般采用的辦法是使用事件驅動機制,啟動一事件,通知串口讀寫線程強制退出,在線程退出之前,通知主線程可以關閉串口。
二、實現
1.程序設計思路
對于不同的應用程序,雖然界面不同,但是如果采用串口與主機之間的通信,對串口的處理方式大致相似,無非就是通過串口收發數據,對于通過串口接收到的數據,交給上層軟件處理顯示,對于上層要發給串口的數據,進行轉發。但在實際編程中,由于采用的通信方式和流控不同,串口設置也不同,這就涉及到 DCB的初始化問題和讀寫串口等細節問題。串口通信應用程序設計的總體思路(即操作過程)是:首先,確定要打開的串口名、波特率、奇偶校驗方式、數據位、停止位,傳遞給CreateFile()函數打開特定串口;其次,為了保護系統對串口的初始設置,調用 GetCommTimeouts()得到串口的原始超時設置;然后,初始化DCB對象,調用SetCommState() 設置DCB,調用SetCommTimeouts()設置串口超時控制;再次,調用SetupComm()設置串口接收發送數據的緩沖區大小,串口的設置就基本完成,之后就可以啟動讀寫線程了。
一般來說,串口的讀寫由串口讀寫線程完成,這樣可以避免讀寫阻塞時主程序死鎖。對于全雙工的串口讀寫,應該分別開啟讀線程和寫線程;對于半雙工和單工的,建議只需開啟一個線程即可。在線程中,按照預定好的通信握手方式,正確檢測串口狀態,讀取發送串口數據。
2.實現細節
在半雙工的情況下,首先完成必要的串口配置,成功打開串口、DCB設置、超時設置;然后開啟線程,如: CwinThread hSerialThread = (CWinThread*) AfxBeginThread(SerialOperation,hWnd,THREAD_PRIORITY_NORMAL); 其中開啟之線程為SerialOperation,優先級為普通。
全雙工情況下的串口編程,與單工差不多,區別僅僅在于啟動雙線程,分別為讀線程和寫線程,讀線程根據不同的事件或消息,通過不斷查詢串口所收到的有效數據,完成讀操作;寫線程通過接收主線程的發送數據事件和要發送的數據,向串口發送。
本文詳細介紹了串行通信的基本原理,以及在Windows NT、Win98環境下用MFC實現串口(COM)通信的方法:使用ActiveX控件或Win API.并給出用Visual C++6.0編寫的相應MFC32位應用程序。關鍵詞:串行通信、VC++6.0、ActiveX控件、Win API、MFC32位應用程序、事件驅動、非阻塞通信、多線程.
在Windows應用程序的開發中,我們常常需要面臨與外圍數據源設備通信的問題。計算機和單片機(如MCS-51)都具有串行通信口,可以設計相應的串口通信程序,完成二者之間的數據通信任務。
實際工作中利用串口完成通信任務的時候非常之多。已有一些文章介紹串口編程的文章在計算機雜志上發表。但總的感覺說來不太全面,特別是介紹32位下編程的更少,且很不詳細。筆者在實際工作中積累了較多經驗,結合硬件、軟件,重點提及比較新的技術,及需要注意的要點作一番探討。希望對各位需要編寫串口通信程序的朋友有一些幫助。
一.串行通信的基本原理
串行端口的本質功能是作為CPU和串行設備間的編碼轉換器。當數據從 CPU經過串行端口發送出去時,字節數據轉換為串行的位。在接收數據時,串行的位被轉換為字節數據。
在Windows環境(Windows NT、Win98、Windows2000)下,串口是系統資源的一部分。
應用程序要使用串口進行通信,必須在使用之前向操作系統提出資源申請要求(打開串口),通信完成后必須釋放資源(關閉串口)。
串口通信程序的流程如下圖:
二.串口信號線的接法
一個完整的RS-232C接口有22根線,采用標準的25芯插頭座(或者9芯插頭座)。25芯和9芯的主要信號線相同。以下的介紹是以25芯的RS-232C為例。
①主要信號線定義:
2腳:發送數據TXD; 3腳:接收數據RXD; 4腳:請求發送RTS; 5腳:清除發送CTS;
6腳:數據設備就緒DSR;20腳:數據終端就緒DTR; 8腳:數據載波檢測DCD;
1腳:保護地; 7腳:信號地。
②電氣特性:
數據傳輸速率最大可到20K bps,最大距離僅15m.
注:看了微軟的MSDN 6.0,其Windows API中關于串行通訊設備(不一定都是串口RS-232C或RS-422或RS-449)速率的設置,最大可支持到RS_256000,即256K bps! 也不知道到底是什么串行通訊設備?但不管怎樣,一般主機和單片機的串口通訊大多都在9600 bps,可以滿足通訊需求。
③接口的典型應用:
大多數計算機應用系統與智能單元之間只需使用3到5根信號線即可工作。這時,除了TXD、RXD以外,還需使用RTS、CTS、DCD、DTR、DSR等信號線。(當然,在程序中也需要對相應的信號線進行設置。)

圖 最簡單的RS232-C信號線接法
以上接法,在設計程序時,直接進行數據的接收和發送就可以了,不需要 對信號線的狀態進行判斷或設置。(如果應用的場合需要使用握手信號等,需要對相應的信號線的狀態進行監測或設置。)
三.16位串口應用程序的簡單回顧
16位串口應用程序中,使用的16位的Windows API通信函數:
① OpenComm() 打開串口資源,并指定輸入、輸出緩沖區的大小(以字節計);
CloseComm() 關閉串口;
例:int idComDev;
idComDev = OpenComm("COM1", 1024, 128);
CloseComm(idComDev);
② BuildCommDCB() 、setCommState()填寫設備控制塊DCB,然后對已打開的串口進行參數配置;
例:DCB dcb;
BuildCommDCB("COM1:2400,n,8,1", &dcb);
SetCommState(&dcb);
③ ReadComm 、WriteComm()對串口進行讀寫操作,即數據的接收和發送.
例:char *m_pRecieve; int count;
ReadComm(idComDev,m_pRecieve,count);
Char wr[30]; int count2;
WriteComm(idComDev,wr,count2);
16位下的串口通信程序最大的特點就在于:串口等外部設備的操作有自己特有的API函數;而32位程序則把串口操作(以及并口等)和文件操作統一起來了,使用類似的操作。
四.在MFC下的32位串口應用程序
32位下串口通信程序可以用兩種方法實現:利用ActiveX控件;使用API 通信函數。
使用ActiveX控件,程序實現非常簡單,結構清晰,缺點是欠靈活;使用API 通信函數的優缺點則基本上相反。
以下介紹的都是在單文檔(SDI)應用程序中加入串口通信能力的程序。
㈠ 使用ActiveX控件:
VC++ 6.0提供的MSComm控件通過串行端口發送和接收數據,為應用程序提供串行通信功能。使用非常方便,但可惜的是,很少有介紹MSComm控件的資料。
⑴.在當前的Workspace中插入MSComm控件。
Project菜單------>Add to Project---->Components and Controls----->Registered
ActiveX Controls--->選擇Components: Microsoft Communications Control,
version 6.0 插入到當前的Workspace中。
結果添加了類CMSComm(及相應文件:mscomm.h和mscomm.cpp )。
⑵.在MainFrm.h中加入MSComm控件。
protected:
CMSComm m_ComPort;
在Mainfrm.cpp::OnCreare()中:
DWORD style=WS_VISIBLE|WS_CHILD;
if (!m_ComPort.Create(NULL,style,CRect(0,0,0,0),this,ID_COMMCTRL)){
TRACE0("Failed to create OLE Communications Control\n");
return -1; // fail to create
}
⑶.初始化串口
m_ComPort.SetCommPort(1); //選擇COM?
m_ComPort. SetInBufferSize(1024); //設置輸入緩沖區的大小,Bytes
m_ComPort. SetOutBufferSize(512); //設置輸入緩沖區的大小,Bytes//
if(!m_ComPort.GetPortOpen()) //打開串口
m_ComPort.SetPortOpen(TRUE);
m_ComPort.SetInputMode(1); //設置輸入方式為二進制方式
m_ComPort.SetSettings("9600,n,8,1"); //設置波特率等參數
m_ComPort.SetRThreshold(1); //為1表示有一個字符引發一個事件
m_ComPort.SetInputLen(0);
⑷.捕捉串口事項。MSComm控件可以采用輪詢或事件驅動的方法從端口獲取數據。我們介紹比較使用的事件驅動方法:有事件(如接收到數據)時通知程序。在程序中需要捕獲并處理這些通訊事件。
在MainFrm.h中:
protected:
afx_msg void OnCommMscomm();
DECLARE_EVENTSINK_MAP()
在MainFrm.cpp中:
BEGIN_EVENTSINK_MAP(CMainFrame,CFrameWnd )
ON_EVENT(CMainFrame,ID_COMMCTRL,1,OnCommMscomm,VTS_NONE)
//映射ActiveX控件事件
END_EVENTSINK_MAP()
⑸.串口讀寫. 完成讀寫的函數的確很簡單,GetInput()和SetOutput()就可。兩個函數的原型是:
VARIANT GetInput();及 void SetOutput(const VARIANT& newValue);都要使用VARIANT類型(所有Idispatch::Invoke的參數和返回值在內部都是作為VARIANT對象處理的)。
無論是在PC機讀取上傳數據時還是在PC機發送下行命令時,我們都習慣于使用字符串的形式(也可以說是數組形式)。查閱VARIANT文檔知道,可以用BSTR表示字符串,但遺憾的是所有的BSTR都是包含寬字符,即使我們沒有定義_UNICODE_UNICODE也是這樣! WinNT支持寬字符, 而Win95并不支持。為解決上述問題,我們在實際工作中使用CbyteArray,給出相應的部分程序如下:
void CMainFrame::OnCommMscomm(){
VARIANT vResponse; int k;
if(m_commCtrl.GetCommEvent()==2) {
k=m_commCtrl.GetInBufferCount(); //接收到的字符數目
if(k>0) {
vResponse=m_commCtrl.GetInput(); //read
SaveData(k,(unsigned char*) vResponse.parray->pvData);
} // 接收到字符,MSComm控件發送事件 }
。。。。。 // 處理其他MSComm控件
}
void CMainFrame::OnCommSend() {
。。。。。。。。 // 準備需要發送的命令,放在TxData[]中
CByteArray array;
array.RemoveAll();
array.SetSize(Count);
for(i=0;i<Count;i++)
array.SetAt(i, TxData[i]);
m_ComPort.SetOutput(COleVariant(array)); // 發送數據
}
請大家認真關注第⑷、⑸中內容,在實際工作中是重點、難點所在。
㈡ 使用32位的API 通信函數:
可能很多朋友會覺得奇怪:用32位API函數編寫串口通信程序,不就是把16位的API換成32位嗎?16位的串口通信程序可是多年之前就有很多人研討過了……
此文主要想介紹一下在API串口通信中如何結合非阻塞通信、多線程等手段,編寫出高質量的通信程序。特別是在CPU處理任務比較繁重、與外圍設備中有大量的通信數據時,更有實際意義。
⑴.在中MainFrm.cpp定義全局變量
HANDLE hCom; // 準備打開的串口的句柄
HANDLE hCommWatchThread ;//輔助線程的全局函數
⑵.打開串口,設置串口
hCom =CreateFile( "COM2", GENERIC_READ | GENERIC_WRITE, // 允許讀寫
0, // 此項必須為0
NULL, // no security attrs
OPEN_EXISTING, //設置產生方式
FILE_FLAG_OVERLAPPED, // 我們準備使用異步通信
NULL );
請大家注意,我們使用了FILE_FLAG_OVERLAPPED結構。這正是使用API實現非阻塞通信的關鍵所在。
ASSERT(hCom!=INVALID_HANDLE_VALUE); //檢測打開串口操作是否成功
SetCommMask(hCom, EV_RXCHAR|EV_TXEMPTY );//設置事件驅動的類型
SetupComm( hCom, 1024,512) ; //設置輸入、輸出緩沖區的大小
PurgeComm( hCom, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR
| PURGE_RXCLEAR ); //清干凈輸入、輸出緩沖區
COMMTIMEOUTS CommTimeOuts ; //定義超時結構,并填寫該結構
…………
SetCommTimeouts( hCom, &CommTimeOuts ) ;//設置讀寫操作所允許的超時
DCB dcb ; // 定義數據控制塊結構
GetCommState(hCom, &dcb ) ; //讀串口原來的參數設置
dcb.BaudRate =9600; dcb.ByteSize =8; dcb.Parity = NOPARITY;
dcb.StopBits = ONESTOPBIT ;dcb.fBinary = TRUE ;dcb.fParity = FALSE;
SetCommState(hCom, &dcb ) ; //串口參數配置
上述的COMMTIMEOUTS結構和DCB都很重要,實際工作中需要仔細選擇參數。
⑶啟動一個輔助線程,用于串口事件的處理。
Windows提供了兩種線程,輔助線程和用戶界面線程。區別在于:輔助線程沒有窗口,所以它沒有自己的消息循環。但是輔助線程很容易編程,通常也很有用。
在次,我們使用輔助線程。主要用它來監視串口狀態,看有無數據到達、通信有無錯誤;而主線程則可專心進行數據處理、提供友好的用戶界面等重要的工作。
hCommWatchThread=
CreateThread( (LPSECURITY_ATTRIBUTES) NULL, //安全屬性
0,//初始化線程棧的大小,缺省為與主線程大小相同
(LPTHREAD_START_ROUTINE)CommWatchProc, //線程的全局函數
GetSafeHwnd(), //此處傳入了主框架的句柄
0, &dwThreadID );
ASSERT(hCommWatchThread!=NULL);
⑷為輔助線程寫一個全局函數,主要完成數據接收的工作。請注意OVERLAPPED結構的使用,以及怎樣實現了非阻塞通信。
UINT CommWatchProc(HWND hSendWnd){
DWORD dwEvtMask=0 ;
SetCommMask( hCom, EV_RXCHAR|EV_TXEMPTY );//有哪些串口事件需要監視?
WaitCommEvent( hCom, &dwEvtMask, os );// 等待串口通信事件的發生
檢測返回的dwEvtMask,知道發生了什么串口事件:
if ((dwEvtMask & EV_RXCHAR) == EV_RXCHAR){ // 緩沖區中有數據到達
COMSTAT ComStat ; DWORD dwLength;
ClearCommError(hCom, &dwErrorFlags, &ComStat ) ;
dwLength = ComStat.cbInQue ; //輸入緩沖區有多少數據?
if (dwLength > 0) {
BOOL fReadStat ;
fReadStat = ReadFile( hCom, lpBuffer,dwLength, &dwBytesRead,
&READ_OS( npTTYInfo ) ); //讀數據
注:我們在CreareFile()時使用了FILE_FLAG_OVERLAPPED,現在ReadFile()也必須使用
LPOVERLAPPED結構.否則,函數會不正確地報告讀操作已完成了.
使用LPOVERLAPPED結構, ReadFile()立即返回,不必等待讀操作完成,實現非阻塞
通信.此時, ReadFile()返回FALSE, GetLastError()返回ERROR_IO_PENDING.
if (!fReadStat){
if (GetLastError() == ERROR_IO_PENDING){
while(!GetOverlappedResult(hCom,
&READ_OS( npTTYInfo ), & dwBytesRead, TRUE )){
dwError = GetLastError();
if(dwError == ERROR_IO_INCOMPLETE) continue;
//緩沖區數據沒有讀完,繼續
…… ……
::PostMessage((HWND)hSendWnd,WM_NOTIFYPROCESS,0,0);//通知主線程,串口收到數據 }
所謂的非阻塞通信,也即異步通信。是指在進行需要花費大量時間的數據讀寫操作(不僅僅是指串行通信操作)時,一旦調用ReadFile()、WriteFile(), 就能立即返回,而讓實際的讀寫操作在后臺運行;相反,如使用阻塞通信,則必須在讀或寫操作全部完成后才能返回。由于操作可能需要任意長的時間才能完成,于是問題就出現了。
非常阻塞操作還允許讀、寫操作能同時進行(即重疊操作?),在實際工作中非常有用。
要使用非阻塞通信,首先在CreateFile()時必須使用FILE_FLAG_OVERLAPPED;然后在 ReadFile()時lpOverlapped參數一定不能為NULL,接著檢查函數調用的返回值,調用GetLastError(),看是否返回ERROR_IO_PENDING。如是,最后調用GetOverlappedResult()返回重疊操作(overlapped operation)的結果;WriteFile()的使用類似。
⑸.在主線程中發送下行命令。
BOOL fWriteStat ; char szBuffer[count];
…………//準備好發送的數據,放在szBuffer[]中
fWriteStat = WriteFile(hCom, szBuffer, dwBytesToWrite,
&dwBytesWritten, &WRITE_OS( npTTYInfo ) ); //寫數據
注:我們在CreareFile()時使用了FILE_FLAG_OVERLAPPED,現在WriteFile()也必須使用 LPOVERLAPPED結構.否則,函數會不正確地報告寫操作已完成了.
使用LPOVERLAPPED結構,WriteFile()立即返回,不必等待寫操作完成,實現非阻塞 通信.此時, WriteFile()返回FALSE, GetLastError()返回ERROR_IO_PENDING.
int err=GetLastError();
if (!fWriteStat) {
if(GetLastError() == ERROR_IO_PENDING){
while(!GetOverlappedResult(hCom, &WRITE_OS( npTTYInfo ),
&dwBytesWritten, TRUE )) {
dwError = GetLastError();
if(dwError == ERROR_IO_INCOMPLETE){
// normal result if not finished
dwBytesSent += dwBytesWritten; continue; }
......................
綜上,我們使用了多線程技術,在輔助線程中監視串口,有數據到達時依靠事件驅動,讀入數據并向主線程報告(發送數據在主線程中,相對說來,下行命令的數據總是少得多);并且,WaitCommEvent()、ReadFile()、WriteFile()都使用了非阻塞通信技術,依靠重疊(overlapped)讀寫操作,讓串口讀寫操作在后臺運行。
依托vc6.0豐富的功能,結合我們提及的技術,寫出有強大控制能力的串口通信應用程序。就個人而言,我更偏愛API技術,因為控制手段要靈活的多,功能也要強大得多。
|