按語:html
在參考CVI參考書使用CVI生成動態庫後,在另外一工程中調用DLL ,編譯通不過,後參考此文,豁然開朗。程序員
http://blog.sina.com.cn/s/blog_6373e9e60101bpsm.html算法
經過前幾章的學習,你們已經掌握了利用CVI開發涉及到UI、硬件、軟件組件的程序。但在現實生活中,前幾章示例程序那樣的幾十行幾百行的代碼的項目幾乎不存在,你們未來遇到更多的是幾萬行乃至幾千萬行代碼的軟件開發項目。這種中型、大型的軟件項目通常須要多人進行合做開發,此時就有必要將項目分爲一個個小的功能模塊,以方便其餘程序員在應用程序中調用。在Windows中,經常採用目標文件(*.obj)、靜態庫(*.lib)或動態連接庫文件(*.dll)進行功能模塊的分割。編程
目標文件和靜態庫文件比較相似,函數和數據被編譯爲機器碼以後存入一個二進制文件中,在使用目標文件或者靜態庫文件時,連接器(Linker)從目標文件或靜態庫中找到這些表明函數和數據的二進制代碼並把它們複製到exe應用程序文件中,和其餘模塊組合起來並生成最終可被執行的exe文件。所以,目標文件和靜態庫文件只是起到一個「二進制數據源」的做用,當最終版的exe發佈時,因爲exe文件中已經包含了目標文件或靜態庫中的數據,所以無需隨exe一塊兒發佈。目標文件與靜態庫文件不一樣的是,目標文件通常是C文件或其餘編程語言文件編譯獲得的二進制代碼,不會用來發布;而靜態庫文件通常發佈以供其餘程序員或者其餘開發環境使用。windows
動態連接庫(DLL,Dynamic Link Library)文件是從Microsoft推出第一個版本的Windows操做系統以來就有的一種可供多個程序調用的功能模塊。跟目標文件、靜態連接庫同樣,大部分的DLL中存放的也是能夠直接被執行的機器碼(之因此用「大部分」而不是「所有」的緣由將會在「4.4 CVI調用.Net平臺下的DLL」中提到)。跟靜態庫文件不一樣的是,應用程序運行時才連接到DLL中的功能模塊中,也就是說,應用程序發佈時其連接的DLL文件須要同時發佈。在程序設計時,通常更傾向於使用DLL而不是靜態庫,有如下緣由:api
·DLL支持任何其餘Windows下的編程語言,避免了編譯器的兼容問題數組
·只要DLL中導出函數的接口不變,修改動態連接庫的功能模塊時就沒必要修改與之相互依存的其餘模塊的代碼緩存
·在同一個Windows操做系統下,不一樣的應用程序能夠共享使用相同的DLL,能夠減小應用程序可執行文件的大小,節省空間併發
·以DLL發佈的程序能夠給用戶提供一個方便的二次開發平臺而又沒必要擔憂自身源代碼的泄漏app
因爲靜態庫文件實際上就是編譯過的代碼塊,在一個工程中添加了一個靜態庫,就如同添加了「加密過的」普通的編程語言文件同樣。當須要調用靜態庫文件時,只須要將靜態庫文件(*.lib)和相應的頭文件加入到工程中,在其餘文件中引用庫的頭文件以後,調用靜態庫中的函數或變量便可。
在使用動態庫文件的時候,每每會用到另外兩個文件文件:一個引入庫(*.lib)文件和一個頭(*.h)文件。雖然引入庫文件的後綴名也是.lib,可是動態連接庫的引入庫文件和靜態庫文件有着本質上的區別。通常而言,靜態庫文件中包含了全部變量和函數以及函數執行的機器碼,而DLL的引入庫文件只包含了該DLL導出的函數以及變量的名稱,而真正的可執行的機器碼在DLL文件中。
通常狀況下,編譯生成DLL的時候導入庫(.lib)文件會一同被建立。但若導入庫文件丟失,在CVI中能夠經過點擊菜單Options-Generate DLL Import Library…來生成一個導入庫文件。
爲了方便你們充分的理解靜態庫的概念與功能,咱們不妨舉個例子。動態庫的例子將會在「4.2 CVI生成DLL」和「4.3 CVI調用DLL」中給出,此小節再也不給出具體的實例。
咱們新建一個名爲「StaticLib」的工程,工程下新建Main.c、StaticLib.c以及StaticLib.h三個文件。
其中,在StaticLib.c文件中添加了以下代碼:
在StaticLib.h頭文件中添加以下代碼:
在Main.c中添加以下代碼:
整個工程創建完畢以後,CVI下的工程目錄如圖 4‑1所示。
從以上代碼咱們能夠看出,在StaticLib.c文件中,咱們寫了一個AddTest函數,輸入兩個int型變量,返回兩個變量的和。在StaticLib.h文件中,咱們提供了AddTest函數的聲明。在Main.c文件中,咱們調用了AddTest函數。以上工程運行的結果如圖 4‑2所示。
若此時咱們右擊StaticLib工程中的Main.c文件,選擇Exclude File from Build,如圖 4‑3所示,而後點擊CVI菜單-Build-Target Type-Static Library,將工程的輸出由exe文件改成靜態庫文件,則編譯以後,CVI將會編譯未被排除在編譯列表的StaticLib.c文件,並在工程目錄下會生成一個名爲「StaticLib.lib」的靜態庫文件。
生成靜態庫文件以後,將StaticLib.c文件排除在編譯列表以外,將剛生成的StaticLib.lib文件添加進工程中,並恢復Main.c文件到編譯列表中,恢復工程編譯生成的目標類型爲可執行文件(菜單Build-Target Type-Executable)。此時工程目錄如圖 4‑4所示。
從新編譯工程,運行後將會獲得跟剛纔徹底同樣的結果。如圖 4‑5所示。
在上一節中,咱們經過一個實例瞭解了在CVI下生成靜態庫文件的過程。生成DLL的步驟跟生成靜態庫的步驟基本相同,除了配置生成文件類型的時候選擇「Dynamic Link Library」而不是「Static Library」以外。
設置生成文件類型爲DLL後,在工程中只保留StaticLib.c文件與StaticLib.h文件在編譯列表中,將其餘文件排除到編譯列表以外,繼續Build上一節的工程。此時CVI彈出如所示的對話框,提示用戶沒有任何函數被導出。
圖 4‑6 CVI提示沒有任何函數被導出
若此時點擊OK,DLL文件仍然能夠被生成,可是DLL文件裏不包含任何函數的任何信息。此時若使用CVI安裝目錄sdk\bin下的DEPENDS.EXE程序查看生成的DLL中包含的函數,則能夠知道,在生成的DLL文件中確實不包含任何的函數的信息。如圖 4‑7所示。
圖 4‑7 使用Depends程序查看DLL中發現不包含任何函數信息
出現以上問題的緣由在於,咱們沒有定義導出的函數庫列表。點擊CVI菜單-Build-Target Settings…以後,彈出如圖 4‑8所示的窗口,在窗口中能夠對生成的DLL進行設置。
在上面的對話框中,點擊Exports中的Change…按鈕,將Export what設置爲Include file symbols,並點擊下面的StaticLib.h使得前面打勾,點擊OK便可。
圖 4‑9 設置工程的導出內容
圖 4‑8對話框各類設置說明以下:
(1) DLL file 設置DLL文件的類型(Debug/Release)以及位置、文件名。Debug類型的DLL文件能夠方便的進行調試,而Release類型的DLL文件中去除了調試信息,文件體積較小,運行速度相對較快,適合發佈。
(2)Import library base name 設置DLL導入庫文件名。
(3)Where to copy DLL 設置是否將生成的DLL文件拷貝到System目錄下或驅動目錄下
(4)Run-time support 是否提供CVI運行引擎的支持
(5)Embed project .UIRs 是否將*.uir文件編譯到DLL文件中
(6)Generate map file 是否生成map文件
(7)Version Info設置DLL文件的版本信息
(8)Import Library Choices… 設置生成DLL導入庫的方式,是隻與當前編譯器兼容仍是與CVI支持的四種編譯器兼容。
(9)Type Library 在CVI中不少庫文件擁有本身獨特的數據類型,如VISA中整數類型爲ViInt32,在這個選項中能夠定義是否將這些類型庫和幫助文檔鏈接到DLL文件。
(10)LoadExternalModule Options 是否將外部的庫文件編譯到DLL中
(11)Exports 設置輸出的方式,是將頭文件定義的內容所有導出,仍是隻導出具備導出標誌的內容。
設置導出列表以後,Build工程,則CVI發出如圖 4‑10所示的提示,告訴用戶DLL已經成功生成。
其中.dll(Dynamic Link Library)文件是動態連接庫文件,.cdb(CVI Debug)是供調試用的參數文件,.lib(Library)文件是DLL文件的導入庫文件。須要注意的是,此時的.lib導入庫文件跟以前生成的.lib靜態庫文件內容與大小均不同。這再次提醒咱們,靜態庫文件與DLL的導入庫文件雖然都是*.lib,可是內容不一樣,不能混淆。
若需將這次編譯出來的DLL發佈以供其餘編譯程序或者其餘程序員使用,只須要將StaticLib_dbg.dll、StaticLib_dbg.lib以及StaticLib.h這三個文件打包發佈便可。
發佈DLL文件時,通常會連同導入庫(*.lib)文件、頭(*.h)文件一塊兒發佈。有了導入庫(*.lib)文件的幫助,在CVI下調用動態連接庫DLL的方法跟調用靜態連接庫(*.lib)的方法大同小異。
調用靜態庫時,咱們須要將靜態庫(*.lib)文件加入工程,並將必要的頭文件加入工程以後,在工程的其餘文件中便可調用靜態庫文件中的函數或者參數。
而在調用動態連接庫時,咱們須要把動態連接庫的導入庫(*.lib)文件加入工程,並將必要的頭文件加入工程以後,在工程的其餘文件中便可調用動態連接庫中的函數或者參數。
須要額外注意的是,由於動態連接庫(*.dll)文件中的二進制機器碼並不會被鏈接器(Linker)複製到exe文件中,因此在運行、發佈時,須要確保DLL文件已經在系統的system目錄下或者工程當前目錄下存在。
在下面的例子中,咱們將使用上一節生成併發布的StaticLib_dbg.dll、StaticLib_dbg.lib以及StaticLib.h。
咱們在工程中添加Main.c源文件、StaticLib.h頭文件以及StaticLib_dbg.lib導入庫文件,將StaticLib_dbg.dll文件複製到工程所在目錄下。此時工程目錄如圖 4‑11所示。
其中Main.c文件與上一節中的內容徹底一致。以下所示:
將工程的生成文件類型設置爲Executable,編譯、運行程序,程序運行結果如圖 4‑12所示。
在VC中調用DLL跟在CVI中調用DLL的總體思路相同。將DLL的導入庫文件以及庫文件的頭文件加入工程中,便可在其餘編程語言文件中調用該DLL中的函數以及參數。
下面咱們將以一個實例來調用上一節發佈的StaticLib_dbg.dll、StaticLib_dbg.lib以及StaticLib.h文件。
創建VC工程
打開VC6.0,在VC6.0中創建一個名爲TestDll的控制檯工程。如圖 4‑13所示,將StaticLib.h文件、StaticLib_dbg.lib導入庫文件以及StaticLib_dbg.dll動態連接庫文件複製到工程所在目錄下,並將頭文件及導入庫文件加入到工程中。
編寫C語言文件
新建TestDll.cpp文件,並在cpp文件中輸入如下代碼,並將其加入到工程。
編譯、調試
點擊組建按鈕,咱們注意到,編譯器此時並無成功的找到AddTest函數,而是報如下錯誤:
在上一節咱們使用CVI調用該DLL時,徹底正常,可是爲何此時會出現問題呢?
問題的緣由
咱們注意到,上述錯誤是一個鏈接器錯誤。並且咱們考慮到,從CVI遷移到VC,無非是換了一個編譯器。而編譯器理論上應該都支持ANSI C標準,理論上這種改變不會致使出現此類問題。兩個集成開發環境最大的不一樣在於其環境變量不一樣。
問題就出在這兒。因爲每一個C語言(*.c)或C++語言(*.cpp)文件都是被編譯器編譯爲目標文件以後再交由鏈接器(Linker)進行鏈接的。每一個編譯器在生成目標文件的過程當中,編程語言文件中的函數名會被重命名,而對於C++編譯器來講,環境變量不一樣,重命名的方法也就不一樣。
做爲一種面向對象的語言,C++支持函數重載,而過程式語言C則不支持。函數被C++編譯後在符號庫中的名字與C語言的不一樣。例如,假設某個函數的原型爲:
void foo( int x, int y );
該函數被C編譯器編譯後在符號庫中的名字爲_foo,而C++編譯器則會產生像_foo_int_int之類的名字(不一樣的編譯器在不一樣的環境中可能生成的名字還略有區別)。
在上述例子中,C++編譯器對TestDll.cpp進行編譯時,AddTest函數在目標文件中被重命名爲_Add_Test_int_int,而CVI中C編譯器對StaticLib.c進行編譯時AddTest函數在導入庫文件中被重命名爲_Add_Test。兩個函數在目標文件中的名字不相同,鏈接器天然也就沒法成功的將兩個文件組建成一個exe文件。
解決C++編譯器兼容問題
C++語言的建立初衷是「a better C」,所以C++之父在設計C++之時就考慮到C++裏面的重載會引發C庫與C++庫的不兼容。爲了在C++中儘量的支持C,extern 「C」{}就是其中的一個策略。extern 「C」{}經常使用在頭文件中,使用方法以下:
爲了讓編譯器得知StaticLib.h文件中全部的函數及參數都是由C編譯器編譯產生的,咱們只須要把頭文件的全部內容加入到extern 「C」內便可。
可是將extern 「C」{}加入頭文件以使得C與C++編譯器兼容存在另一個問題。extern 「C」{}自己並非C語言的關鍵字,extern 「C」加在C語言中會引發編譯錯誤。爲了不C語言編譯錯誤,咱們只須要在extern 「C」的聲明先後分別加入#if defined (__cplusplus)以及#endif便可。
修改以後的StaticLib.h文件以下所示:
再次編譯、運行
再次編譯,運行,則原先的鏈接錯誤再也不出現,運行結果如圖 4‑14所示。
上節咱們已經初步接觸到了在CVI中調用DLL的方法。在本節中,咱們將系統的介紹CVI調用其餘編譯器生成的DLL的方法。
調用DLL的方法分爲‘顯示調用’和‘隱式調用’兩種方法。
隱式調用方法須要被調用DLL的頭文件,採用建立DLL導入庫.lib文件的方法調用。該方式使用Options選項卡中的Generate DLL Import Library嚮導建立DLL導入庫.lib(若提供DLL同時也提供了.lib則不須要此步),將導入庫.lib添加至工程項目後,就能夠經過函數名方便靈活的使用被調用DLL中的函數,須要注意的是,頭文件中須要對所調用的函數進行申明。
顯式調用方法不須要被調用DLL的頭文件以及導入庫文件,使用windows.h提供的Loadlibrary、GetProcAddress、Freelibrary函數,直接根據指針訪問DLL中的函數。該方式適用於沒有DLL的頭文件以及導入庫文件,但知道被調用函數的原型的場合。
下面咱們建立一個名爲LoadLibrary的工程,並只將上節發佈的StaticLib_dbg.dll文件複製到工程目錄下,使用顯示調用的方法使用StaticLib_dbg.dll文件中的AddTest函數。
咱們在LoadLibrary工程中建立一個LoadLibrary.c的C語言源文件,建立完畢以後,LoadLibrary工程目錄如所示。
圖 4‑15 LoadLibrary工程目錄
向LoadLibrary.c文件中添加以下內容:
在上述文件中,咱們先經過加載DLL文件獲取DLL文件的句柄,而後從DLL文件中查得AddTest函數的指針地址,而後賦值給一個函數指針,隨即執行函數指針所指向的函數,便可顯示最終執行的結果。
上述工程不須要使用頭文件也不須要使用導入庫文件。運行結果如圖 4‑16所示。
對你們來講,上述代碼讓人不解的地方可能在於函數指針的使用。其實函數指針是內核編程等經常使用到的一種指針,經過函數指針的使用大大增長了靈活度,使程序變得更加清晰和簡潔。若是你們的C語言功底足夠強,應該能夠發現,上述C語言代碼中,main函數內的代碼能夠精簡爲如下四行代碼:
對函數指針感興趣的同窗能夠深刻研究能夠這樣精簡的緣由,此處盡起一個拋磚引玉的做用,具體內容再也不深究。
在VC下編寫一個簡單的DLL程序也並不複雜。下面咱們將以一個實例來講明在VC下編寫一個簡單的DLL程序的方法。
組建VC下的DLL工程
打開VC,點擊菜單-文件-新建,在工程頁面中選擇Win32 Dynamic-Link Library,選擇工程路徑與工程名稱以後,點擊肯定,建立一個空白的DLL工程。
圖 4‑17 VC下新建一DLL工程
新建一個C++源文件(*.cpp)與頭文件(*.h),添加到工程中。其中cpp文件的源代碼以下:
頭文件的源代碼以下:
點擊VC組建按鈕,則VC發出以下提示,告訴你們組建成功,DLL文件已經生成。
咱們使用「4.2.1 CVI下生成DLL文件」中提到的DEPENDS工具查看生成的DLL文件內部是否已經包含了咱們所須要的SubTest函數。結果如圖 4‑18所示。
咱們並無在生成的DLL文件中發現任何函數。同時,咱們也沒有在DLL的目錄下發現有導入庫(*.lib)文件生成!
問題的緣由
跟在CVI下生成DLL時遇到的問題類似,咱們並無定義要導出的函數。在VC中,咱們須要使用__declspec關鍵詞來定義須要導出的函數。
__declspec 是MFC提供的修飾符號。在要輸出的函數、類、數據的聲明前加上__declspec(dllexport)的修飾符,表示該函數、類、數據須要被輸出到DLL文件中。
所以,咱們若把頭文件中SubTest的聲明中添加__declspec(dllexport),即告訴編譯器咱們須要將SubTest函數導出到DLL文件中。
添加__declspec(dllexport)關鍵詞以後,咱們從新編譯,組建,則在Debug目錄下生成了咱們所須要的導入庫文件、DLL文件。用DEPENDS工具查看生成的DLL,能夠發現SubTest函數已經在DLL文件中了。如圖 4‑19所示。
在CVI下調用VC生成的DLL,跟調用CVI生成的DLL並沒有區別。
咱們新建一CVI工程,將上一小節生成的DLL文件、導入庫(*.lib)文件以及頭文件複製到CVI工程目錄下,將導入庫(*.lib)文件添加到工程中,在CVI工程中添加一C語言源文件,代碼以下:
編譯、鏈接、運行以後,測試結果如圖 4‑20所示。
經過上面的示例程序咱們能夠發現,使用CVI調用VC生成的DLL的步驟跟調用CVI生成的DLL步驟幾乎徹底同樣。
須要額外注意的是,假若VC生成的DLL運行時也依賴與其餘的非系統system下的DLL(運行依賴的DLL可使用DEPENDS工具查看),那麼也須要將對應的DLL文件複製到CVI工程所在文件夾下或者複製到系統的system目錄下。
經常使用的DLL包括託管DLL和非託管DLL兩種。託管DLL爲中間代碼,徹底依賴於.NET平臺運行,而非託管DLL是機器代碼,不依賴於.NET平臺運行。C#、VB.NET和F#編程採用純.NET語言開發,生成的DLL屬於託管DLL。而平時接觸到的其它語言編寫的DLL,包括C/C++、CVI、LabVIEW編寫的DLL都屬於非託管DLL。託管的應用程序和DLL能夠直接調用非託管的DLL,而非託管的應用程序和DLL必須經過.NET Runtime才能調用託管的DLL。
前兩節介紹的調用的DLL與生成的DLL都是非託管的DLL,是機器代碼,不依賴於.Net平臺運行。可是若在CVI下調用.Net平臺下的DLL,那麼則須要藉助CVI的.Net工具。
CVI提供了幫助用戶調用.Net程序集的.Net庫。用戶能夠經過菜單-Tools-Create .Net Controller…嚮導生成可在CVI下使用的.Net庫。經過使用CVI以及.Net庫,咱們能夠完成如下工做:
·註冊.Net控件而且加載控件
·建立.Net對象而且調用.Net對象
·管理系統資源
·建立.Net數組,並從.Net數據中獲取元素
·獲取.Net的錯誤與異常信息
·提供.Net組件的基本信息
·同COM組件交互操做
可是CVI對.Net的支持也有一些限制。CVI不支持.Net事件和委託。而且CVI也不是一個.Net控件容器。所以,在CVI中不能使用.Net的用戶界面對象。
CVI調用C#等.NET語言編寫的DLL須要使用工具選項卡中的‘Creat .NET controller’。經過它生成一個調用.NET彙編代碼的包裝器(wrapper),該包裝器包含對應的儀器驅動、源文件和頭文件。包裝器生成具體步驟以下:
(1)選擇Tools選項卡中的‘Creat .NET controller’
(2)通常第三方開發的DLL都不在Global Assembly Cache中,所以彈出對話框中勾選‘Specify Assembly by Path’,選擇須要調用的 DLL
(3)在‘Target Instrument’中指定一個儀器驅動.fp文件,點擊OK,CVI程序便生成一個能夠調用所選DLL的儀器驅動
(4)將生成的.fp文件添加至工程項目
包裝器自動生成的儀器驅動中會封裝好一些調用函數,函數的命名方式爲 [命名空間]_[類名稱] ,命名空間和類名稱都是在編寫.NET程序時定義好的,命名空間也就是DLL的名稱。如圖 4‑21,ClassLibrary1爲命名空間,Class1爲類名稱,一個DLL中包括一個命名空間,一個命名空間下能夠包含一個或多個類,一個類下又能夠包含多個函數。圖 4‑21中的ClassLibrary1_Class1__add就是一個DLL中的加法函數。
通常如 Initialize_[命名空間] 和 Close_[命名空間] 兩個函數分別調用 CDotNetLoadAssembly 和CDotNetDiscardAssemblyHandle 兩個函數,[命名空間]_[類名稱]__Create 調用CDotNetCreateGenericInstance 函數, [命名空間]_[類名稱]_[函數名稱] 調用CDotNetInvokeGenericStaticMember 函數等。
特別注意的是,因爲被調用的DLL沒有在全局程序集緩存(GAC,Global Assembly Cache)中,調用前須要使用CDotNetRegisterAssemblyPath函數先註冊.NET的DLL。若是沒有註冊將出現如圖4所提示的調用失敗的錯誤。全局程序集緩存所在的文件夾爲C:\WINDOWS\assembly。
圖 4‑22 .NET的DLL加載失敗錯誤提示
另外,C#編程中無需關心垃圾內存的回收(Garbage Collector),而回到C環境中被調用函數涉及的變量最後須要經過手動調用釋放內存的函數來釋放變量的內存空間。
編程的主要流程以下:
(1)聲明「[DLL名稱]_[類名稱]」類型的句柄;
(2)調用CDotNetRegisterAssemblyPath("[DLL名稱] , Version=x.x.x.x, Culture=xx , PublicKeyToken=xx" , ''Full Path of DLL")註冊.NET的DLL,DLL的路徑分隔符用"\",如D:\\CVI\\Projects\\C#net DLL call;
(3)調用「Initialize_[DLL名稱]」函數初始化.NET controller;
(4)根據句柄,調用「[DLL名稱]_[類名稱]__Create」建立被調用DLL的實例;
(5)調用「[DLL名稱]_[類名稱]_[函數名稱]」等具體函數,編寫相應代碼;
(6)調用CDotNetDiscardHandle釋放.NET DLL實例句柄;
(7)調用CDotNetFreeMemory釋放變量內存;
(8)調用「Close_[DLL名稱]」卸載.NET DLL;
須要額外注意數據類型轉換的問題,.NET語言中的Enum、Rectangular Array、String、System.Decimal和System.Boolean能夠自動完成轉換,而COM Run-Time Callable Wrapper (RCW) types、Jagged arrays和Boxed data types須要手動調用庫函數轉換。
在接下來的示例中,咱們將在C#中寫一個基於.Net的DLL,並在系統的全局程序集緩存(GAC)中註冊,並在CVI中調用此DLL。
新建C#類庫工程
打開Visual Studio 2008,新建一個C#類庫(Class Library)工程。如圖 4‑23所示。
爲工程添加密鑰文件
點擊開始菜單-程序- Microsoft Visual Studio 2008 SDK- Tools- System Definition Model Command Prompt,敲入sn.exe –k c:\1.snk,爲C#的工程生成一個密鑰文件。生成的密鑰文件將會保存在指定的c:\1.snk下。
打開C#工程的AssemblyInfo.cs文件,在其中插入如下代碼:
在工程的Class1.cs文件中,輸入如下代碼:
點擊Visual Studio 2008菜單Build-Build Solution,編譯C#工程。在工程目錄的bin\Debug目錄下會生成ClassLibrary1.dll文件。
把DLL文件註冊到系統全局程序集緩存中
點擊開始菜單-程序-管理工具-Microsoft .NET Framework 2.0 配置,如所示,在程序集緩存中,將生成的DLL文件加入全局程序集緩存(GAC)中。
圖 4‑24 Microsoft .NET Framework 2.0 配置
添加.Net控件
新建CVI工程,在CVI中點擊菜單Tools-Create .Net Controller…,添加生成的ClassLibrary1.dll文件,並添加生成的.fp文件的路徑。點擊肯定以後,在CVI左下角的函數庫窗口的Instruments文件夾下多出了ClassLibrary1庫,在當前工程下多出了剛纔指定的.fp文件。
編寫代碼,編譯運行
新建一C文件,在C文件中添加以下代碼,並添加到當前工程中:
CVI程序目的是調用基於C#.Net的DLL的函數實現計算1+2的功能。編譯運行後,程序運行結果如圖 4‑25所示。
在實際的軟件項目中,當一個收費軟件發佈時,經常須要用戶購買「註冊碼」,輸入正確的註冊碼以後才能夠正常使用。然而,若該註冊碼一旦被人公開,整個收費軟件的註冊碼形同虛設。爲了不註冊碼共享後便可被全部人使用的問題,咱們必須針對每一臺計算機生成獨一無二的註冊號。
對於一臺計算機而言,軟件環境常常會被改變,因此靠檢測軟件使用環境來識別一臺計算機顯然不夠嚴密。通常狀況下,最經常使用的方法是獲取計算機的CPU序列號、網卡號或者硬盤序列號後來計算得知該計算機的「註冊碼」。
然而,在Intel的CPU中,獲取計算機序列號是靠一條叫作cpuid的彙編指令來完成的。而在CVI下,筆者還沒有發現嵌入彙編語言的方法。假若一個軟件總體框架是採用CVI寫的,那麼咱們能夠經過將使用VC來獲取CPU序列號的代碼封裝成一個DLL,以提供給CVI使用。
軟件運行時,首先調用這個DLL獲取CPU序列號與網卡號,通過某種算法計算獲得「註冊碼」以後與用戶輸入的註冊碼比較,若相同則軟件繼續運行,若不相同,則用戶軟件提示相應的註冊信息並退出。
在本小節例子中,咱們將只演示獲取CPU序列號以及網卡物理地址的內容。獲取硬盤序列號的方法略複雜,步驟與獲取網卡物理地址相似,此處再也不給出具體代碼,具體代碼能夠從例程中得到。
獲取CPU序列號(Intel)
在Intel的處理器中,獲取CPU序列號須要用到彙編指令CPUID。因爲入口參數存放在EAX寄存器中,執行前,往EAX寄存器賦值,再執行CPUID指令,便可從EAX、EBX、ECX以及EDX中獲取CPUID的返回值。具體EAX輸入以及四個寄存器輸出的對應關係參見表 4‑1。
從上面的表格能夠看出,往EAX中賦值0x00,則運行CPUID後能夠獲得字符GenuineIntel。往EAX中賦值0x01,運行CPUID後從EAX與EBX中得到處理器簽名以及一些特性值。往EAX中賦值0x03,運行CPUID後從ECX以及EDX後便可獲得CPU的序列號。
獲取網卡號
獲取網卡號經過Windows IP輔助API庫(IPHlpApi.h)來完成,經過GetAdaptersInfo函數能夠獲取網卡的信息,而且將PIP_ADAPTER_INFO結構體中的Address等成員變量進行處理以後顯示出來便可。
運行GetAdaptersInfo函數須要庫文件Iphlpapi.lib的支持。Iphlpapi.lib在VC6.0的SDK(可能須要單獨安裝)的lib文件夾下,若添加
語句以後還不能找到Iphlpapi.lib文件多是由於沒有將Microsoft SDK加入VC的鏈接目錄中致使的。此時須要點擊VC菜單-工具-選項-目錄-Library files,將Microsoft SDK目錄加入其中便可。
VC最終代碼
瞭解如何使用VC獲取計算機的網卡號與CPU序列號以後,咱們就能夠着手實現具體程序了。
首先新建一個空的DLL工程,具體步驟參見「4.3.2 VC生成DLL文件」,並在工程中建立、添加一個cpp文件、一個頭文件。CPP文件代碼以下:
頭文件代碼以下:
在上述代碼中,咱們經過__asm指令完成了C++代碼與彙編代碼的嵌套。咱們利用Windows IP輔助庫的GetAdaptersInfo函數實現了網卡物理地址的獲取。經過接口函數GetSerialNum,咱們能夠在CVI中方便的調用該DLL,實現獲取CPU的序列號以及網卡的物理地址的功能。
CVI調用DLL
在CVI中,咱們建立一個名爲GetPhyNum的工程,將VC建立生成的DLL文件、lib文件以及頭文件複製到工程目錄下。在工程中新建一名爲GetPhyNum.c的C語言源文件並將SerialNum.lib導入庫文件添加進工程中。添加完畢後,工程目錄如所示。
圖 4‑26 CVI獲取CPU、網卡序列號的工程目錄
GetPhyNum.c源代碼以下所示:
在上述C語言文件中,咱們經過定義一個字符串並將字符串指針傳給GetSerialNum函數。GetSerialNum函數運行時會完成將指針指向的字符串賦值的功能。最後咱們調用printf函數顯示獲取獲得的含有CPU的序列號以及網卡物理地址的字符串。
調試、運行
當CVI運行時,屏幕上會顯示出CPU序列號以及網卡物理地址。程序運行結果如所示。
圖 4‑27 CVI獲取CPU序列號、網卡物理地址運行結果
運行計算機硬件檢測程序Everest查看計算機的CPU序列號與網卡物理地址,能夠發現以上程序檢測獲得的CPU序列號與網卡物理地址均準確無誤。
圖 4‑28 用Everest軟件檢測獲得的CPU序列號與網卡物理地址
使用CVI實時獲取USB攝像頭中的圖像,顯示在界面中,並使用算法檢測一張白紙上的黑色方塊。檢測到的方塊用紅色方框實時標註出來。
探索利用Matlab生成DLL文件並在CVI中調用Matlab生成的DLL的方法。