LabVIEW外部調(diào)用DLL資料_第1頁
LabVIEW外部調(diào)用DLL資料_第2頁
LabVIEW外部調(diào)用DLL資料_第3頁
LabVIEW外部調(diào)用DLL資料_第4頁
LabVIEW外部調(diào)用DLL資料_第5頁
已閱讀5頁,還剩10頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認(rèn)領(lǐng)

文檔簡介

LabVIEW與外部程序間DLL文件的調(diào)用什么是DLL文件DLL(動態(tài)鏈接庫)文件是DynamicLinkLibrary的縮寫形式,是一種允許程序共享執(zhí)行特殊任務(wù)所必需的代碼和其他資源的可執(zhí)行文件。其多數(shù)情況下是帶有DLL擴展名的文件,但也可能是EXE或其他擴展名。Windows提供的DLL文件中包含了允許基于Windows的程序在Windows環(huán)境下操作的許多函數(shù)和資源。動態(tài)鏈接提供了一種方法,使進程可以調(diào)用不屬于其可執(zhí)行代碼的函數(shù)。這些函數(shù)的可執(zhí)行代碼位于一個DLL中,該DLL包含一個或多個已被編譯、鏈接并與使用它們的進程分開存儲的函數(shù)。DLL還有助于共享數(shù)據(jù)和資源,多個應(yīng)用程序可同時訪問內(nèi)存中單DLL副本的內(nèi)容??傊?,DLL是一個包含可由多個程序同時使用的代碼和數(shù)據(jù)的庫。動態(tài)鏈接是相對于靜態(tài)鏈接而言的。所謂靜態(tài)鏈接是指把要調(diào)用的函數(shù)或者過程鏈接到可執(zhí)行文件中,成為可執(zhí)行文件的一部分。換句話說,函數(shù)和過程的代碼就在程序的exe文件中,該文件包含了運行時所需的全部代碼。當(dāng)多個程序都調(diào)用相同函數(shù)時,內(nèi)存中就會存在這個函數(shù)的多個拷貝,這樣就浪費了寶貴的內(nèi)存資源。而動態(tài)鏈接所調(diào)用的函數(shù)代碼并沒有被拷貝到應(yīng)用程序的可執(zhí)行文件中去,而是僅僅在其中加入了所調(diào)用函數(shù)的描述信息(往往是一些重定位信息)。僅當(dāng)應(yīng)用程序被裝入內(nèi)存開始運行時,在Windows的管理下,才在應(yīng)用程序與相應(yīng)的DLL之間建立鏈接關(guān)系。當(dāng)要執(zhí)行所調(diào)用DLL中的函數(shù)時,根據(jù)鏈接產(chǎn)生的重定位信息,Windows才轉(zhuǎn)去執(zhí)行DLL中相應(yīng)的函數(shù)代碼。由于向運行于Windows操作系統(tǒng)下的程序提供代碼、數(shù)據(jù)或函數(shù),程序可根據(jù)DLL文件中的指令打開、啟用、查詢、禁用和關(guān)閉驅(qū)動程序。在Windows操作系統(tǒng)中,DLL對于程序執(zhí)行是非常重要的,因為程序在執(zhí)行的時候,必須鏈接到DLL文件,才能夠正確地運行。而有些DLL文件可以被許多程序共用,因此程序設(shè)計人員可以利用DLL文件,使程序不至于太過巨大。但是當(dāng)安裝的程序越來越多,DLL文件也就會越來越多,如果當(dāng)刪除程序的時候,沒有用的DLL文件沒有被刪除的話,久而久之就造成系統(tǒng)的負擔(dān)了。通過使用DLL,程序可以實現(xiàn)模塊化,由相對獨立的組件組成。因為模塊是彼此獨立的,所以程序的加載速度更快,而且模塊只在相應(yīng)的功能被請求時才加載。此外,可以更為容易地將更新應(yīng)用于各個模塊,而不會影響該程序的其他部分。例如,可能有一個工資計算程序,稅率每年都會更改,當(dāng)這些更改被隔離到DLL中以后,我們無需重新生成或安裝整個程序就可以應(yīng)用更新。總的來說,使用DLL文件有以下好處:1、多個應(yīng)用程序可以共享代碼和數(shù)據(jù)。比如Office軟件的各個組成部分有相似的外觀和功能,這就是通過共享動態(tài)鏈接庫實現(xiàn)的。2、在鉤子程序過濾系統(tǒng)消息時必須使用動態(tài)鏈接庫。3、動態(tài)鏈接庫以一種自然的方式將一個大的應(yīng)用程序劃分為幾個小的模塊,有利于小組內(nèi)部成員的分工與合作,而且各個模塊可以獨立升級。如果小組中的一個成員開發(fā)了一組實用例程,他就可以把這些例程放在一個動態(tài)鏈接庫中,供其他成員使用。4、實現(xiàn)應(yīng)用程序的國際化,往往需要使用動態(tài)鏈接庫。使用動態(tài)鏈接庫可以將針對某一國家、語言的信息存放在其中。例如,在使用AppWizard生成應(yīng)用程序時,我們可以指定資源文件使用的語言,這就是通過提供不同的動態(tài)鏈接庫實現(xiàn)的。對于不同的版本,使用不同的動態(tài)鏈接庫,常用的一些編程軟件均可以編寫DLL文件。DLL不是獨立運行的程序,而是某個程序的一個部分,它只能由所屬的程序調(diào)用,用戶不能,也不需要打開它。LabVIEW調(diào)用DLL文件LabVIEW支持通過調(diào)用DLL文件的方式與其它編程語言混合使用。比如,在實際的工程項目中,用戶可以用C++語言實現(xiàn)軟件的運算部分,并把這些功能構(gòu)建在DLL文件中,然后再使用LabVIEW編寫程序的界面部分,并通過調(diào)用編寫好的DLL來調(diào)用運算部分的功能。LabVIEW中是通過CallLibraryFunctionNode(CLN)節(jié)點來完成DLL文件調(diào)用的。創(chuàng)建一個新的VI,右擊程序框圖,在FunctionsPalette中依次選中Connectivity——Libraries&Executables工具欄即可找到該節(jié)點(圖1)。圖1CallLibraryFunctionNode將節(jié)點放置在程序框圖中,雙擊會出現(xiàn)它的配置對話框,共有四頁。第一頁用于填寫被調(diào)用函數(shù)的信息(圖2)。Librarynameorpath需給出DLL文件名和路徑,操作系統(tǒng)路徑下的DLL文件,直接輸入文件名也可調(diào)用,否則必須輸入全路徑。在這里已經(jīng)給出名字的DLL是被靜態(tài)加載到程序中的,也就是說當(dāng)調(diào)用了這個DLL的VI被裝入內(nèi)存時,DLL同時被裝入內(nèi)存。LabVIEW也可動態(tài)加載DLL,只要勾選上Specifypathondiagram的選項即可。選擇了這個選項,在Librarynameorpath中輸入的內(nèi)容就無效了,取而代之的是CLN節(jié)點多出一對輸入輸出,用于指明所需要使用的DLL的路徑。這樣,當(dāng)VI被打開時,DLL不會被裝入內(nèi)存,只用程序運行到需要使用這個DLL中的函數(shù)時,才把其裝入內(nèi)存。Functionname是需要調(diào)用的函數(shù)的名稱,LabVIEW會把DLL中所有的暴露出來的函數(shù)都列出,用戶只要在下拉框中選取即可。Thread欄用于設(shè)定哪個線程里運行被調(diào)用的函數(shù)。用戶可以通過CLN節(jié)點的配置面板來指定被調(diào)用函數(shù)運行所在的線程。CLN的線程選項非常簡單,只有兩項:RuninUIthread和Runinanythread。LabVIEW的程序框圖上直接可以看出一個CLN節(jié)點是選用圖2填寫被調(diào)用函數(shù)信息的什么線程。如果RuninUIthread,節(jié)點顏色是橙色的;Runinanythread則是淺黃色的(圖3)。圖3CLN不同線程對比通常情況下,除非使用的動態(tài)鏈接庫是多線程安全的,CLN中選擇Runinanythread方式;否則必須選擇RuninUIthread方式。判斷一個動態(tài)鏈接庫是不是多線程安全的,需通過以下方法:如果一個動態(tài)鏈接庫的文檔中沒有明確說明它是多線程安全的,那么就要當(dāng)作是非多線程安全的;在可以看到動態(tài)鏈接庫源代碼的條件下,如果代碼中存在全局變量、靜態(tài)變量或者代碼中看不到有l(wèi)ock一類的操作,那么這個動態(tài)鏈接庫也就肯定不是多線程安全的。選擇了Runinanythread方式,LabVIEW會在最方便的線程內(nèi)運行動態(tài)鏈接庫函數(shù),且一般會與調(diào)用它的VI在同一個線程內(nèi)運行。因為LabVIEW是自動多線程的語言,它也很可能會把動態(tài)鏈接庫函數(shù)分配給一個單獨的線程運行。如果程序中存在沒有直接或間接先后關(guān)系的兩個CLN節(jié)點,LabVIEW很可能會同時在不同的線程內(nèi)運行它們所調(diào)用的函數(shù),也許是同一函數(shù)。對于非多線程安全的動態(tài)鏈接庫,這是很危險的操作。很容易引起數(shù)據(jù)混亂,甚至是程序崩潰。選擇RuninUIthread方式,因為LabVIEW只有一個界面線程,所以如果所有的CLN設(shè)置都是界面線程,那么就可以保證這些CLN調(diào)用的函數(shù)肯定全部都運行在同一線程下,肯定不會被同時調(diào)用。對于非多線程安全的動態(tài)鏈接庫,這種方式就保證了它的安全。讓我們回到配置對話框第一頁,Callingconvention用于指明被調(diào)用函數(shù)的調(diào)用約定。這里只支持兩種約定:stdcall和Ccall。它們之間的區(qū)別在于,stdcall由被調(diào)用者負責(zé)清理堆棧,Ccall由調(diào)用者清理堆棧。這個設(shè)置錯誤時,可能會引起LabVIEW崩潰,也就是說如果LabVIEW調(diào)用DLL函數(shù)時出現(xiàn)異常,首先應(yīng)該考慮這個設(shè)置是否正確。(WindowsAPI一般使用的都是stdcall;標(biāo)準(zhǔn)C的庫函數(shù)大多使用Ccall。如果函數(shù)聲明中有類似__stdcall這樣的關(guān)鍵字,它就是stdcall的。)第二頁是函數(shù)參數(shù)的配置(圖4)。圖4配置函數(shù)的參數(shù)DLL和LabVIEW之間傳遞參數(shù),最常用的三種數(shù)據(jù)類型是數(shù)值、數(shù)值型數(shù)組和字符串。C語言中經(jīng)常把指針或者數(shù)據(jù)的地址在函數(shù)間傳遞,在32位操作系統(tǒng)中,可以使用int32數(shù)值來表示指針。因此,當(dāng)需要在LabVIEW中傳遞指針數(shù)據(jù)時,可以使用I32或U32數(shù)值類型來表示這個地址類型的數(shù)據(jù)。但是,64位的程序中,數(shù)據(jù)的地址只能使用I64或U64來表示。這樣,如果一個調(diào)用了DLL函數(shù)的VI,并且函數(shù)參數(shù)中有地址型數(shù)據(jù),使用固定數(shù)據(jù)類型的數(shù)值來表示地址,就要準(zhǔn)備兩份代碼。解決方法是使用LabVIEW中的新的數(shù)據(jù)類型Pointer-sizedInteger。這個數(shù)據(jù)類型的長度在不同的平臺上會自動使用32位或64位長度。如果在C語言函數(shù)參數(shù)聲明中有const關(guān)鍵字,可以選中Constant選項。布爾類型在DLL函數(shù)和LabVIEWVI之間傳遞沒有專有的數(shù)據(jù)類型,是利用數(shù)值類型來傳遞的。輸入時先把布爾值轉(zhuǎn)變?yōu)閿?shù)值,在傳遞給DLL函數(shù);輸出時再把數(shù)值轉(zhuǎn)為布爾值。對于數(shù)組的傳遞,LabVIEW只支持C數(shù)據(jù)類型中的數(shù)值型數(shù)組,傳遞數(shù)組類型需要注意的的是“ArrayFormat”要選擇“ArrayDataPointer”。這個設(shè)置中還有其他兩個選項,帶有“Handle”的參數(shù)類型都是表示LabVIEW定義的特殊類型的。在第三方的DLL中不會使用到數(shù)組參數(shù)作為輸出值時,要記得為輸出的數(shù)組數(shù)開辟空間。開辟數(shù)據(jù)空間的方法有兩種:第一種方法,創(chuàng)建一個長度滿足要求的數(shù)組,作為初始值傳遞給參數(shù),輸出數(shù)的數(shù)據(jù)就會被放置在輸入數(shù)組的所在的內(nèi)存空間內(nèi)。第二種方法是直接在參數(shù)配置面板上進行設(shè)置。在Minimumsize中寫入一個固定的數(shù)值,LabVIEW就會按此大小為輸出的數(shù)組開辟空間。在Minimumsize中選擇函數(shù)的其它數(shù)值參數(shù),而不是固定數(shù)值。這樣LabVIEW會按照當(dāng)時被選擇的參數(shù)值的大小來開辟空間。字符串與使用與數(shù)組是非常類似的,實際上在C語言中字符串就是一個I8數(shù)組。在NI軟件的安裝路徑下打開當(dāng)前使用版本的LabVIEW文件夾,通過examples\dll\datapassing\CallNativeCode.llb找到簡單數(shù)據(jù)類型在LabVIEW與C之間的對應(yīng)關(guān)系。部分常見關(guān)系見表1。表1數(shù)據(jù)類型對比第三頁用于為DLL設(shè)置一些回調(diào)函數(shù),可以使用這些回調(diào)函數(shù)在特定的情形下完成初始化、清理資源等工作(圖5)。圖5設(shè)置回調(diào)函數(shù)如果為Reserve選擇了一個回調(diào)函數(shù),那么當(dāng)一個新的線程開始調(diào)用這個DLL時,這個回調(diào)函數(shù)首先被調(diào)用??梢岳眠@個函數(shù)為新線程使用到的數(shù)據(jù)做初始化工作。線程在使用完這個DLL之后,它會去調(diào)用Unreserve中指定的回調(diào)函數(shù)。Abort中指定的函數(shù)用于VI非正常結(jié)束時被調(diào)用,也就是讓一個程序在運行完前停止。這些回調(diào)函數(shù)的原型在Prototypefortheseprocedures中列出,必須要由DLL的開發(fā)者按照特定的格式實現(xiàn)。如果使用的DLL不是專為LabVIEW設(shè)計的,一般不會包含這樣的回調(diào)函數(shù)。第四頁是錯誤處理方式,用戶可根據(jù)需要選擇相應(yīng)的錯誤檢查級別。另外還需要注意的是,C語言中的struct在LabVIEW中可以使用cluster來表示,但有時需要作出相應(yīng)的調(diào)整。這是因為在C語言中,struct的字節(jié)對齊是可以進行設(shè)置的,這就決定了其各元素的存放地址的可變性。C語言中的對字節(jié)對齊數(shù)可通過#pragmapack指令或在工程屬性中進行指定。而在LabVIEW的cluster中,所有元素只能是1字節(jié)對齊的,所以如果要和C語言中非1字節(jié)對齊的struct對應(yīng),需要做出一些調(diào)整。比如,對于C語言中2字節(jié)對齊的struct,第一個元素如果是I8型的,在LabVIEW的cluster中第一個元素對應(yīng)不變,但不能緊挨著放第二個元素,必須留一個無意義的空位。C語言的struct其實也是如此,只不過沒有表現(xiàn)出來。所以為了方便,如果自己用C語言生成DLL文件供LabVIEW調(diào)用最好將struct都設(shè)為1字節(jié)對齊。C語言的struct中可以嵌套數(shù)組,但是這和LabVIEW中含有數(shù)組元素的cluster是不一樣的,LabVIEW中需要將數(shù)組中的元素都拆開放入cluster中。如果C語言的struct中含有一個指針,LabVIEW中的cluster只能用一個U32數(shù)值(32位系統(tǒng)上,64位系統(tǒng)上使用U64)來表示指針的地址,而不能將指針?biāo)赶虻膬?nèi)容放到Cluster中去。如果聲明的是指向struct的指針,才能在LabVIEW中使用cluster與之對應(yīng)。CLN節(jié)點的配置面板中,沒有一個專門命名的“struct”或者“cluster”參數(shù)類型,應(yīng)選擇“AdapttoType”就可以了。如果參數(shù)的類型就是結(jié)構(gòu)而非指針,考慮到C函數(shù)參數(shù)的壓棧順序,把一個結(jié)構(gòu)體作為參數(shù)傳給函數(shù),相當(dāng)于把結(jié)構(gòu)中每個元素分別作為參數(shù)傳遞給函數(shù)。圖6為C語言中struct和LabVIEW中cluster的部分匹配圖。圖6struct和cluster匹配LabVIEW打包DLL文件我們接下來學(xué)習(xí)如何使用LabVIEW來打包一個DLL文件。首先我們編寫一個名為Scale.vi的程序,功能很簡單就是對輸入的數(shù)據(jù)乘上10,然后再輸出(圖7)。圖7scale.vi必須在任務(wù)管理器中才能生成.dll文件。所以我們首先建立一個project,過程如下:點擊File>>NewProject:圖8生成新項目接著彈出是否將該VI添加到新項目的對話框:圖9是否添加VI到新建項目選擇Add,生成新的項目管理器,將其保存在需要的路徑下:圖10項目管理器右鍵單擊項目瀏覽器窗口中的BuildSpecifications,在快捷菜單中選擇New>>SharedLibrary(DLL),彈出對DLL文件進行設(shè)置的對話框。點擊Category>>Information,根據(jù)自己需求修改Buildspecificationname和Targetfilename:圖11Information頁面點擊SourceFiles>>ProjectFiles>>Scale.vi>>,彈出對話框,直接用默認(rèn)值,點擊OK:圖12DefineVIPrototype點擊Destination>>Scale.dll,點擊,可選擇需要保存的路徑。然后再點擊SupportDirectory,這是指明了DLL支持文件的路徑(比如數(shù)據(jù)文件之類的放在哪個文件夾),選擇默認(rèn)即可:圖13Destination頁面Category中的SourceFiles可供用戶對打包VI的屬性和密碼做一些設(shè)置;Advanced和AdditionalExclusions可以做一些高級的設(shè)置,這些均按默認(rèn)值即可。VersionInformation可讓用戶填寫版本號、名稱、版權(quán)、公司等信息:圖14VersionInformation點擊Run-TimeLanguages,可對支持語言進行選擇,默認(rèn)即可。點擊Preview>>GeneratePreview,可以預(yù)覽到結(jié)果:圖15預(yù)覽生成點擊Build,彈出生成狀態(tài)對話框:圖16生成狀態(tài)框點擊Done,生成完成,打開DLL文件保存的路徑查看:圖17DLL文件保存路徑LabVIEW調(diào)用DLL文件LabVIEW可以方便地調(diào)用DLL文件,這些DLL文件可以是其他編譯工具,如VC,生成的。LabVIEW可以直接通過CLN節(jié)點來調(diào)用DLL文件,以前面生成的Scale.dll文件為例?,F(xiàn)有一個內(nèi)部定時連續(xù)采集程序,通過調(diào)用該DLL文件,使讀取的值為實際采集值的10倍(圖18)圖18連續(xù)采集程序方法一在程序框圖放入CallLibraryFunctionNode,雙擊彈出對話框。在Function頁面的Librarynameorpath中給入生成的Scale.dll文件的路徑,F(xiàn)unctionname選擇Scale,其他選項默認(rèn)。圖19Function頁面由于是LabVIEW生成的DLL文件,在Parameters頁面不需要做改動,但是由于VI還有一路輸出,所以還需要添加一個參數(shù)y,作為DLL文件的輸出。如果是C語言等非LabVIEW生成的DLL函數(shù),需要將retuentype的type選項和Datatype選項改成函數(shù)定義的參數(shù)類型,對于函數(shù)里輸入的參數(shù)也都需要自行添加。Callbacks和ErrorChecking則不需要改動。圖20Parameters頁面點擊“OK”,將生成的CLN的輸入段連接到DAQmxRead.vi,returntype輸出連接到波形圖表上,即可實現(xiàn)采集值放大10倍的功能。圖21完成后的程序方法二LabVIEW中還有一種方法可以調(diào)用DLL文件,在VI的選項欄,依次選擇Tools——Import——SharedLibrary(.dll),彈出ImportSharedLibrary對話框。圖22生成ImportSharedLibrary對話框選擇CreateVIsforasharedlibrary,點擊Next,在SharedLibrary(.dll)Files中輸入Scale.dll文件的路徑,Head(.h)File里填寫頭文件的路徑。圖23選擇DLL文件路徑和頭文件路徑點擊Next,如果DLL文件中依賴其他的一些DLL文件,需要在IncludePaths中填寫這些文件的路徑。其他選項可以根據(jù)客戶需求設(shè)置,一般默認(rèn)即可。這樣一直點擊Next到最后,選擇Openthegeneratedlibrary,點擊Finish。這樣可以生成一個.lvlib格式的庫文件,里面包含了Scale.vi,這是將調(diào)用該DLL文件的方法封裝好的VI,只留下輸入和輸出接口,方便運用到LabVIEW的程序中。直接將Scale.vi拖放到剛才的連續(xù)采集中即可完成方法一的功能。圖24完成的程序VC調(diào)用LabVIEW生成的DLL文件剛才介紹了LabVIEW調(diào)用DLL文件的方法,使用VC調(diào)用LabVIEW生成的DLL文件也很簡單。還是以之前生成Scale的DLL文件為例,不同的是采集電壓程序使用的是C語言的例程,但和LavVIEW實現(xiàn)的功能相同。首先將先前生成ScaleDLL文件時,路徑下所有的文件全部復(fù)制粘帖到C語言例程的文件夾下。打開連續(xù)采集程序,點擊狀態(tài)欄的Project——Settings,在ProjectSettings對話框中加載入Scale.lib的靜態(tài)鏈接庫。在程序中鍵入#include"Scale.h",以便引入該DLL函數(shù)。下面是C程序的代碼,功能是有限點采集電壓,通過Scale.dll文件實現(xiàn)采樣值放大10倍的功能。加粗部分是因為調(diào)用DLL文件所做的改動。#include#include"NIDAQmx.h"#include"Scale.h"#defineDAQmxErrChk(functionCall)if(DAQmxFailed(error=(functionCall)))gotoError;elseintmain(void){int32error=0;TaskHandletaskHandle=0;int32read;float64data[1000];charerrBuff[2048]={'\0'};inti=0;doublex10=0;/*********************************************///DAQmxConfigureCode/*********************************************/DAQmxErrChk(DAQmxCreateTask("",&taskHandle));DAQmxErrChk(DAQmxCreateAIVoltageChan(taskHandle,"Dev1/ai0"/*Configcorrectdevice*/,"",DAQmx_Val_Cfg_Default,-10.0,10.0,DAQmx_Val_Volts,NULL));//DAQmxErrChk(DAQmxCfgSampClkTiming(taskHandle,"",10000.0,DAQmx_Val_Rising,DAQmx_Val_FiniteSamps,1000));/*********************************************///DAQmxStartCode

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論