2011年12月2日星期五

<转>Critical Section,Mutex,Semaphore,Event区别

临界区(Critical Section
保证在某一时刻只有一个线程能访问数据的简便办法。在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。   
临界区包含两个操作原语: EnterCriticalSection() 进入临界区 LeaveCriticalSection() 离开临界区EnterCriticalSection()语句执行后代码将进入临界区以后无论发生什么,必须确保与之匹配的LeaveCriticalSection()都能够被执行到。否则临界区保护的共享资源将永远不会被释放。在使用临界区时,一般不允许其运行时间过长,只要进入临界区的线程还没有离开,其他所有试图进入此临界区的线程都会被挂起而进入到等待状态,并会在一定程度上影响。程序的运行性能。尤其需要注意的是不要将等待用户输入或是其他一些外界干预的操作包含到临界区。如果进入了临界区却一直没有释放,同样也会引起其他线程的长时间等待。换句话说,在执行了EnterCriticalSection()语句进入临界区后无论发生什么,必须确保与之匹配的LeaveCriticalSection()都能够被执行到。可以通过添加结构化异常处理代码来确保LeaveCriticalSection()语句的执行。虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。
MFC提供了很多功能完备的类,我用MFC实现了临界区。MFC为临界区提供有一个CCriticalSection类,使用该类进行线程同步处理是非常简单的。只需在线程函数中用CCriticalSection类成员函数Lock()和UnLock()标定出被保护代码片段即可。Lock()后代码用到的资源自动被视为临界区内的资源被保护。UnLock后别的线程才能访问这些资源。
互斥量(Mutex
互斥(Mutex)是一种用途非常广泛的内核对象。能够保证多个线程对同一共享资源的互斥访问。同临界区有些类似,只有拥有互斥对象的线程才具有访问资源的权限,由于互斥对象只有一个,因此就决定了任何情况下此共享资源都不会同时被多个线程所访问。当前占据资源的线程在任务处理完后应将拥有的互斥对象交出,以便其他线程在获得后得以访问资源。与其他几种内核对象不同,互斥对象在操作系统中拥有特殊代码,并由操作系统来管理,操作系统甚至还允许其进行一些其他内核对象所不能进行的非常规操作。 互斥量跟临界区很相似,只有拥有互斥对象的线程才具有访问资源的权限,由于互斥对象只有一个,因此就决定了任何情况下此共享资源都不会同时被多个线程所访问。当前占据资源的线程在任务处理完后应将拥有的互斥对象交出,以便其他线程在获得后得以访问资源。互斥量比临界区复杂。因为使用互斥不仅仅能够在同一应用程序不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对资源的安全共享。
以互斥内核对象来保持线程同步可能用到的函数主要有CreateMutex()、OpenMutex()、ReleaseMutex()、WaitForSingleObject()和WaitForMultipleObjects()等。在使用互斥对象前,首先要通过CreateMutex()或OpenMutex()创建或打开一个互斥对象。CreateMutex()函数原型为:
HANDLE CreateMutex(
 LPSECURITY_ATTRIBUTES lpMutexAttributes, // 安全属性指针
 BOOL bInitialOwner, // 初始拥有者
 LPCTSTR lpName // 互斥对象名
);
参数bInitialOwner主要用来控制互斥对象的初始状态。一般多将其设置为FALSE,以表明互斥对象在创建时并没有为任何线程所占有。如果在创建互斥对象时指定了对象名,那么可以在本进程其他地方或是在其他进程通过OpenMutex()函数得到此互斥对象的句柄。OpenMutex()函数原型为:
HANDLE OpenMutex(
 DWORD dwDesiredAccess, // 访问标志
 BOOL bInheritHandle, // 继承标志
 LPCTSTR lpName // 互斥对象名
);
当目前对资源具有访问权的线程不再需要访问此资源而要离开时,必须通过ReleaseMutex()函数来释放其拥有的互斥对象,其函数原型为:
BOOL ReleaseMutex(HANDLE hMutex);
其唯一的参数hMutex为待释放的互斥对象句柄。至于WaitForSingleObject()和WaitForMultipleObjects()等待函数在互斥对象保持线程同步中所起的作用与在其他内核对象中的作用是基本一致的,也是等待互斥内核对象的通知。但是这里需要特别指出的是:在互斥对象通知引起调用等待函数返回时,等待函数的返回值不再是通常的WAIT_OBJECT_0(对于WaitForSingleObject()函数)或是在WAIT_OBJECT_0WAIT_OBJECT_0+nCount-1之间的一个值(对于WaitForMultipleObjects()函数),而是将返回一个WAIT_ABANDONED_0(对于WaitForSingleObject()函数)或是在WAIT_ABANDONED_0WAIT_ABANDONED_0+nCount-1之间的一个值(对于WaitForMultipleObjects()函数)。以此来表明线程正在等待的互斥对象由另外一个线程所拥有,而此线程却在使用完共享资源前就已经终止。除此之外,使用互斥对象的方法在等待线程的可调度性上同使用其他几种内核对象的方法也有所不同,其他内核对象在没有得到通知时,受调用等待函数的作用,线程将会挂起,同时失去可调度性,而使用互斥的方法却可以在等待的同时仍具有可调度性,这也正是互斥对象所能完成的非常规操作之一。
在编写程序时,互斥对象多用在对那些为多个线程所访问的内存块的保护上,可以确保任何线程在处理此内存块时都对其拥有可靠的独占访问权。
互斥对象在MFC中通过CMutex类进行表述。使用CMutex类的方法非常简单,在构造CMutex类对象的同时可以指明待查询的互斥对象的名字,在构造函数返回后即可访问此互斥变量。CMutex类也是只含有构造函数这唯一的成员函数,当完成对互斥对象保护资源的访问后,可通过调用从父类CSyncObject继承的UnLock()函数完成对互斥对象的释放。CMutex类构造函数原型为:
CMutex( BOOL bInitiallyOwn = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );
该类的适用范围和实现原理与API方式创建的互斥内核对象是完全类似的,但要简洁的多。
信号量(Semaphores
信号量对象对线程的同步方式与前面几种方法不同,信号允许多个线程同时使用共享资源,这与操作系统中的PV操作相同。它指出了同时访问共享资源的线程最大数目。它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。在用CreateSemaphore()创建信号量时即要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减1,只要当前可用资源计数是大于0的,就可以发出信号量信号。但是当前可用计数减小到0时则说明当前占用资源的线程数已经达到了所允许的最大数目,不能在允许其他线程的进入,此时的信号量信号将无法发出。线程在处理完共享资源后,应在离开的同时通过ReleaseSemaphore()函数将当前可用资源计数加1。在任何时候当前可用资源计数决不可能大于最大资源计数。 信号量是通过计数来对线程访问资源进行控制的,而实际上信号量确实也被称作Dijkstra计数器。
PV操作及信号量的概念都是由荷兰科学家E.W.Dijkstra提出的。信号量S是一个整数,S大于等于零时代表可供并发进程使用的资源实体数,但S小于零时则表示正在等待使用共享资源的进程数。
    P操作申请资源:
    1S1
    2)若S1后仍大于等于零,则进程继续执行;
    3)若S1后小于零,则该进程被阻塞后进入与该信号相对应的队列中,然后转入进程调度。
  
    V操作 释放资源:
    1S1
    2)若相加结果大于零,则进程继续执行;
    3)若相加结果小于等于零,则从该信号的等待队列中唤醒一个等待进程,然后再返回原进程继续执行或转入进程调度。
使用信号量内核对象进行线程同步主要会用到CreateSemaphore()、OpenSemaphore()、ReleaseSemaphore()、WaitForSingleObject()和WaitForMultipleObjects()等函数。其中,CreateSemaphore()用来创建一个信号量内核对象,其函数原型为:
HANDLE CreateSemaphore(
 LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // 安全属性指针
 LONG lInitialCount, // 初始计数
 LONG lMaximumCount, // 最大计数
 LPCTSTR lpName // 对象名指针
);
参数lMaximumCount是一个有符号32位值,定义了允许的最大资源计数,最大取值不能超过4294967295lpName参数可以为创建的信号量定义一个名字,由于其创建的是一个内核对象,因此在其他进程中可以通过该名字而得到此信号量。OpenSemaphore()函数即可用来根据信号量名打开在其他进程中创建的信号量,函数原型如下:
HANDLE OpenSemaphore(
 DWORD dwDesiredAccess, // 访问标志
 BOOL bInheritHandle, // 继承标志
 LPCTSTR lpName // 信号量名
);
在线程离开对共享资源的处理时,必须通过ReleaseSemaphore()来增加当前可用资源计数。否则将会出现当前正在处理共享资源的实际线程数并没有达到要限制的数值,而其他线程却因为当前可用资源计数为0而仍无法进入的情况。ReleaseSemaphore()的函数原型为:
BOOL ReleaseSemaphore(
 HANDLE hSemaphore, // 信号量句柄
 LONG lReleaseCount, // 计数递增数量
 LPLONG lpPreviousCount // 先前计数
);
该函数将lReleaseCount中的值添加给信号量的当前资源计数,一般将lReleaseCount设置为1,如果需要也可以设置其他的值。WaitForSingleObject()和WaitForMultipleObjects()主要用在试图进入共享资源的线程函数入口处,主要用来判断信号量的当前可用资源计数是否允许本线程的进入。只有在当前可用资源计数值大于0时,被监视的信号量内核对象才会得到通知。
信号量的使用特点使其更适用于对Socket(套接字)程序中线程的同步。例如,网络上的HTTP服务器要对同一时间内访问同一页面的用户数加以限制,这时可以为没一个用户对服务器的页面请求设置一个线程,而页面则是待保护的共享资源,通过使用信号量对线程的同步作用可以确保在任一时刻无论有多少用户对某一页面进行访问,只有不大于设定的最大用户数目的线程能够进行访问,而其他的访问企图则被挂起,只有在有用户退出对此页面的访问后才有可能进入。
MFC中,通过CSemaphore类对信号量作了表述。该类只具有一个构造函数,可以构造一个信号量对象,并对初始资源计数、最大资源计数、对象名和安全属性等进行初始化,其原型如下:
CSemaphore( LONG lInitialCount = 1, LONG lMaxCount = 1, LPCTSTR pstrName = NULL, LPSECURITY_ATTRIBUTES lpsaAttributes = NULL );
在构造了CSemaphore类对象后,任何一个访问受保护共享资源的线程都必须通过CSemaphore从父类CSyncObject类继承得到的Lock()和UnLock()成员函数来访问或释放CSemaphore对象。与前面介绍的几种通过MFC类保持线程同步的方法类似,通过CSemaphore类也可以将前面的线程同步代码进行改写,这两种使用信号量的线程同步方法无论是在实现原理上还是从实现结果上都是完全一致的。
事件(Event
事件对象也可以通过通知操作的方式来保持线程的同步。并且可以实现不同进程中的线程同步操作。
    信号量包含的几个操作原语:
    CreateEvent() 创建一个信号量
    OpenEvent() 打开一个事件
    SetEvent() 回置事件
    WaitForSingleObject() 等待一个事件
    WaitForMultipleObjects() 等待多个事件
使用临界区只能同步同一进程中的线程,而使用事件内核对象则可以对进程外的线程进行同步,其前提是得到对此事件对象的访问权。可以通过OpenEvent()函数获取得到,其函数原型为:
HANDLE OpenEvent(
 DWORD dwDesiredAccess, // 访问标志
 BOOL bInheritHandle, // 继承标志
 LPCTSTR lpName // 指向事件对象名的指针
);
如果事件对象已创建(在创建事件时需要指定事件名),函数将返回指定事件的句柄。对于那些在创建事件时没有指定事件名的事件内核对象,可以通过使用内核对象的继承性或是调用DuplicateHandle()函数来调用CreateEvent()以获得对指定事件对象的访问权。在获取到访问权后所进行的同步操作与在同一个进程中所进行的线程同步操作是一样的。
如果需要在一个线程中等待多个事件,则用WaitForMultipleObjects()来等待。WaitForMultipleObjects()与WaitForSingleObject()类似,同时监视位于句柄数组中的所有句柄。这些被监视对象的句柄享有平等的优先权,任何一个句柄都不可能比其他句柄具有更高的优先权。WaitForMultipleObjects()的函数原型为:
DWORD WaitForMultipleObjects(
 DWORD nCount, // 等待句柄数
 CONST HANDLE *lpHandles, // 句柄数组首地址
 BOOL fWaitAll, // 等待标志
 DWORD dwMilliseconds // 等待时间间隔
);
参数nCount指定了要等待的内核对象的数目,存放这些内核对象的数组由lpHandles来指向。fWaitAll对指定的这nCount个内核对象的两种等待方式进行了指定,为TRUE时当所有对象都被通知时函数才会返回,为FALSE则只要其中任何一个得到通知就可以返回。dwMilliseconds在这里的作用与在WaitForSingleObject()中的作用是完全一致的。如果等待超时,函数将返回WAIT_TIMEOUT。如果返回WAIT_OBJECT_0WAIT_OBJECT_0+nCount-1中的某个值,则说明所有指定对象的状态均为已通知状态(当fWaitAllTRUE时)或是用以减去WAIT_OBJECT_0而得到发生通知的对象的索引(当fWaitAllFALSE时)。如果返回值在WAIT_ABANDONED_0WAIT_ABANDONED_0+nCount-1之间,则表示所有指定对象的状态均为已通知,且其中至少有一个对象是被丢弃的互斥对象(当fWaitAllTRUE时),或是用以减去WAIT_OBJECT_0表示一个等待正常结束的互斥对象的索引(当fWaitAllFALSE时)。
MFC为事件相关处理也提供了一个CEvent类,共包含有除构造函数外的4个成员函数PulseEvent()、ResetEvent()、SetEvent()和UnLock()。在功能上分别相当与Win32 APIPulseEvent()、ResetEvent()、SetEvent()和CloseHandle()等函数。而构造函数则履行了原CreateEvent()函数创建事件对象的职责,其函数原型为:
CEvent(BOOL bInitiallyOwn = FALSE, BOOL bManualReset = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );
按照此缺省设置将创建一个自动复位、初始状态为复位状态的没有名字的事件对象。封装后的CEvent类使用起来更加方便,事件可以实现不同进程中的线程同步操作,并且可以方便的实现多个线程的优先比较等待操作,例如写多个WaitForSingleObject来代替WaitForMultipleObjects从而使编程更加灵活。
总结:
1. 互斥量与临界区的作用非常相似,但互斥量是可以命名的,也就是说它可以跨越进程使用。所以创建互斥量需要的资源更多,所以如果只为了在进程内部是用的话使用临界区会带来速度上的优势并能够减少资源占用量。因为互斥量是跨进程的互斥量一旦被创建,就可以通过名字打开它。
2. 互斥量(Mutex),信号灯(Semaphore),事件(Event)都可以被跨越进程使用来进行同步数据操作,而其他的对象与数据同步操作无关,但对于进程和线程来讲,如果进程和线程在运行状态则为无信号状态,在退出后为有信号状态。所以可以使用WaitForSingleObject来等待进程和线程退出。
3. 通过互斥量可以指定资源被独占的方式使用,但如果有下面一种情况通过互斥量就无法处理,比如现在一位用户购买了一份三个并发访问许可的数据库系统,可以根据用户购买的访问许可数量来决定有多少个线程/进程能同时进行数据库操作,这时候如果利用互斥量就没有办法完成这个要求,信号灯对象可以说是一种资源计数器。

2011年11月10日星期四

<转>返回值为函数指针的函数

一个函数的返回值可以是一个指向函数的指针,下面是一个例子
#include <stdio.h>

int get_big(int i,int j)
{
        return i>=j?i:j;
}

int (*f(int a))(int,int)    //f(int a)是一个函数,这个函数的返回值是一个
{                             //指向函数(该函数的返回值值为int,有两个int型的形参)的指针
        printf("a=%d/n",a);
        return get_big;
}

int main(void)
{
        int max;
        int (*p)(int,int);     //定义了一个指向函数(该函数的返回值为int,有两个均为int的形参)的指针
        p=f(100);
        max=p(5,8);
        printf("max=%d/n",max);
        return 1;
}

2011年11月9日星期三

<转>dll和lib的区别

ib文件是在link的时候用   
dll文件是在Run的时候用
 
lib   是静态链接库  
dll   是动态链接库
 
dll你需要和你的程序一起发布才行  
lib编译后就不需要了
 
lib将在连接时把一些代码拷贝到你的程序代码里,所以叫做静态连接  
dll是程序运行时把dll里的代码和资源加再到进程地址空间去,所以叫动态连接
 
动态链接与静态链接的不同之处在于:动态链接允许可执行模块(.dll 文件或 .exe 文件)仅包含在运行时定位 DLL 函数的可执行代码所需的信息。在静态链接中,链接器从静态链接库获取所有被引用的函数,并将库同代码一起放到可执行文件中。
lib与dll文件最大区别在调用方面
dll可以静态陷入
 
.LIB 静态连接库(生成可执行文件)。  
.DLL
动态连接库(运行时加载)。
 
.lib .dll都是编译了的可执行代码   所不同的是,.lib中的执行代码的内容在你自己写的程序的编译过程中被拷贝了一份,而  
dll
只是标记了一个对这个dll文件的某一段代码(函数)的引用,你的程序运行时必须有   这一个.dll文件
 
调用方法存在不同 
使用LIB,你在工程内部不用调用LoadLibrary及GetProcAddress.  
 
一、关于调用方式:  
   
  1、静态调用方式:由编译系统完成对DLL的加载和应用程序结束时DLL卸载的编码(如还有其它程序使用该DLL,则Windows对DLL的应用记录减1,直到所有相关程序都结束对该DLL的使用时才释放它),简单实用,但不够灵活,只能满足一般要求。  
     隐式的调用:需要把产生动态连接库时产生的.LIB文件加入到应用程序的工程中,想使用DLL中的函数时,只须说明一下。隐式调用不需要调用 LoadLibrary()和FreeLibrary()。程序员在建立一个DLL文件时,链接程序会自动生成一个与之对应的LIB导入文件。该文件包含 了每一个DLL导出函数的符号名和可选的标识号,但是并不含有实际的代码。LIB文件作为DLL的替代文件被编译到应用程序项目中。当程序员通过静态链接 方式编译生成应用程序时,应用程序中的调用函数与LIB文件中导出符号相匹配,这些符号或标识号进入到生成的EXE文件中。LIB文件中也包含了对应的 DLL文件名(但不是完全的路径名),链接程序将其存储在EXE文件内部。当应用程序运行过程中需要加载DLL文件时,Windows根据这些信息发现并 加载DLL,然后通过符号名或标识号实现对DLL函数的动态链接。所有被应用程序调用的DLL文件都会在应用程序EXE文件加载时被加载在到内存中。可执 行程序链接到一个包含DLL输出函数信息的输入库文件(.LIB文件)。操作系统在加载使用可执行程序时加载DLL。可执行程序直接通过函数名调用DLL 的输出函数,调用方法和程序内部其他的函数是一样的。  
  2、动态调用方式:是由编程者用API函数加载和卸载DLL来达到调用DLL的目的,使用上较复杂,但能更加有效地使用内存,是编制大型应用程序时的重要方式。  
     显式的调用:是指在应用程序中用LoadLibrary或MFC提供的AfxLoadLibrary显式的将自己所做的动态连接库调进来,动态连接库的 文件名即是上面两个函数的参数,再用GetProcAddress()获取想要引入的函数。自此,你就可以象使用如同本应用程序自定义的函数一样来调用此 引入函数了。在应用程序退出之前,应该用FreeLibrary或MFC提供的AfxFreeLibrary释放动态连接库。直接调用Win32   的LoadLibary函数,并指定DLL的路径作为参数。LoadLibary返回HINSTANCE参数,应用程序在调用 GetProcAddress函数时使用这一参数。GetProcAddress函数将符号名或标识号转换为DLL内部的地址。程序员可以决定DLL文件 何时加载或不加载,显式链接在运行时决定加载哪个DLL文件。使用DLL的程序在使用之前必须加载(LoadLibrary)加载DLL从而得到一个 DLL模块的句柄,然后调用GetProcAddress函数得到输出函数的指针,在退出之前必须卸载DLL(FreeLibrary)。  
     Windows将遵循下面的搜索顺序来定位DLL:    
  1.包含EXE文件的目录,    
  2.进程的当前工作目录,    
  3.Windows系统目录,    
  4.Windows目录,    
  5.列在Path环境变量中的一系列目录。 
 
五、关于DLL的函数:  
     动态链接库中定义有两种函数:导出函数(export   function)和内部函数(internal   function)。导出函数可以被其它模块调用,内部函数在定义它们的DLL程序内部使用。  
   
  输出函数的方法有以下几种:  
  1、传统的方法  
          在模块定义文件的EXPORT部分指定要输入的函数或者变量。语法格式如下:  
  entryname[=internalname]   [@ordinal[NONAME]]   [DATA]   [PRIVATE]  
  其中:  
  entryname是输出的函数或者数据被引用的名称;  
  internalname同entryname;  
  @ordinal表示在输出表中的顺序号(index);  
  NONAME仅仅在按顺序号输出时被使用(不使用entryname);  
  DATA表示输出的是数据项,使用DLL输出数据的程序必须声明该数据项为_declspec(dllimport)。  
  上述各项中,只有entryname项是必须的,其他可以省略。  
       对于“C”函数来说,entryname可以等同于函数名;但是对“C++”函数(成员函数、非成员函数)来说,entryname是修饰名。可以 从.map映像文件中得到要输出函数的修饰名,或者使用DUMPBIN   /SYMBOLS得到,然后把它们写在.def文件的输出模块。DUMPBIN是VC提供的一个工具。  
      如果要输出一个“C++”类,则把要输出的数据和成员的修饰名都写入.def模块定义文件。  
  2、在命令行输出  
          对链接程序LINK指定/EXPORT命令行参数,输出有关函数。  
  3、使用MFC提供的修饰符号_declspec(dllexport)  
          在要输出的函数、类、数据的声明前加上_declspec(dllexport)的修饰符,表示输出。__declspec(dllexport)在C调 用约定、C编译情况下可以去掉输出函数名的下划线前缀。extern   "C"使得在C++中使用C编译方式成为可能。在“C++”下定义“C”函数,需要加extern   “C”关键词。用extern   "C"来指明该函数使用C编译方式。输出的“C”函数可以从“C”代码里调用。  
          例如,在一个C++文件中,有如下函数:  
          extern   "C"   {void     __declspec(dllexport)   __cdecl   Test(int   var);}  
  其输出函数名为:Test    
          MFC提供了一些宏,就有这样的作用。  
  AFX_CLASS_IMPORT:__declspec(dllexport)  
     
  AFX_API_IMPORT:__declspec(dllexport)  
  AFX_DATA_IMPORT:__declspec(dllexport)  
  AFX_CLASS_EXPORT:__declspec(dllexport)  
  AFX_API_EXPORT:__declspec(dllexport)  
  AFX_DATA_EXPORT:__declspec(dllexport)  
  AFX_EXT_CLASS:   #ifdef   _AFXEXT    
                                  AFX_CLASS_EXPORT  
                                  #else  
                                  AFX_CLASS_IMPORT  
  AFX_EXT_API:#ifdef   _AFXEXT  
                            AFX_API_EXPORT  
                            #else  
                            AFX_API_IMPORT  
  AFX_EXT_DATA:#ifdef   _AFXEXT  
                              AFX_DATA_EXPORT  
                              #else  
                              AFX_DATA_IMPORT
     像AFX_EXT_CLASS这样的宏,如果用于DLL应用程序的实现中,则表示输出(因为_AFX_EXT被定义,通常是在编译器的标识参数中指定该 选项/D_AFX_EXT);如果用于使用DLL的应用程序中,则表示输入(_AFX_EXT没有定义)。  
     要输出整个的类,对类使用_declspec(_dllexpot);要输出类的成员函数,则对该函数使用_declspec(_dllexport)。如:  
  class   AFX_EXT_CLASS   CTextDoc   :   public   CDocument  
  {  
          …  
  }  
   
  extern   "C"   AFX_EXT_API   void   WINAPI   InitMYDLL();  
   
          这几种方法中,最好采用第三种,方便好用;其次是第一种,如果按顺序号输出,调用效率会高些;最次是第二种。    
 
编译生成dll文件后,我们同时得到lib文件。这个lib和静态链接库的lib文件有何区别?谢谢!
怎么可能一样呢,和DLL一起生成.lib内只有函数名等信息,而不包括实现信息。
不一样.    
  1   静态lib中,有实现的代码  
  2   而dll输出的lib,只有连接信息.(即export项中的函数入口地址)  
      这样,   调用模块可以模拟这个lib去修改相应的import项
对,不一样。  
  静态lib链接后就不要lib的支持了,lib中的代码会合并到exe文件中。  
  dll的lib链接后还要dll的支持。 
1        静态lib中,包含实现的二进制码,连接时直接连入Exe文件的地址空间。 
2        dll输出的lib,仅含导出函数的地址和一些位址信息,可以帮助Link程序完成连接(在此时安排调用入口地址及函数回调信息)。这样,在运行时才将DLL中真正的代码调入执行,实现动态连接。
那我想知道静态的Lib怎么作?
怎么将DLL作为静态编译。
VC里有lib和implib,   都可以从.dll产生对应的.lib  
  如:lib   /DEF:SCSIIO.DLL
如何从dll文件导出对应的lib文件?  
  Visual   C++   开发工具提供了两个命令行工具,一个是dumpbin.exe,另一个是lib.exe。利用这两个工具即可从dll导出其对应的lib。  
  1、在命令行执行:  
  dumpbin   /exports   yourdll.dll   >   yourdll.def  
  2、编辑   yourdll.def   文件,使之格式与.def文件格式一致。比如:  
  EXPORTS;  
                fn1;  
                fn2;  
  3、在命令行执行:  
  lib   /def:yourdll.def   /machine:i386   /out:yourdll.lib