LabWindows/CVI入门之第四章:库文件(转)

按语:html

  在参考CVI参考书使用CVI生成动态库后,在另外一工程中调用DLL ,编译通不过,后参考此文,豁然开朗。程序员

http://blog.sina.com.cn/s/blog_6373e9e60101bpsm.html算法

4.1 静态库与动态库

 

  4.1.1 简介

    经过前几章的学习,你们已经掌握了利用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

  4.1.2 使用静态库与动态库

    因为静态库文件实际上就是编译过的代码块,在一个工程中添加了一个静态库,就如同添加了“加密过的”普通的编程语言文件同样。当须要调用静态库文件时,只须要将静态库文件(*.lib)和相应的头文件加入到工程中,在其余文件中引用库的头文件以后,调用静态库中的函数或变量便可。

    在使用动态库文件的时候,每每会用到另外两个文件文件:一个引入库(*.lib)文件和一个头(*.h)文件。虽然引入库文件的后缀名也是.lib,可是动态连接库的引入库文件和静态库文件有着本质上的区别。通常而言,静态库文件中包含了全部变量和函数以及函数执行的机器码,而DLL的引入库文件只包含了该DLL导出的函数以及变量的名称,而真正的可执行的机器码在DLL文件中。

    通常状况下,编译生成DLL的时候导入库(.lib)文件会一同被建立。但若导入库文件丢失,在CVI中能够经过点击菜单Options-Generate DLL Import Library…来生成一个导入库文件。

  4.1.3 静态库生成和使用的例子

    为了方便你们充分的理解静态库的概念与功能,咱们不妨举个例子。动态库的例子将会在“4.2 CVI生成DLL”和“4.3 CVI调用DLL”中给出,此小节再也不给出具体的实例。

    咱们新建一个名为“StaticLib”的工程,工程下新建Main.c、StaticLib.c以及StaticLib.h三个文件。

    其中,在StaticLib.c文件中添加了以下代码:

image

    在StaticLib.h头文件中添加以下代码:

image

image

    在Main.c中添加以下代码:

image

    整个工程创建完毕以后,CVI下的工程目录如图 4‑1所示。

image

4‑1 新建的工程目录

    从以上代码咱们能够看出,在StaticLib.c文件中,咱们写了一个AddTest函数,输入两个int型变量,返回两个变量的和。在StaticLib.h文件中,咱们提供了AddTest函数的声明。在Main.c文件中,咱们调用了AddTest函数。以上工程运行的结果如图 4‑2所示。

image

4‑2 工程运行结果

    若此时咱们右击StaticLib工程中的Main.c文件,选择Exclude File from Build,如图 4‑3所示,而后点击CVI菜单-Build-Target Type-Static Library,将工程的输出由exe文件改成静态库文件,则编译以后,CVI将会编译未被排除在编译列表的StaticLib.c文件,并在工程目录下会生成一个名为“StaticLib.lib”的静态库文件。

image

4‑3 将Main.c文件从编译列表中排除

    生成静态库文件以后,将StaticLib.c文件排除在编译列表以外,将刚生成的StaticLib.lib文件添加进工程中,并恢复Main.c文件到编译列表中,恢复工程编译生成的目标类型为可执行文件(菜单Build-Target Type-Executable)。此时工程目录如图 4‑4所示。

image

4‑4 添加库文件的工程目录

    从新编译工程,运行后将会获得跟刚才彻底同样的结果。如图 4‑5所示。

image

4‑5 从静态库中运行的结果

4.2 CVI生成DLL

 

 4.2.1 CVI下生成DLL文件

    在上一节中,咱们经过一个实例了解了在CVI下生成静态库文件的过程。生成DLL的步骤跟生成静态库的步骤基本相同,除了配置生成文件类型的时候选择“Dynamic Link Library”而不是“Static Library”以外。

    设置生成文件类型为DLL后,在工程中只保留StaticLib.c文件与StaticLib.h文件在编译列表中,将其余文件排除到编译列表以外,继续Build上一节的工程。此时CVI弹出如所示的对话框,提示用户没有任何函数被导出。

image

图 4‑6 CVI提示没有任何函数被导出

    若此时点击OK,DLL文件仍然能够被生成,可是DLL文件里不包含任何函数的任何信息。此时若使用CVI安装目录sdk\bin下的DEPENDS.EXE程序查看生成的DLL中包含的函数,则能够知道,在生成的DLL文件中确实不包含任何的函数的信息。如图 4‑7所示。

image

4‑7 使用Depends程序查看DLL中发现不包含任何函数信息

    出现以上问题的缘由在于,咱们没有定义导出的函数库列表。点击CVI菜单-Build-Target Settings…以后,弹出如图 4‑8所示的窗口,在窗口中能够对生成的DLL进行设置。

image

4‑8 设置目标DLL的属性

    在上面的对话框中,点击Exports中的Change…按钮,将Export what设置为Include file symbols,并点击下面的StaticLib.h使得前面打勾,点击OK便可。

image

图 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已经成功生成。

image

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这三个文件打包发布便可。

  4.2.2 在CVI下调用CVI生成的DLL

    发布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所示。

image

4‑11 调用DLL文件的工程目录

    其中Main.c文件与上一节中的内容彻底一致。以下所示:

image

image

    将工程的生成文件类型设置为Executable,编译、运行程序,程序运行结果如图 4‑12所示。

image

4‑12 调用DLL运行结果

  4.2.3 在VC下调用CVI生成的DLL

    在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动态连接库文件复制到工程所在目录下,并将头文件及导入库文件加入到工程中。

image

4‑13 在VC中新建控制台工程

   编写C语言文件

    新建TestDll.cpp文件,并在cpp文件中输入如下代码,并将其加入到工程。

image

   编译、调试

    点击组建按钮,咱们注意到,编译器此时并无成功的找到AddTest函数,而是报如下错误:

image

    在上一节咱们使用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”{}经常使用在头文件中,使用方法以下:

image

    为了让编译器得知StaticLib.h文件中全部的函数及参数都是由C编译器编译产生的,咱们只须要把头文件的全部内容加入到extern “C”内便可。

    可是将extern “C”{}加入头文件以使得C与C++编译器兼容存在另一个问题。extern “C”{}自己并非C语言的关键字,extern “C”加在C语言中会引发编译错误。为了不C语言编译错误,咱们只须要在extern “C”的声明先后分别加入#if defined (__cplusplus)以及#endif便可。

    修改以后的StaticLib.h文件以下所示:

image

   再次编译、运行

    再次编译,运行,则原先的链接错误再也不出现,运行结果如图 4‑14所示。

image

4‑14 VC调用CVI生成的DLL运行测试

4.3 CVI调用DLL

 

    上节咱们已经初步接触到了在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的头文件以及导入库文件,但知道被调用函数的原型的场合。

  4.3.1 显式调用方法的例子

    下面咱们建立一个名为LoadLibrary的工程,并只将上节发布的StaticLib_dbg.dll文件复制到工程目录下,使用显示调用的方法使用StaticLib_dbg.dll文件中的AddTest函数。

    咱们在LoadLibrary工程中建立一个LoadLibrary.c的C语言源文件,建立完毕以后,LoadLibrary工程目录如所示。

image

图 4‑15 LoadLibrary工程目录

    向LoadLibrary.c文件中添加以下内容:

image

    在上述文件中,咱们先经过加载DLL文件获取DLL文件的句柄,而后从DLL文件中查得AddTest函数的指针地址,而后赋值给一个函数指针,随即执行函数指针所指向的函数,便可显示最终执行的结果。

    上述工程不须要使用头文件也不须要使用导入库文件。运行结果如图 4‑16所示。

image

4‑16 显示调用DLL文件运行结果

    对你们来讲,上述代码让人不解的地方可能在于函数指针的使用。其实函数指针是内核编程等经常使用到的一种指针,经过函数指针的使用大大增长了灵活度,使程序变得更加清晰和简洁。若是你们的C语言功底足够强,应该能够发现,上述C语言代码中,main函数内的代码能够精简为如下四行代码:

image

    对函数指针感兴趣的同窗能够深刻研究能够这样精简的缘由,此处尽起一个抛砖引玉的做用,具体内容再也不深究。

  4.3.2 VC生成DLL文件

    在VC下编写一个简单的DLL程序也并不复杂。下面咱们将以一个实例来讲明在VC下编写一个简单的DLL程序的方法。

   组建VC下的DLL工程

    打开VC,点击菜单-文件-新建,在工程页面中选择Win32 Dynamic-Link Library,选择工程路径与工程名称以后,点击肯定,建立一个空白的DLL工程。

image

图 4‑17 VC下新建一DLL工程

    新建一个C++源文件(*.cpp)与头文件(*.h),添加到工程中。其中cpp文件的源代码以下:

image

    头文件的源代码以下:

image

image

    点击VC组建按钮,则VC发出以下提示,告诉你们组建成功,DLL文件已经生成。

image

    咱们使用“4.2.1 CVI下生成DLL文件”中提到的DEPENDS工具查看生成的DLL文件内部是否已经包含了咱们所须要的SubTest函数。结果如图 4‑18所示。

image

4‑18 生成的DLL文件内部函数查看

    咱们并无在生成的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所示。

image

4‑19 DLL文件中已经包含导出的SubTest函数

  4.3.3 CVI下调用VC生成的DLL

    在CVI下调用VC生成的DLL,跟调用CVI生成的DLL并没有区别。

    咱们新建一CVI工程,将上一小节生成的DLL文件、导入库(*.lib)文件以及头文件复制到CVI工程目录下,将导入库(*.lib)文件添加到工程中,在CVI工程中添加一C语言源文件,代码以下:

image

    编译、链接、运行以后,测试结果如图 4‑20所示。

image

4‑20 CVI调用VC生成的DLL的运行测试

    经过上面的示例程序咱们能够发现,使用CVI调用VC生成的DLL的步骤跟调用CVI生成的DLL步骤几乎彻底同样。

    须要额外注意的是,假若VC生成的DLL运行时也依赖与其余的非系统system下的DLL(运行依赖的DLL可使用DEPENDS工具查看),那么也须要将对应的DLL文件复制到CVI工程所在文件夹下或者复制到系统的system目录下。

4.4 CVI调用.Net平台下的DLL

 

    经常使用的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 函数等。

image

4‑21 生成的仪器驱动函数树

    特别注意的是,因为被调用的DLL没有在全局程序集缓存(GAC,Global Assembly Cache)中,调用前须要使用CDotNetRegisterAssemblyPath函数先注册.NET的DLL。若是没有注册将出现如图4所提示的调用失败的错误。全局程序集缓存所在的文件夹为C:\WINDOWS\assembly。

image

图 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所示。

image

4‑23 新建C#类库工程

   为工程添加密钥文件

    点击开始菜单-程序- Microsoft Visual Studio 2008 SDK- Tools- System Definition Model Command Prompt,敲入sn.exe –k c:\1.snk,为C#的工程生成一个密钥文件。生成的密钥文件将会保存在指定的c:\1.snk下。

    打开C#工程的AssemblyInfo.cs文件,在其中插入如下代码:

image

    在工程的Class1.cs文件中,输入如下代码:

image

image

    点击Visual Studio 2008菜单Build-Build Solution,编译C#工程。在工程目录的bin\Debug目录下会生成ClassLibrary1.dll文件。

   把DLL文件注册到系统全局程序集缓存中

    点击开始菜单-程序-管理工具-Microsoft .NET Framework 2.0 配置,如所示,在程序集缓存中,将生成的DLL文件加入全局程序集缓存(GAC)中。

image

图 4‑24 Microsoft .NET Framework 2.0 配置

   添加.Net控件

    新建CVI工程,在CVI中点击菜单Tools-Create .Net Controller…,添加生成的ClassLibrary1.dll文件,并添加生成的.fp文件的路径。点击肯定以后,在CVI左下角的函数库窗口的Instruments文件夹下多出了ClassLibrary1库,在当前工程下多出了刚才指定的.fp文件。

   编写代码,编译运行

    新建一C文件,在C文件中添加以下代码,并添加到当前工程中:

image

image

    CVI程序目的是调用基于C#.Net的DLL的函数实现计算1+2的功能。编译运行后,程序运行结果如图 4‑25所示。

image

4‑25 .Net程序运行结果

4.5 示例:CVI获取计算机CPU、硬盘、网卡ID

 

    在实际的软件项目中,当一个收费软件发布时,经常须要用户购买“注册码”,输入正确的注册码以后才能够正常使用。然而,若该注册码一旦被人公开,整个收费软件的注册码形同虚设。为了不注册码共享后便可被全部人使用的问题,咱们必须针对每一台计算机生成独一无二的注册号。

    对于一台计算机而言,软件环境常常会被改变,因此靠检测软件使用环境来识别一台计算机显然不够严密。通常状况下,最经常使用的方法是获取计算机的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。

4‑1 CPUID指令返回值与输入值对应关系表

image

    从上面的表格能够看出,往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文件夹下,若添加

image

    语句以后还不能找到Iphlpapi.lib文件多是由于没有将Microsoft SDK加入VC的链接目录中致使的。此时须要点击VC菜单-工具-选项-目录-Library files,将Microsoft SDK目录加入其中便可。

   VC最终代码

    了解如何使用VC获取计算机的网卡号与CPU序列号以后,咱们就能够着手实现具体程序了。

    首先新建一个空的DLL工程,具体步骤参见“4.3.2 VC生成DLL文件”,并在工程中建立、添加一个cpp文件、一个头文件。CPP文件代码以下:

image

image

image

    头文件代码以下:

image

    在上述代码中,咱们经过__asm指令完成了C++代码与汇编代码的嵌套。咱们利用Windows IP辅助库的GetAdaptersInfo函数实现了网卡物理地址的获取。经过接口函数GetSerialNum,咱们能够在CVI中方便的调用该DLL,实现获取CPU的序列号以及网卡的物理地址的功能。

   CVI调用DLL

    在CVI中,咱们建立一个名为GetPhyNum的工程,将VC建立生成的DLL文件、lib文件以及头文件复制到工程目录下。在工程中新建一名为GetPhyNum.c的C语言源文件并将SerialNum.lib导入库文件添加进工程中。添加完毕后,工程目录如所示。

image

图 4‑26 CVI获取CPU、网卡序列号的工程目录

    GetPhyNum.c源代码以下所示:

image

    在上述C语言文件中,咱们经过定义一个字符串并将字符串指针传给GetSerialNum函数。GetSerialNum函数运行时会完成将指针指向的字符串赋值的功能。最后咱们调用printf函数显示获取获得的含有CPU的序列号以及网卡物理地址的字符串。

   调试、运行

    当CVI运行时,屏幕上会显示出CPU序列号以及网卡物理地址。程序运行结果如所示。

image

图 4‑27 CVI获取CPU序列号、网卡物理地址运行结果

    运行计算机硬件检测程序Everest查看计算机的CPU序列号与网卡物理地址,能够发现以上程序检测获得的CPU序列号与网卡物理地址均准确无误。

image

图 4‑28 用Everest软件检测获得的CPU序列号与网卡物理地址

4.6 探索与实验

 

  1.6.1 实验

    使用CVI实时获取USB摄像头中的图像,显示在界面中,并使用算法检测一张白纸上的黑色方块。检测到的方块用红色方框实时标注出来。

  1.6.2 探索

    探索利用Matlab生成DLL文件并在CVI中调用Matlab生成的DLL的方法。

相关文章
相关标签/搜索