Where there is a dream ,there is hope

  C++博客 :: 首页 :: 联系 :: 聚合  :: 管理
  64 Posts :: 0 Stories :: 8 Comments :: 0 Trackbacks

常用链接

留言簿(1)

我参与的团队

搜索

  •  

最新评论

阅读排行榜

评论排行榜

#

原文地址:http://hi.baidu.com/freedomknightduzhi/blog/item/a0504560d1277555ebf8f8ff.html

1:神马是Dll和Lib,神马是静态链接和动态链接

大家都懂的,DLL就是动态链接库,LIB是静态链接库。DLL其实就是EXE,只不过没main。

动态链接是相对于静态链接而言的。所谓静态链接就是把函数或过程直接链接到可执行文件中,成为可执行程序中的一部分,当多个程序调用同样的函数时,内存里就会有这个函数的多个拷贝,浪费内存资源。而动态链接则是提供了一个函数的描述信息给可执行文件(并没有内存拷贝),当程序被夹在到内存里开始运行的时候,系统会在底层创建DLL和应用程序之间的连接关系,当执行期间需要调用DLL函数时,系统才会真正根据链接的定位信息去执行DLL中的函数代码。

在WINDOWS32系统底下,每个进程有自己的32位的线性地址空间,若一个DLL被进程使用,则该DLL首先会被调入WIN32系统的全局堆栈,然后通过内存映射文件方式映射到这个DLL的进程地址空间。若一个DLL被多个进程调用,则每个进程都会接收到该DLL的一个映像,而非多份的拷贝。但,在WIN16系统下,每个进程需要拥有自己的一份DLL空间,可以理解为何静态链接没啥区别。

 

2:DLL和LIB区别和联系。

DLL是程序在运行阶段才需要的文件。

LIB是程序编译时需要链接的文件。

DLL只有一种,其中一定是函数和过程的实现。

LIB是有两种。若只生成LIB的话,则这个LIB是静态编译出来的,它内部包含了函数索引以及实现,这个LIB会比较大。若生成DLL的话,则也会生成一个LIB,这个LIB和刚才那个LIB不同,它是只有函数索引,没有实现的,它很小。但是这俩LIB依然遵循上个原则,是在编译时候是需要被链接的。若不链接第一个LIB的话,在程序运行时会无法找到函数实现,当掉。若不链接第二个LIB的话,在程序运行时依然会无法找到函数实现。但第二种LIB有一种替代方式,就是在程序里,使用LoadLibrary,GetProcAddress替代第二个LIB的功能。第一种LIB生成的EXE文件会很大,因为LIB所有信息被静态链接进EXE里了。第二种LIB生成的EXE文件会比较小,因为函数过程实现依旧在DLL内。

(啰嗦了一堆,某志希望大家能够明白两个LIB的区别。要再不行的话,我们可以将静态编译的LIB称为 静态链接库。但动态编译的LIB称为 引入库。可能会比较好一些。)

静态链接LIB的优点是免除挂接动态链接库,缺点是EXE大,版本控制麻烦些。

动态链接DLL的优点是文件小,版本更换时换DLL就好了,缺点是多了点文件。动态链接若是被多个进程使用,会更加方便和节省内存。

 

3:为什么编译DLL时总会同时生成一个LIB?这个LIB有用吗?

若我们不是用静态链接,而使用DLL,那么我们也需要一个LIB,这个LIB的作用是被链接到程序里,在程序运行时告诉系统你需要什么DLL文件。这个LIB里保存的是DLL的名字和输出函数入口的顺序表。它是有意义的。

当然,若我们的应用程序里不链接这个LIB,则可以使用LoadLibrary,GetProcAddress来告诉系统我们在运行时需要怎么着DLL以及其内的函数。

 

4:DLL意义。

1:DLL真正实现了跨语言。各种语言都可以生成DLL,而对系统以及应用程序来说,哪种语言生成的DLL是没有区别的。

2:DLL有足够的封装性,对于版本更新有很大好处。因为DLL是运行期间才会使用,所以,即使DLL内函数实现有变化(只要参数和返回值不发生变化),程序是不需要进行编译的。大大提高了软件开发和维护的效率。

3:DLL被多个进程使用,因为有内存映射机制,无需占用更多内存。

 

5:创建DLL。(注意:某志就不再讲解使用MFC AppWizard[dll] 方式创建DLL了。有兴趣的自己去百度。这里创建DLL只指使用Win32 Dynamic-link Library创建Non-MFC DLL。呃,DLL的三种类型就不解释了,依旧那句话:百度一下你就知道。)

每个应用程序必须有一个main或者winmain函数作为入口,DLL一样,有自己的缺省的入口函数,就是DllMain。函数如下

BOOL APIENTRY DllMain( HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
 switch (ul_reason_for_call)
 {
 case DLL_PROCESS_ATTACH:   // 进程被调用
 case DLL_THREAD_ATTACH:     // 线程被调用
 case DLL_THREAD_DETACH:   // 线程被停止
 case DLL_PROCESS_DETACH:  // 进程被停止
  break;
 }
 return TRUE;
}

一般情况下,我们不需要对这个缺省的入口函数进行什么修改,它就会使动态链接库得到正确的初始化。但是,当我们的DLL需要额外分配内存或者资源的时候,或者,DLL希望对调用自己的进程或线程进行初始化或清除的额外操作时,可以在上述代码case中加一些自己感冒的东东。(懒……不想细写了- -Orz,现在是晚上2点了,明天还一堆的事情)

DLL对于导出类和导出函数没啥不同。只要加上 __declspec( dllexport ) 修饰函数或者类就好了。

但是有查看过DLL代码的人员都会经常见到这么一段代码

#ifdef FK_DLL_EXPORTS

#define FK_DLL __declspec( dllexport )

#else

#define FK_DLL __declspec( dllimport )

#endif

意义很明显,但是,问题是  FK_DLL_EXPORTS 这个宏是应该在哪儿定义呢?在DLL项目内,还是在使用DLL的应用程序内?

这点某志曾迷糊很久,呵呵~其实后来想想,还是蛮明确的。export是导出。import是导入。对于DLL来说,是要导出这些函数给其他应用程序使用的,所以应当定义 FK_DLL_EXPORTS 宏。对于使用DLL的应用程序来说,是导入,是无需定义的。

使用时候也很简单。

class FK_DLL CMyDllClass{} ;

则整个类被导出。

FK_DLL void MyTestFun( int a );

则该函数被导出。

但是有时我们可以见到这样的代码

extern "C" FK_DLL void MyTestFun2( float b );

其中extern "C"的原理就是标示该函数要求以C形式去进行编译,不要以C++形式去编译。具体的编译原理就不罗嗦了,简而言之,被extern "C"定义函数,可以被C以及其他语言进行DLL调用,而未被extern "C"定义的函数,C是无法访问DLL中这个函数的。

 

在VS中开发DLL还有一种方式,使用.def文件。

新建个文本文档,改后缀为FKDll.def,加入到工程里。

FKDll.def里加入以下代码

LIBRARY FKDll

EXPORTS

MyTestFun@1

MyTestFun2@2

就可以了。其中,LIBRARY语句是说明.def文件是属于FKDll这个Dll的。EXPORTS下面是我们需要导出的函数名。后面加的@+数字,是表示导出函数的顺序编号。这样就足够了。(详细的自己百度,好困,zzzZZZ)

 

6:使用DLL

使用DLL有两种方式。显式链接和隐式链接。

隐式链接很容易。直接#progam comment(lib, "FKDll.lib") 就可以。当然,也可以在项目工程->属性->链接库里加上库和路径(相对路径和绝对路径都可以)。

显式链接则麻烦些。在程序中使用LoadLibrary加载DLL,再GetProcAddress获取函数实现,在程序退出之前,调用FreeLibrary来动态释放掉链接库。

‍例如:

void Main()

{

     typedef void (*FKDllFun1)(int a);

    FKDllFun1 pFun1;

    HINSTANCE hDLL  = LoadLibrary("FKDll.dll");   // 若hDll为空则读取Dll失败。

    pFun1 = (pFun1)GetProcAddress(hDll, "MyTestFun1" );   // 从应用程序中的DLL镜像中获取名为 MyTestFun1 的函数指针

    pFun1( 100 );

    FreeLibrary(hDll);

}

当然,我们刚才.def里面还指定了导出函数的导出顺序,那么我们可以修改里面获取函数指针那一段为

‍pFun1 = (pFun1)GetProcAddress(hDll, MAKEINTERSOURCE(1) );  // 1 是刚才指定的MyTestFun1函数导出顺序编号。

这样可以更快,但是别将编号记混了,会导致诡异的错误。

 

7:比较显式链接和隐式链接。

可能的话,尽量使用显式链接。

显式链接可以在程序执行时动态的加载DLL和卸载DLL文件,隐式链接是做不到的。

显式链接LoadLibrary,GetProcAddress时能获知是否加载失败,我们可以对其进行检查错误处理。而显式链接可能是一个很恶劣的提示或是程序崩溃的结果。

对于有些Ex类型的加强函数,显式链接可以允许我们找到替代方案。也包括选择D3d9.dll和OpenGL.dll时也可采用同样处理。

例如:

if( GetProcAddress( hDll, "FKDllFunEx") == NULL )

{

‍    pFun = GetProcAddress( hDll, "FKDllFun");    // 然后使用pFun进行处理

}

 

8:导出类和导出函数

类和函数的导出方式上面给出了说明,原本极其类似的。

我们说下使用导出类。

若我们隐式的使用了一个导出类,则我们在应用程序里继承它的时候,就如同该类就在应用程序代码里一样,无需任何处理。

例如:

class FK_DLL CMyDllClass{} ;    // Dll文件内的代码

-----------------------

class CAppClass : public CMyDllClass      // 应用程序内代码,无需做任何处理。

{

       ....

}

也可以直接使用DLL导出类

void main

{

     CMyDllClass* pClass = new CMyDllClass ();

}

但是,若应用程序声明或者分类一个DLL中导出类的对象时会存在一个很讨厌的问题:这个操作会使内存跟踪系统失效,使其错误的报告内存分配和释放情况。

为解决这个问题,我们可以给出两个接口函数对DLL导出类进行创建销毁支持,就可以使内存跟踪系统正常了。例如

class FK_DLL CMyDllClass{} ; 

额外增加俩函数

FK_DLL CMyDllClass* CreateMyDllClass(){ return new CMyDllClass(); }

FK_DLL void DestoryMyDllClass( CMyDllClass* p_pClass ){ delete p_pClass; }

-----------------------------------------------

上面的方法可以正确进行内存跟踪了,但是,因为DLL导出类CMyDllClass依旧是导出的状态,用户同样可以跳过我们提供的接口直接使用。那么怎么办呢。方法是不再对类进行DLL导出,而对类内的函数全部进行DLL导出即可,

-----------------------------------------------

但是若仅仅提供上面两个接口函数以及类内全部函数,的确功能可以实现,却无法进行类继承了。若这个类继承很重要,必须开放,那么就需要使用新的内存跟踪程序替换应用程序内的原有内存跟踪程序。或者使用下面的一个方法。(见模块9:复杂问题)

-----------------------------------------------

同样,我们也可以发现,在不导出DLL类本身,而只导出DLL类内函数也有一些好处,一些我们不希望外界知道的函数可以不设置导出标记,这进一步保护了DLL内函数的安全性。

 

9:复杂问题。

若我们使用LoadLibrary显式加载一个DLL,并尝试在应用程序中调用一个类内成员函数的话,无论该函数是否在头文件中有声明,VS会给出一个"unresolved external symbol(未解析的外部符号)"的错误。我们此时可以将项目属性中的内联函数扩展选项修改为"Only __inline"或"Any Suitable"即可。但,我们可能在调试连编的时候期望关闭内联函数扩展,那么另一种解决方案是,将希望导出的函数声明为虚函数,例如

class CMyDllClass

{

   FK_DLL virtual void MyTestFun( int a ){  dosth(); }  

   // 用上面代码替换 FK_DLL void MyTestFun( int a ){  dosth(); }  

}

这样做还有一个额外的好处。将导出的类成员函数设置为虚函数之后,该虚函数所在的类在应用程序中也如同被声明一样,可以接受继承。

例如若是上面的做法,应用程序就可以进行顺利继承,而不必要求CMyDllClass 被标示为导出。(原理不知,希望精通底层的高手协助解释。)

class CAppClass : public CMyDllClass      // 应用程序内代码,无需做任何处理。

{

       ....

}


posted @ 2011-01-17 11:55 IT菜鸟 阅读(552) | 评论 (0)编辑 收藏

 

 见到这两个符号的很多不同的用法,整理在一起

宏中"#""##"的用法

一、一般用法

我们使用#把宏参数变为一个字符串,##把两个宏参数贴合在一起.

用法:

#include<cstdio>

#include<climits>

using namespace std;

#define STR(s)   #s

#define CONS(a,b) int(a##e##b)

int main()

{

 printf(STR(vck));       // 输出字符串"vck"

 printf("%d\n", CONS(2,3)); // 2e3 输出:2000

 return 0;

}

 

二、当宏参数是另一个宏的时候

需要注意的是凡宏定义里有用'#''##'的地方宏参数是不会再展开.

1, '#''##'的情况

#define TOW     (2)

#define MUL(a,b) (a*b)

printf("%d*%d=%d\n", TOW, TOW, MUL(TOW,TOW));

这行的宏会被展开为:

printf("%d*%d=%d\n", (2), (2), ((2)*(2)));

MUL里的参数TOW会被展开为(2).

2, 当有'#''##'的时候

#define A       (2)

#define STR(s)   #s

#define CONS(a,b) int(a##e##b)

printf("int max: %s\n", STR(INT_MAX));   // INT_MAX #include<climits>

这行会被展开为:

printf("int max: %s\n", "INT_MAX");

printf("%s\n", CONS(A, A));           // compile error

这一行则是:

printf("%s\n", int(AeA));

A不会再被展开, 然而解决这个问题的方法很简单. 加多一层中间转换宏.

加这层宏的用意是把所有宏的参数在这层里全部展开, 那么在转换宏里的那一个宏(_STR)就能得到正确的宏参数.

#define A       (2)

#define _STR(s)   #s

#define STR(s)     _STR(s)       // 转换宏

#define _CONS(a,b) int(a##e##b)

#define CONS(a,b)   _CONS(a,b)     // 转换宏

printf("int max: %s\n", STR(INT_MAX));       // INT_MAX,int型的最大值,为一个变量 #include<climits>

输出为: int max: 0x7fffffff

STR(INT_MAX) --> _STR(0x7fffffff) 然后再转换成字符串;

printf("%d\n", CONS(A, A));

输出为:200

CONS(A, A) --> _CONS((2), (2)) --> int((2)e(2))

三、'#''##'的一些应用特例

1、合并匿名变量名

#define ___ANONYMOUS1(type, var, line) type var##line

#define __ANONYMOUS0(type, line) ___ANONYMOUS1(type, _anonymous, line)

#define ANONYMOUS(type) __ANONYMOUS0(type, __LINE__)

例:ANONYMOUS(static int); : static int _anonymous70; 70表示该行行号;

第一层:ANONYMOUS(static int); --> __ANONYMOUS0(static int, __LINE__);

第二层:                 --> ___ANONYMOUS1(static int, _anonymous, 70);

第三层:                 --> static int _anonymous70;

即每次只能解开当前层的宏,所以__LINE__在第二层才能被解开;

2、填充结构

#define FILL(a)   {a, #a}

enum IDD{OPEN, CLOSE};

typedef struct MSG{

IDD id;

const char * msg;

}MSG;

MSG _msg[] = {FILL(OPEN), FILL(CLOSE)};

相当于:

MSG _msg[] = {{OPEN, "OPEN"},

        {CLOSE, "CLOSE"}};

3、记录文件名

#define _GET_FILE_NAME(f)   #f

#define GET_FILE_NAME(f)   _GET_FILE_NAME(f)

static char FILE_NAME[] = GET_FILE_NAME(__FILE__);

4、得到一个数值类型所对应的字符串缓冲大小

#define _TYPE_BUF_SIZE(type) sizeof #type

#define TYPE_BUF_SIZE(type)   _TYPE_BUF_SIZE(type)

char buf[TYPE_BUF_SIZE(INT_MAX)];

 --> char buf[_TYPE_BUF_SIZE(0x7fffffff)];

 --> char buf[sizeof "0x7fffffff"];

这里相当于:

char buf[11];

posted @ 2010-12-17 14:34 IT菜鸟 阅读(332) | 评论 (0)编辑 收藏

原文地址:http://zhedahht.blog.163.com/blog/static/254111742007127104759245/
题目:输入一棵二元查找树,将该二元查找树转换成一个排序的双向链表。要求不能创建任何新的结点,只调整指针的指向。

  比如将二元查找树
    
                                        10
                                          /    \
                                        6       14
                                      /  \     /  \
                                    4     8  12    16
转换成双向链表

4=6=8=10=12=14=16

  分析:本题是微软的面试题。很多与树相关的题目都是用递归的思路来解决,本题也不例外。下面我们用两种不同的递归思路来分析。

  思路一:当我们到达某一结点准备调整以该结点为根结点的子树时,先调整其左子树将左子树转换成一个排好序的左子链表,再调整其右子树转换右子链表。最近链接左子链表的最右结点(左子树的最大结点)、当前结点和右子链表的最左结点(右子树的最小结点)。从树的根结点开始递归调整所有结点。

  思路二:我们可以中序遍历整棵树。按照这个方式遍历树,比较小的结点先访问。如果我们每访问一个结点,假设之前访问过的结点已经调整成一个排序双向链表,我们再把调整当前结点的指针将其链接到链表的末尾。当所有结点都访问过之后,整棵树也就转换成一个排序双向链表了。

参考代码:

首先我们定义二元查找树结点的数据结构如下:
    struct BSTreeNode // a node in the binary search tree
    {
        int          m_nValue; // value of node
        BSTreeNode  *m_pLeft;  // left child of node
        BSTreeNode  *m_pRight; // right child of node
    };

思路一对应的代码:
///////////////////////////////////////////////////////////////////////
// Covert a sub binary-search-tree into a sorted double-linked list
// Input: pNode - the head of the sub tree
//        asRight - whether pNode is the right child of its parent
// Output: if asRight is true, return the least node in the sub-tree
//         else return the greatest node in the sub-tree
///////////////////////////////////////////////////////////////////////
BSTreeNode* ConvertNode(BSTreeNode* pNode, bool asRight)
{
      if(!pNode)
            return NULL;

      BSTreeNode *pLeft = NULL;
      BSTreeNode *pRight = NULL;

      // Convert the left sub-tree
      if(pNode->m_pLeft)
            pLeft = ConvertNode(pNode->m_pLeft, false);

      // Connect the greatest node in the left sub-tree to the current node
      if(pLeft)
      {
            pLeft->m_pRight = pNode;
            pNode->m_pLeft = pLeft;
      }

      // Convert the right sub-tree
      if(pNode->m_pRight)
            pRight = ConvertNode(pNode->m_pRight, true);

      // Connect the least node in the right sub-tree to the current node
      if(pRight)
      {
            pNode->m_pRight = pRight;
            pRight->m_pLeft = pNode;
      }

      BSTreeNode *pTemp = pNode;

      // If the current node is the right child of its parent, 
      // return the least node in the tree whose root is the current node
      if(asRight)
      {
            while(pTemp->m_pLeft)
                  pTemp = pTemp->m_pLeft;
      }
      // If the current node is the left child of its parent, 
      // return the greatest node in the tree whose root is the current node
      else
      {
            while(pTemp->m_pRight)
                  pTemp = pTemp->m_pRight;
      }
 
      return pTemp;
}

///////////////////////////////////////////////////////////////////////
// Covert a binary search tree into a sorted double-linked list
// Input: the head of tree
// Output: the head of sorted double-linked list
///////////////////////////////////////////////////////////////////////
BSTreeNode* Convert(BSTreeNode* pHeadOfTree)
{
      // As we want to return the head of the sorted double-linked list,
      // we set the second parameter to be true
      return ConvertNode(pHeadOfTree, true);
}

思路二对应的代码:
///////////////////////////////////////////////////////////////////////
// Covert a sub binary-search-tree into a sorted double-linked list
// Input: pNode -           the head of the sub tree
//        pLastNodeInList - the tail of the double-linked list
///////////////////////////////////////////////////////////////////////
void ConvertNode(BSTreeNode* pNode, BSTreeNode*& pLastNodeInList)
{
      if(pNode == NULL)
            return;

      BSTreeNode *pCurrent = pNode;

      // Convert the left sub-tree
      if (pCurrent->m_pLeft != NULL)
            ConvertNode(pCurrent->m_pLeft, pLastNodeInList);

      // Put the current node into the double-linked list
      pCurrent->m_pLeft = pLastNodeInList; 
      if(pLastNodeInList != NULL)
            pLastNodeInList->m_pRight = pCurrent;

      pLastNodeInList = pCurrent;

      // Convert the right sub-tree
      if (pCurrent->m_pRight != NULL)
            ConvertNode(pCurrent->m_pRight, pLastNodeInList);
}

///////////////////////////////////////////////////////////////////////
// Covert a binary search tree into a sorted double-linked list
// Input: pHeadOfTree - the head of tree
// Output: the head of sorted double-linked list
///////////////////////////////////////////////////////////////////////
BSTreeNode* Convert_Solution1(BSTreeNode* pHeadOfTree)
{
      BSTreeNode *pLastNodeInList = NULL;
      ConvertNode(pHeadOfTree, pLastNodeInList);

      // Get the head of the double-linked list
      BSTreeNode *pHeadOfList = pLastNodeInList;
      while(pHeadOfList && pHeadOfList->m_pLeft)
            pHeadOfList = pHeadOfList->m_pLeft;

      return pHeadOfList;
}

posted @ 2010-12-17 08:58 IT菜鸟 阅读(315) | 评论 (0)编辑 收藏

原文地址:
http://blog.csdn.net/fly2k5/archive/2005/12/05/544112.aspx
在C语言中,假设我们有这样的一个函数:
  
  int function(int a,int b)
  
  调用时只要用result = function(1,2)这样的方式就可以使用这个函数。但是,当高级语言被编译成计算机可以识别的机器码时,有一个问题就凸现出来:在CPU中,计算机没有办法知道一个函数调用需要多少个、什么样的参数,也没有硬件可以保存这些参数。也就是说,计算机不知道怎么给这个函数传递参数,传递参数的工作必须由函数调用者和函数本身来协调。为此,计算机提供了一种被称为栈的数据结构来支持参数传递。

  栈是一种先进后出的数据结构,栈有一个存储区、一个栈顶指针。栈顶指针指向堆栈中第一个可用的数据项(被称为栈顶)。用户可以在栈顶上方向栈中加入数据,这个操作被称为压栈(Push),压栈以后,栈顶自动变成新加入数据项的位置,栈顶指针也随之修改。用户也可以从堆栈中取走栈顶,称为弹出栈(pop),弹出栈后,栈顶下的一个元素变成栈顶,栈顶指针随之修改。

  函数调用时,调用者依次把参数压栈,然后调用函数,函数被调用以后,在堆栈中取得数据,并进行计算。函数计算结束以后,或者调用者、或者函数本身修改堆栈,使堆栈恢复原装。

  在参数传递中,有两个很重要的问题必须得到明确说明:
  
  当参数个数多于一个时,按照什么顺序把参数压入堆栈 
  函数调用后,由谁来把堆栈恢复原装 
  在高级语言中,通过函数调用约定来说明这两个问题。常见的调用约定有:

  stdcall 
  cdecl 
  fastcall 
  thiscall 
  naked call

  stdcall调用约定
  stdcall很多时候被称为pascal调用约定,因为pascal是早期很常见的一种教学用计算机程序设计语言,其语法严谨,使用的函数调用约定就是stdcall。在Microsoft C++系列的C/C++编译器中,常常用PASCAL宏来声明这个调用约定,类似的宏还有WINAPI和CALLBACK。

  stdcall调用约定声明的语法为(以前文的那个函数为例):
  
  int __stdcall function(int a,int b)
  
  stdcall的调用约定意味着:1)参数从右向左压入堆栈,2)函数自身修改堆栈 3)函数名自动加前导的下划线,后面紧跟一个@符号,其后紧跟着参数的尺寸

  以上述这个函数为例,参数b首先被压栈,然后是参数a,函数调用function(1,2)调用处翻译成汇编语言将变成:

  push 2        第二个参数入栈
  push 1        第一个参数入栈
  call function    调用参数,注意此时自动把cs:eip入栈

  而对于函数自身,则可以翻译为: 
  push ebp       保存ebp寄存器,该寄存器将用来保存堆栈的栈顶指针,可以在函数退出时恢复
  mov ebp, esp    保存堆栈指针
  mov eax,[ebp + 8H] 堆栈中ebp指向位置之前依次保存有ebp, cs:eip, a, b, ebp +8指向a
  add eax,[ebp + 0CH] 堆栈中ebp + 12处保存了b
  mov esp, ebp    恢复esp
  pop ebp
  ret 8

  而在编译时,这个函数的名字被翻译成_function@8

  注意不同编译器会插入自己的汇编代码以提供编译的通用性,但是大体代码如此。其中在函数开始处保留esp到ebp中,在函数结束恢复是编译器常用的方法。

  从函数调用看,2和1依次被push进堆栈,而在函数中又通过相对于ebp(即刚进函数时的堆栈指针)的偏移量存取参数。函数结束后,ret 8表示清理8个字节的堆栈,函数自己恢复了堆栈。

  
  cdecl调用约定
  cdecl调用约定又称为C调用约定,是C语言缺省的调用约定,它的定义语法是:

  int function (int a ,int b) //不加修饰就是C调用约定
  int __cdecl function(int a,int b)//明确指出C调用约定

  在写本文时,出乎我的意料,发现cdecl调用约定的参数压栈顺序是和stdcall是一样的,参数首先由右向左压入堆栈。所不同的是,函数本身不清理堆栈,调用者负责清理堆栈。由于这种变化,C调用约定允许函数的参数的个数是不固定的,这也是C语言的一大特色。对于前面的function函数,使用cdecl后的汇编码变成:

  调用处
  push 1
  push 2
  call function
  add esp, 8     注意:这里调用者在恢复堆栈

  被调用函数_function处
  push ebp       保存ebp寄存器,该寄存器将用来保存堆栈的栈顶指针,可以在函数退出时恢复
  mov ebp,esp     保存堆栈指针
  mov eax,[ebp + 8H] 堆栈中ebp指向位置之前依次保存有ebp,cs:eip,a,b,ebp +8指向a
  add eax,[ebp + 0CH] 堆栈中ebp + 12处保存了b
  mov esp,ebp     恢复esp
  pop ebp
  ret         注意,这里没有修改堆栈

  MSDN中说,该修饰自动在函数名前加前导的下划线,因此函数名在符号表中被记录为_function,但是我在编译时似乎没有看到这种变化。

  由于参数按照从右向左顺序压栈,因此最开始的参数在最接近栈顶的位置,因此当采用不定个数参数时,第一个参数在栈中的位置肯定能知道,只要不定的参数个数能够根据第一个后者后续的明确的参数确定下来,就可以使用不定参数,例如对于CRT中的sprintf函数,定义为:
  int sprintf(char* buffer,const char* format,...)
  由于所有的不定参数都可以通过format确定,因此使用不定个数的参数是没有问题的。

  fastcall
  fastcall调用约定和stdcall类似,它意味着: 
  
  函数的第一个和第二个DWORD参数(或者尺寸更小的)通过ecx和edx传递,其他参数通过从右向左的顺序压栈 
  被调用函数清理堆栈 
  函数名修改规则同stdcall 
  其声明语法为:int fastcall function(int a, int b)

  thiscall
  thiscall是唯一一个不能明确指明的函数修饰,因为thiscall不是关键字。它是C++类成员函数缺省的调用约定。由于成员函数调用还有一个this指针,因此必须特殊处理,thiscall意味着:

  参数从右向左入栈 
  如果参数个数确定,this指针通过ecx传递给被调用者;如果参数个数不确定,this指针在所有参数压栈后被压入堆栈。对参数个数不定的,调用者清理堆栈,否则函数自己清理堆栈为了说明这个调用约定,定义如下类和使用代码:

  class A
  {
  public:
    int function1(int a,int b);
    int function2(int a,...);
  };

  int A::function1 (int a,int b)
  {
    return a+b;
  }

  #include <stdarg.h>
  int A::function2(int a,...)
  {
    va_list ap;
    va_start(ap,a);
    int i;
    int result = 0;
    for(i = 0 ; i < a ; i ++)
    {
     result += va_arg(ap,int);
    }
    return result;
  }

  void callee()
  {
    A a;
    a.function1(1, 2);
    a.function2(3, 1, 2, 3);
  }

callee函数被翻译成汇编后就变成: 
  //函数function1调用
  00401C1D  push    2
  00401C1F  push    1
  00401C21  lea     ecx,[ebp-8]
  00401C24  call    function1     注意,这里this没有被入栈

  //函数function2调用
  00401C29  push    3
  00401C2B  push    2
  00401C2D  push    1
  00401C2F  push    3
  00401C31  lea     eax, [ebp-8]    这里引入this指针
  00401C34  push    eax
  00401C35  call    function2
  00401C3A  add     esp, 14h
  
  可见,对于参数个数固定情况下,它类似于stdcall,不定时则类似cdecl

  naked call
  这是一个很少见的调用约定,一般程序设计者建议不要使用。编译器不会给这种函数增加初始化和清理代码,更特殊的是,你不能用return返回返回值,只能用插入汇编返回结果。这一般用于实模式驱动程序设计,假设定义一个求和的加法程序,可以定义为:

  __declspec(naked) int add(int a,int b)
  {
    __asm mov eax,a
    __asm add eax,b
    __asm ret 
  }

  注意,这个函数没有显式的return返回值,返回通过修改eax寄存器实现,而且连退出函数的ret指令都必须显式插入。上面代码被翻译成汇编以后变成:

  mov eax,[ebp+8]
  add eax,[ebp+12]
  ret 8

  注意这个修饰是和__stdcall及cdecl结合使用的,前面是它和cdecl结合使用的代码,对于和stdcall结合的代码,则变成:

  __declspec(naked) int __stdcall function(int a,int b)
  {
    __asm mov eax,a
    __asm add eax,b
    __asm ret 8    //注意后面的8
  }

  至于这种函数被调用,则和普通的cdecl及stdcall调用函数一致。

  函数调用约定导致的常见问题
  如果定义的约定和使用的约定不一致,则将导致堆栈被破坏,导致严重问题,下面是两种常见的问题:

  函数原型声明和函数体定义不一致 
  DLL导入函数时声明了不同的函数约定 
  以后者为例,假设我们在dll种声明了一种函数为:

  __declspec(dllexport) int func(int a,int b);//注意,这里没有stdcall,使用的是cdecl
  使用时代码为:

  typedef int (*WINAPI DLLFUNC)func(int a,int b);
  hLib = LoadLibrary(...);

  DLLFUNC func = (DLLFUNC)GetProcAddress(...)//这里修改了调用约定
  result = func(1,2);//导致错误

  由于调用者没有理解WINAPI的含义错误的增加了这个修饰,上述代码必然导致堆栈被破坏,MFC在编译时插入的checkesp函数将告诉你,堆栈被破坏

posted @ 2010-12-15 15:42 IT菜鸟 阅读(178) | 评论 (0)编辑 收藏

ebp和esp是32位的SP,BP 
esp是堆栈指针    
ebp是基址指针 
ESP与SP的关系就象AX与AL,AH的关系.

32位CPU所含有的寄存器有:

4个数据寄存器(EAX、EBX、ECX和EDX)
2个变址和指针寄存器(ESI和EDI) 2个指针寄存器(ESP和EBP)
6个段寄存器(ES、CS、SS、DS、FS和GS)
1个指令指针寄存器(EIP) 1个标志寄存器(EFlags)

1、数据寄存器

数据寄存器主要用来保存操作数和运算结果等信息,从而节省读取操作数所需占用总线和访问存储器的时间。

32位CPU有4个32位的通用寄存器EAX、EBX、ECX和EDX。对低16位数据的存取,不会影响高16位的数据。这些
低16位寄存器分别命名为:AX、BX、CX和DX,它和先前的CPU中的寄存器相一致。

4个16位寄存器又可分割成8个独立的8位寄存器(AX:AH-AL、BX:BH-BL、CX:CH-CL、DX:DH-DL),每个寄
存器都有自己的名称,可独立存取。程序员可利用数据寄存器的这种“可分可合”的特性,灵活地处理字/字
节的信息。

寄存器AX和AL通常称为累加器(Accumulator),用累加器进行的操作可能需要更少时间。累加器可用于乘、
除、输入/输出等操作,它们的使用频率很高;
寄存器BX称为基地址寄存器(Base Register)。它可作为存储器指针来使用;
寄存器CX称为计数寄存器(Count Register)。在循环和字符串操作时,要用它来控制循环次数;在位操作
中,当移多位时,要用CL来指明移位的位数;
寄存器DX称为数据寄存器(Data Register)。在进行乘、除运算时,它可作为默认的操作数参与运算,也
可用于存放I/O的端口地址。


在16位CPU中,AX、BX、CX和DX不能作为基址和变址寄存器来存放存储单元的地址,但在32位CPU中,其32位
寄存器EAX、EBX、ECX和EDX不仅可传送数据、暂存数据保存算术逻辑运算结果,而且也可作为指针寄存器,
所以,这些32位寄存器更具有通用性。

2、变址寄存器

32位CPU有2个32位通用寄存器ESI和EDI。其低16位对应先前CPU中的SI和DI,对低16位数据的存取,不影响
高16位的数据。

寄存器ESI、EDI、SI和DI称为变址寄存器(Index Register),它们主要用于存放存储单元在段内的偏移量,
用它们可实现多种存储器操作数的寻址方式,为以不同的地址形式访问存储单元提供方便。

变址寄存器不可分割成8位寄存器。作为通用寄存器,也可存储算术逻辑运算的操作数和运算结果。

它们可作一般的存储器指针使用。在字符串操作指令的执行过程中,对它们有特定的要求,而且还具有特
殊的功能。

3、指针寄存器

32位CPU有2个32位通用寄存器EBP和ESP。其低16位对应先前CPU中的SBP和SP,对低16位数据的存取,不影
响高16位的数据。

寄存器EBP、ESP、BP和SP称为指针寄存器(Pointer Register),主要用于存放堆栈内存储单元的偏移量,
用它们可实现多种存储器操作数的寻址方式,为以不同的地址形式访问存储单元提供方便。

指针寄存器不可分割成8位寄存器。作为通用寄存器,也可存储算术逻辑运算的操作数和运算结果。

它们主要用于访问堆栈内的存储单元,并且规定:

BP为基指针(Base Pointer)寄存器,用它可直接存取堆栈中的数据;
SP为堆栈指针(Stack Pointer)寄存器,用它只可访问栈顶。

4、段寄存器

段寄存器是根据内存分段的管理模式而设置的。内存单元的物理地址由段寄存器的值和一个偏移量组合而成
的,这样可用两个较少位数的值组合成一个可访问较大物理空间的内存地址。

CPU内部的段寄存器:

CS——代码段寄存器(Code Segment Register),其值为代码段的段值;
DS——数据段寄存器(Data Segment Register),其值为数据段的段值;
ES——附加段寄存器(Extra Segment Register),其值为附加数据段的段值;
SS——堆栈段寄存器(Stack Segment Register),其值为堆栈段的段值;
FS——附加段寄存器(Extra Segment Register),其值为附加数据段的段值;
GS——附加段寄存器(Extra Segment Register),其值为附加数据段的段值。

在16位CPU系统中,它只有4个段寄存器,所以,程序在任何时刻至多有4个正在使用的段可直接访问;在32位
微机系统中,它有6个段寄存器,所以,在此环境下开发的程序最多可同时访问6个段。

32位CPU有两个不同的工作方式:实方式和保护方式。在每种方式下,段寄存器的作用是不同的。有关规定简
单描述如下:

实方式: 前4个段寄存器CS、DS、ES和SS与先前CPU中的所对应的段寄存器的含义完全一致,内存单元的逻辑
地址仍为“段值:偏移量”的形式。为访问某内存段内的数据,必须使用该段寄存器和存储单元的偏移量。
保护方式: 在此方式下,情况要复杂得多,装入段寄存器的不再是段值,而是称为“选择子”(Selector)的某个值。。

5、指令指针寄存器

32位CPU把指令指针扩展到32位,并记作EIP,EIP的低16位与先前CPU中的IP作用相同。

指令指针EIP、IP(Instruction Pointer)是存放下次将要执行的指令在代码段的偏移量。在具有预取指令功
能的系统中,下次要执行的指令通常已被预取到指令队列中,除非发生转移情况。所以,在理解它们的功能
时,不考虑存在指令队列的情况。

在实方式下,由于每个段的最大范围为64K,所以,EIP中的高16位肯定都为0,此时,相当于只用其低16位
的IP来反映程序中指令的执行次序。

6、标志寄存器

一、运算结果标志位
1、进位标志CF(Carry Flag)
进位标志CF主要用来反映运算是否产生进位或借位。如果运算结果的最高位产生了一个进位或借位,那么,其值为1,否则其值为0。

使用该标志位的情况有:多字(字节)数的加减运算,无符号数的大小比较运算,移位操作,字(字节)之间移位,专门改变CF值的指令等。

2、奇偶标志PF(Parity Flag)
奇偶标志PF用于反映运算结果中“1”的个数的奇偶性。如果“1”的个数为偶数,则PF的值为1,否则其值为0。

利用PF可进行奇偶校验检查,或产生奇偶校验位。在数据传送过程中,为了提供传送的可靠性,如果采用奇偶校验的方法,就可使用该标志位。

3、辅助进位标志AF(Auxiliary Carry Flag)
在发生下列情况时,辅助进位标志AF的值被置为1,否则其值为0:

(1)、在字操作时,发生低字节向高字节进位或借位时;
(2)、在字节操作时,发生低4位向高4位进位或借位时。

对以上6个运算结果标志位,在一般编程情况下,标志位CF、ZF、SF和OF的使用频率较高,而标志位PF和AF的使用频率较低。

4、零标志ZF(Zero Flag)
零标志ZF用来反映运算结果是否为0。如果运算结果为0,则其值为1,否则其值为0。在判断运算结果是否为0时,可使用此标志位。

5、符号标志SF(Sign Flag)
符号标志SF用来反映运算结果的符号位,它与运算结果的最高位相同。在微机系统中,有符号数采用补码表示法,所以,SF也就反映运算结果的正负号。运算结果为正数时,SF的值为0,否则其值为1。

6、溢出标志OF(Overflow Flag)
溢出标志OF用于反映有符号数加减运算所得结果是否溢出。如果运算结果超过当前运算位数所能表示的范围,则称为溢出,OF的值被置为1,否则,OF的值被清为0。

“溢出”和“进位”是两个不同含义的概念,不要混淆。如果不太清楚的话,请查阅《计算机组成原理》课程中的有关章节。

二、状态控制标志位
状态控制标志位是用来控制CPU操作的,它们要通过专门的指令才能使之发生改变。

1、追踪标志TF(Trap Flag)
当追踪标志TF被置为1时,CPU进入单步执行方式,即每执行一条指令,产生一个单步中断请求。这种方式主要用于程序的调试。

指令系统中没有专门的指令来改变标志位TF的值,但程序员可用其它办法来改变其值。

2、中断允许标志IF(Interrupt-enable Flag)
中断允许标志IF是用来决定CPU是否响应CPU外部的可屏蔽中断发出的中断请求。但不管该标志为何值,CPU都必须响应CPU外部的不可屏蔽中断所发出的中断请求,以及CPU内部产生的中断请求。具体规定如下:

(1)、当IF=1时,CPU可以响应CPU外部的可屏蔽中断发出的中断请求;

(2)、当IF=0时,CPU不响应CPU外部的可屏蔽中断发出的中断请求。

CPU的指令系统中也有专门的指令来改变标志位IF的值。

3、方向标志DF(Direction Flag)
方向标志DF用来决定在串操作指令执行时有关指针寄存器发生调整的方向。具体规定在第5.2.11节——字符串操作指令——中给出。在微机的指令系统中,还提供了专门的指令来改变标志位DF的值。

三、32位标志寄存器增加的标志位
1、I/O特权标志IOPL(I/O Privilege Level)
I/O特权标志用两位二进制位来表示,也称为I/O特权级字段。该字段指定了要求执行I/O指令的特权级。如果当前的特权级别在数值上小于等于IOPL的值,那么,该I/O指令可执行,否则将发生一个保护异常。

2、嵌套任务标志NT(Nested Task)
嵌套任务标志NT用来控制中断返回指令IRET的执行。具体规定如下:

(1)、当NT=0,用堆栈中保存的值恢复EFLAGS、CS和EIP,执行常规的中断返回操作;

(2)、当NT=1,通过任务转换实现中断返回。

3、重启动标志RF(Restart Flag)
重启动标志RF用来控制是否接受调试故障。规定:RF=0时,表示“接受”调试故障,否则拒绝之。在成功执行完一条指令后,处理机把RF置为0,当接受到一个非调试故障时,处理机就把它置为1。

4、虚拟8086方式标志VM(Virtual 8086 Mode)
如果该标志的值为1,则表示处理机处于虚拟的8086方式下的工作状态,否则,处理机处于一般保护方式下的工作状态
posted @ 2010-12-15 11:17 IT菜鸟 阅读(1005) | 评论 (0)编辑 收藏

一、常用指令 

1. 通用数据传送指令

   MOV 传送字或字节

   MOVSX 先符号扩展,再传送

   MOVZX 先零扩展,再传送

   PUSH 把字压入堆栈

   POP 把字弹出堆栈

   PUSHA AX,CX,DX,BX,SP,BP,SI,DI依次压入堆栈

   POPA DI,SI,BP,SP,BX,DX,CX,AX依次弹出堆栈

   PUSHAD EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI依次压入堆栈

   POPAD EDI,ESI,EBP,ESP,EBX,EDX,ECX,EAX依次弹出堆栈

   BSWAP 交换32位寄存器里字节的顺序 

   XCHG 交换字或字节.( 至少有一个操作数为寄存器,段寄存器不可作为操作数

   CMPXCHG 比较并交换操作数.( 第二个操作数必须为累加器AL/AX/EAX ) 

   XADD 先交换再累加.( 结果在第一个操作数里

   XLAT 字节查表转换

   BX 指向一张 256 字节的表的起点, AL 为表的索引值 (0-255, 0-FFH); 返回 AL 为查表结果. ( [BX+AL]-> AL

2. 输入输出端口传送指令

   IN I/O端口输入. ( 语法: IN 累加器, {端口号│DX} ) 

   OUT I/O端口输出. ( 语法: OUT {端口号│DX},累加器

   输入输出端口由立即方式指定时, 其范围是 0-255; 由寄存器 DX 指定时,其范围是 0-65535. 

3. 目的地址传送指令

   LEA 装入有效地址

     : LEA DX,string ;把偏移地址存到DX. 

   LDS 传送目标指针,把指针内容装入DS. 

     : LDS SI,string ;把段地址:偏移地址存到DS:SI. 

   LES 传送目标指针,把指针内容装入ES. 

     : LES DI,string ;把段地址:偏移地址存到ESDI. 

   LFS 传送目标指针,把指针内容装入FS. 

     : LFS DI,string ;把段地址:偏移地址存到FSD. 

   LGS 传送目标指针,把指针内容装入GS. 

     : LGS DI,string ;把段地址:偏移地址存到GSDI. 

   LSS 传送目标指针,把指针内容装入SS. 

     : LSS DI,string ;把段地址:偏移地址存到SSDI. 

4. 标志传送指令

   LAHF 标志寄存器传送,把标志装入AH. 

   SAHF 标志寄存器传送,AH内容装入标志寄存器

   PUSHF 标志入栈

   POPF 标志出栈

   PUSHD 32位标志入栈

   POPD 32位标志出栈

二、算术运算指令 

   ADD 加法

   ADC 带进位加法

   INC 1. 

   AAA 加法的ASCII码调整

   DAA 加法的十进制调整

   SUB 减法

   SBB 带借位减法

   DEC 1. 

   NEC 求反( 0 减之). 

   CMP 比较.(两操作数作减法,仅修改标志位,不回送结果). 

   AAS 减法的ASCII码调整

   DAS 减法的十进制调整

   MUL 无符号乘法

  IMUL 整数乘法

     以上两条,结果回送AHAL(字节运算),DXAX(字运算), 

   AAM 乘法的ASCII码调整

   DIV 无符号除法

   IDIV 整数除法

     以上两条,结果回送

     商回送AL,余数回送AH, (字节运算); 

     商回送AX,余数回送DX, (字运算). 

   AAD 除法的ASCII码调整

   CBW 字节转换为字. (AL中字节的符号扩展到AH中去

   CWD 字转换为双字. (AX中的字的符号扩展到DX中去

   CWDE 字转换为双字. (AX中的字符号扩展到EAX中去

   CDQ 双字扩展. (EAX中的字的符号扩展到EDX中去

三、逻辑运算指令 

   AND 与运算

   OR 或运算

   XOR 异或运算

   NOT 取反

   TEST 测试.(两操作数作与运算,仅修改标志位,不回送结果). 

   SHL 逻辑左移

   SAL 算术左移.(=SHL) 

   SHR 逻辑右移

   SAR 算术右移.(=SHR) 

   ROL 循环左移

   ROR 循环右移

   RCL 通过进位的循环左移

   RCR 通过进位的循环右移

     以上八种移位指令,其移位次数可达255

     移位一次时, 可直接用操作码. SHL AX,1. 

     移位>1次时, 则由寄存器CL给出移位次数

      MOV CL,04 

         SHL AX,CL 

四、串指令 

   DS:SI 源串段寄存器 :源串变址

   ES I 目标串段寄存器:目标串变址

   CX 重复次数计数器

   AL/AX 扫描值

   D标志 0表示重复操作中SIDI应自动增量; 1表示应自动减量

Z标志 用来控制扫描或比较操作的结束

   MOVS 串传送

   ( MOVSB 传送字符. MOVSW 传送字. MOVSD 传送双字. ) 

   CMPS 串比较

   ( CMPSB 比较字符. CMPSW 比较字. ) 

   SCAS 串扫描

     ALAX的内容与目标串作比较,比较结果反映在标志位

   LODS 装入串

     把源串中的元素(字或字节)逐一装入ALAX

   ( LODSB 传送字符. LODSW 传送字. LODSD 传送双字. ) 

   STOS 保存串

   LODS的逆过程

   REP CX/ECX<>0时重复

   REPE/REPZ ZF=1或比较结果相等,CX/ECX<>0时重复

   REPNE/REPNZ ZF=0或比较结果不相等,CX/ECX<>0时重复

  REPC CF=1CX/ECX<>0时重复

   REPNC CF=0CX/ECX<>0时重复

五、程序转移指令 

1>无条件转移指令 (长转移

   JMP 无条件转移指令 

   CALL 过程调用 

   RET/RETF过程返回

2>条件转移指令 (短转移,-128+127的距离内

   ( 当且仅当(SF XOR OF)=1,OP1<OP2 ) 

   JA/JNBE <

posted @ 2010-12-15 11:01 IT菜鸟 阅读(249) | 评论 (0)编辑 收藏

今天写一个小程序出现了这种问题:
>c:\program files\microsoft sdks\windows\v6.0a\include\ws2def.h(91) : warning C4005: “AF_IPX”: 宏重定义
1>        c:\program files\microsoft sdks\windows\v6.0a\include\winsock.h(460) : 参见“AF_IPX”的前一个定义
1>c:\program files\microsoft sdks\windows\v6.0a\include\ws2def.h(127) : warning C4005: “AF_MAX”: 宏重定义
1>        c:\program files\microsoft sdks\windows\v6.0a\include\winsock.h(479) : 参见“AF_MAX”的前一个定义
1>c:\program files\microsoft sdks\windows\v6.0a\include\ws2def.h(163) : warning C4005: “SO_DONTLINGER”: 宏重定义
1>        c:\program files\microsoft sdks\windows\v6.0a\include\winsock.h(402) : 参见“SO_DONTLINGER”的前一个定义
1>c:\program files\microsoft sdks\windows\v6.0a\include\ws2def.h(206) : error C2011: “sockaddr”: “struct”类型重定义
1>        c:\program files\microsoft sdks\windows\v6.0a\include\winsock.h(485) : 参见“sockaddr”的声明
1>c:\program files\microsoft sdks\windows\v6.0a\include\ws2def.h(384) : error C2143: 语法错误 : 缺少“}”(在“常量”的前面)
1>c:\program files\microsoft sdks\windows\v6.0a\include\ws2def.h(384) : error C2143: 语法错误 : 缺少“;”(在“常量”的前面)
1>c:\program files\microsoft sdks\windows\v6.0a\include\ws2def.h(384) : error C2059: 语法错误 : “常量”
1>c:\program files\microsoft sdks\windows\v6.0a\include\ws2def.h(437) : error C2143: 语法错误 : 缺少“;”(在“}”的前面)
1>c:\program files\microsoft sdks\windows\v6.0a\include\ws2def.h(437) : error C4430: 缺少类型说明符 - 假定为 int。注意: C++ 不支持默认 int
1>c:\program files\microsoft sdks\windows\v6.0a\include\ws2def.h(437) : error C4430: 缺少类型说明符 - 假定为 int。注意: C++ 不支持默认 int
1>c:\program files\microsoft sdks\windows\v6.0a\include\ws2def.h(518) : warning C4005: “IN_CLASSA”: 宏重定义
1>        c:\program files\microsoft sdks\windows\v6.0a\include\winsock.h(287) : 参见“IN_CLASSA”的前一个定义
1>c:\program files\microsoft sdks\windows\v6.0a\include\ws2def.h(524) : warning C4005: “IN_CLASSB”: 宏重定义
1>        c:\program files\microsoft sdks\windows\v6.0a\include\winsock.h(293) : 参见“IN_CLASSB”的前一个定义
1>c:\program files\microsoft sdks\windows\v6.0a\include\ws2def.h(530) : warning C4005: “IN_CLASSC”: 宏重定义
1>        c:\program files\microsoft sdks\windows\v6.0a\include\winsock.h(299) : 参见“IN_CLASSC”的前一个定义
1>c:\program files\microsoft sdks\windows\v6.0a\include\ws2def.h(541) : warning C4005: “INADDR_ANY”: 宏重定义
1>        c:\program files\microsoft sdks\windows\v6.0a\include\winsock.h(304) : 参见“INADDR_ANY”的前一个定义
1>c:\program files\microsoft sdks\windows\v6.0a\include\ws2def.h(543) : warning C4005: “INADDR_BROADCAST”: 宏重定义
1>        c:\program files\microsoft sdks\windows\v6.0a\include\winsock.h(306) : 参见“INADDR_BROADCAST”的前一个定义
1>c:\program files\microsoft sdks\windows\v6.0a\include\ws2def.h(577) : error C2011: “sockaddr_in”: “struct”类型重定义
1>        c:\program files\microsoft sdks\windows\v6.0a\include\winsock.h(312) : 参见“sockaddr_in”的声明
1>c:\program files\microsoft sdks\windows\v6.0a\include\winsock2.h(132) : error C2011: “fd_set”: “struct”类型重定义
1>        c:\program files\microsoft sdks\windows\v6.0a\include\winsock.h(68) : 参见“fd_set”的声明
1>c:\program files\microsoft sdks\windows\v6.0a\include\winsock2.h(167) : warning C4005: “FD_SET”: 宏重定义
1>        c:\program files\microsoft sdks\windows\v6.0a\include\winsock.h(102) : 参见“FD_SET”的前一个定义
1>c:\program files\microsoft sdks\windows\v6.0a\include\winsock2.h(176) : error C2011: “timeval”: “struct”类型重定义
1>        c:\program files\microsoft sdks\windows\v6.0a\include\winsock.h(111) : 参见“timeval”的声明


好久没写TCP的程序了,都忘记是怎么回事了,隐约记得解决方法很简单。搜索了一下,记录下来:

引用链接:http://www.cnblogs.com/tonyyang132/archive/2009/10/14/1583110.html

初看到如此一堆的错误委实不爽,但是只要将二者的包含顺序调换一下问题就会解决,原因参见下面那个链接。另外,上述问题不仅影响直接包含二者的文件,还影响间接包含的情形。比如,a.h包含了windows.h,b.h包含了winsock2.h,如果在c.h当中要引用a.h和b.h,那么正确的顺序应当是b.h先于a.h。当然,实践当中有时很难找到究竟是哪两个文件顺序不对了,终极的解决办法是,在当前工程(就是编译不过的这个工程)所有include语句最前面加上#include <winsock2.h>和#include<windows.h>,世界清静了。


关于WINSOCK.H与winsock2.h中的重定义解决办法分析
问题描述:在 VC 6.0中使用socket相关的函数时没有什么问题,可是到了.net下就有以下类似的错误,
[C++ Error] winsock2.h(109): E2238 Multiple declaration for 'fd_set'
[C++ Error] winsock.h(54): E2344 Earlier declaration of 'fd_set'
[C++ Error] winsock2.h(112): E2146 Need an identifier to declare
[C++ Warning] winsock2.h(144): W8017 Redefinition of 'FD_SET' is not identical
[C++ Error] winsock2.h(153): E2238 Multiple declaration for 'timeval'
[C++ Error] winsock.h(97): E2344 Earlier declaration of 'timeval'
[C++ Error] winsock2.h(209): E2238 Multiple declaration for 'hostent'
[C++ Error] winsock.h(153): E2344 Earlier declaration of 'hostent'
[C++ Error] winsock2.h(222): E2238 Multiple declaration for 'netent'
[C++ Error] winsock.h(166): E2344 Earlier declaration of 'netent'
[C++ Error] winsock2.h(229): E2238 Multiple declaration for 'servent'
[C++ Error] winsock.h(173): E2344 Earlier declaration of 'servent'
[C++ Error] winsock2.h(241): E2238 Multiple declaration for 'protoent'
[C++ Error] winsock.h(185): E2344 Earlier declaration of 'protoent'
[C++ Error] winsock2.h(327): E2238 Multiple declaration for 'in_addr'
[C++ Error] winsock.h(269): E2344 Earlier declaration of 'in_addr'
[C++ Error] winsock2.h(385): E2238 Multiple declaration for 'sockaddr_in'
[C++ Error] winsock.h(319): E2344 Earlier declaration of 'sockaddr_in'
[C++ Error] winsock2.h(395): E2238 Multiple declaration for 'WSAData'
[C++ Error] winsock.h(329): E2344 Earlier declaration of 'WSAData'
[C++ Error] winsock2.h(411): E2146 Need an identifier to declare
[C++ Warning] winsock2.h(455): W8017 Redefinition of 'SO_DONTLINGER' is not identical
[C++ Warning] winsock2.h(512): W8017 Redefinition of 'AF_IPX' is not identical
[C++ Warning] winsock2.h(540): W8017 Redefinition of 'AF_MAX' is not identical
[C++ Error] winsock2.h(546): E2238 Multiple declaration for 'sockaddr'
[C++ Error] winsock.h(492): E2344 Earlier declaration of 'sockaddr'
[C++ Error] winsock2.h(586): E2238 Multiple declaration for 'sockproto'
[C++ Error] winsock.h(501): E2344 Earlier declaration of 'sockproto'
[C++ Error] winsock2.h(625): E2238 Multiple declaration for 'linger'
[C++ Error] winsock2.h(625): E2228 Too many error or warning messages

Solution:

This problem arises because windows.h (at least, that version of it) includes not winsock2.h but winsock.h; sadly when Microsoft wrote winsock2.h they chose neither to change windows.h to include winsock2.h, which replaces winsock.h, nor to include windows.h from winsock2.h and then add the definitions for the new Winsock 2 API methods & structures (this might seem reasonable since Winsock 2 does, strictly speaking, replace Winsock 1, but since the API must be fully backwards-compatible the distinction is somewhat meaningless and there's no real benefit to making winsock2.h standalone).

The fix is thankfully simple: always "#include <winsock2.h>" before windows.h.

However, you must remember that if windows.h has been included by (for example) a higher-level header file that is subsequently including your header file, it's too late - so you must make sure that the higher-level header files respect this convention also.

It is however rarely necessary to modify the header files of libraries or other code modules you are using just because you include their header files, and their header files include windows.h - you can just include winsock2.h before you include the library's header files.


在包含jrtplib有时候我也遇到这个问题,解决方法与之相同。一句话,在#include<windows.h>之前 #include <winsock2.h> 问题就可以解决。

问题描述]
   在包含了<windows.h>以及<winsock2.h>的工程中,编译有时会出现如
下错误:

     error C2011: 'fd_set' : 'struct' type redefinition
     error C2011: 'timeval' : 'struct' type redefinition
                     ....
     error C2375: 'accept' : redefinition; different linkage
[原因分析]
   主要原因是因为<windows.h>中包含了<winsock.h>头文件,由于其版
本的不同,导致出
现上述的错误。<windows.h>中相关代码如下:
               #ifndef WIN32_LEAN_AND_MEAN
               #include <cderr.h>
               #include <dde.h>
               #include <ddeml.h>
               ........
                #ifndef _MAC
               #include <winperf.h>
               #include <winsock.h>
               #endif
                .......

               #include <commdlg.h>
               #endif
               #endif
[解决方案]
    由以上代码可以看出如果在没有定义WIN32_LEAN_AND_MEAN宏
的大前
提下windows.h有可能包含winsock.h 头文件,因此我们得出一个很简单
的解决方
法就是在包含<windows.h>之前定义WIN32_LEAN_AND_MEAN宏,如
下所示:
#define WIN32_LEAN_AND_MEAN
#include <windows.h>

posted @ 2010-12-07 15:30 IT菜鸟 阅读(1126) | 评论 (0)编辑 收藏

     摘要: 原地址:http://www.cnblogs.com/DonLiang/archive/2008/02/16/1070717.html近段时间,有几个刚刚开始学习C#语言的爱好者问我:C#中的函数,其参数的传递,按值传递和按引用传递有什么区别。针对这一问题,我简单写了个示例程序,用以讲解,希望我没有把他们绕晕。因为,常听别人说起:“你不说我还明白,你一说,我就糊涂了”。&n...  阅读全文
posted @ 2010-12-03 09:38 IT菜鸟 阅读(501) | 评论 (0)编辑 收藏

看如下代码:
template <int I> class Test
{
    union Obj
    
{
        union Obj 
*next;
        
char data[1];
    }
;

    
static Obj* freeList[16];
    
static T* ms_singleton;
}
;

第二个静态变量初始化很容易:
template<Class T>
T
* Test<T>::ms_singleton=0;

第一个呢?
要这样:
template<class T>
typename Test
<T>::Obj* Test<T>::freeList[16]={0};

用typename关键字来告诉编译器Obj是个类型。
posted @ 2010-11-26 09:30 IT菜鸟 阅读(1094) | 评论 (0)编辑 收藏

C#结构体定义:
[Serializable]
    
struct DR2DE_Send
    
{
        
public UInt32 AA;
        
public UInt32 BB;
        
public UInt16 CC;   //! Encryption method.
        public UInt16 DD;            //! message version  // used to decide message version.
        public UInt32 EE;           //! message id.
        public UInt32 FF;          //! Message order. it is a increseable integer and set when checkEncrypt called and when ReturnMessage is set.
        public Byte GG;
        
public Byte LL;
        
public char[] MM;
    }

序列化操作:
byte[] sendMsg = new byte[1024];
            BinaryFormatter bf 
= new BinaryFormatter();
            MemoryStream stream 
= new MemoryStream();
            DR2DE_Send dr2deMessage;
            dr2deMessage.AA
= (UInt32)(0);
            dr2deMessage.BB
= (UInt16)(0);
            dr2deMessage.CC
=(UInt32)(0);
            dr2deMessage.DD
= (UInt16)(0);
            dr2deMessage.EE
= "Hello".ToCharArray();
            dr2deMessage.FF
= 40010;
            dr2deMessage.GG
= (UInt32)(22 + dr2deMessage.data.Length);
            dr2deMessage.HH
= 1;
            dr2deMessage.II
= 1;

            bf.Serialize(stream, dr2deMessage);
            sendMsg 
= stream.ToArray();
sendMsg就可以放到sock中发送出去了。
posted @ 2010-11-24 10:53 IT菜鸟 阅读(1237) | 评论 (0)编辑 收藏

仅列出标题
共7页: 1 2 3 4 5 6 7