自己做的简单的socket原始套接字截获ip数据包

  看了一些网络上的资料,做课程实习搞了个
  供大家参考
  
#include “stdafx.h”
#include
#include
#include
#include
#include “stdio.h”
  
  #pragma comment(lib,”ws2_32.lib”)
  #pragma comment(lib,”netapi32.lib”)
  #pragma comment(lib,”wsock32.lib”)
  
  #define SIO_RCVALL _WSAIOW(IOC_VENDOR,1)
  #define BUFFER_SIZE 65535
  
  typedef struct _IP_HEADER //定义IP头
  {
   union
   {
   BYTE Version; //版本(前4位)
   BYTE HdrLen; //报头标长(后4位),IP头的长度
   };
   BYTE ServiceType; //服务类型
   WORD TotalLen; //总长度
   WORD ID; //标识
   union
   {
   WORD Flags; //标志(前3位)
   WORD FragOff; //分段偏移(后13位)
   };
   BYTE TimeToLive; //生命期
   BYTE Protocol; //协议
   WORD HdrChksum; //头校验和
   DWORD SrcAddr; //源地址
   DWORD DsrAddr; //目的地址
   BYTE Options; //选项
  }IP_HEADER;
  
  char buffer[BUFFER_SIZE];
  
  int main(int argc, char* argv[])
  {
  
   WORD wVersionRequested;
   WSADATA WsaData;
   int err;
   wVersionRequested = MAKEWORD( 2, 2 );
   err = WSAStartup(wVersionRequested,&WsaData);
   if ( err != 0 )
   {
   return 0;
   }
   if ( LOBYTE( WsaData.wVersion ) != 2 || HIBYTE( WsaData.wVersion ) != 2 )
   {
   WSACleanup();
   return 0;
   }
  
  
   ofstream ofs(argv[1],ios::app);
   /*创建原始套接字*/
   SOCKET sock;
   sock=WSASocket(AF_INET,SOCK_RAW,IPPROTO_IP,NULL,0,WSA_FLAG_OVERLAPPED);
  // sock=socket(AF_INET,SOCK_RAW,IPPROTO_IP);
  
   /*设置IP头操作选项*/
   BOOL flag=true;
   setsockopt(sock,IPPROTO_IP,IP_HDRINCL,(char*)&flag,sizeof(flag));
  
   /*socket初始化*/
   /*获取主机名*/
   char hostName[128];
   gethostname(hostName,128);
   /*获取本机IP地址*/
   hostent *pHostIP;
   pHostIP=gethostbyname(hostName);
   /*填充SOCKADDR_IN结构的内容*/
   //cout<><>
   sockaddr_in addr_in;
   addr_in.sin_addr=*(in_addr *)(pHostIP->h_addr_list[0]);
   addr_in.sin_family=AF_INET;
   addr_in.sin_port=htons(6000);
  /*绑定socket*/
   bind(sock,(PSOCKADDR)&addr_in,sizeof(addr_in));
  
  /*设置网卡为混杂模式*/
   DWORD dwBufferLen[10];
   DWORD dwBufferInLen=1;
   DWORD dwBytesReturned=0;
   WSAIoctl(sock,SIO_RCVALL,&dwBufferInLen,sizeof(dwBufferInLen),&dwBufferLen,
   sizeof(dwBufferLen),&dwBytesReturned,NULL,NULL);
  
  /*接收数据包*/
  while(true)
  {
   int DF;
   int MF;
   recv(sock,buffer,BUFFER_SIZE,0); //接收数据抱
   IP_HEADER ip=*(IP_HEADER *)buffer;
   /*解析数据包*/
   ofs<><(ip.version>>4)
   ofs<><(ip.hdrlen &="">
   ofs<><(ip.servicetype>>5)
   ofs<><((ip.servicetype>>1) & 0x0f)
   ofs<><>
   ofs<><>
   ofs<><(df=((ip.flags>>14)&0x01))
   ofs<><(mf=((ip.flags>>13)&0x01))<>
   ofs<><(ip.fragoff>
   ofs<><>
   ofs<><>
   ofs<><>
   ofs<><>
   ofs<><>
  
   cout<><(ip.version>>4)
   cout<><(ip.hdrlen &="">
   cout<><(ip.servicetype>>5)
   cout<><((ip.servicetype>>1) & 0x0f)
   cout<><>
   cout<><>
   cout<><(df=((ip.flags>>14)&0x01))
   cout<><(mf=((ip.flags>>13)&0x01))<>
   cout<><(ip.fragoff>
   cout<><>
   cout<><>
   cout<><>
   cout<><>
   cout<><>
  
  
  }
  return 0;
  }
  
  
  
  

分类:VC++ 相关 | 评论:0 | 浏览:1583 | 举报 | 收藏 |

网友评论:我要评论
暂无评论!

发布评论:









c++下的类型转换

  这四个操作符是, static_cast,const_cast, dynamic_cast, 和reinterpret_cast。在大多数情况下,对于这些操作符你只需要知道原来你习惯于这样写,(type)expression而现在你总应该这样写:
  
    static_cast(expression)
  
    例如,假设你想把一个int转换成double,以便让包含int类型变量的表达式产生出浮点数值的结果。如果用C风格的类型转换,你能这样写:
  
    int firstNumber, secondNumber;
    …
    double result = ((double)firstNumber)/secondNumber;
  
    如果用上述新的类型转换方法,你应该这样写:
  
    double result = static_cast(firstNumber)/secondNumber;
  
    这样的类型转换不论是对人工还是对程序都很容易识别。
  
    static_cast 在功能上基本上与C风格的类型转换一样强大,含义也一样。它也有功能上限制。例如,你不能用static_cast象用C风格的类型转换一样把struct转换成int类型或者把double类型转换成指针类型,另外,static_cast不能从表达式中去除const属性,因为另一个新的类型转换操作符const_cast有这样的功能。
  
    其它新的C++类型转换操作符被用在需要更多限制的地方。const_cast 用于类型转换掉表达式的const或volatileness属性。通过使用const_cast,你向人们和编译器强调你通过类型转换想做的只是改变一些东西的constness或者 volatileness属性。这个含义被编译器所约束。如果你试图使用const_cast来完成修改constness 或者volatileness属性之外的事情,你的类型转换将被拒绝。下面是一些例子:
  
    class Widget { … };
    class SpecialWidget: public Widget { … };
    void update(SpecialWidget *psw);
    SpecialWidget sw; // sw 是一个非const 对象
    const SpecialWidget& csw = sw; // csw 是sw的一个引用
     // 它是一个const 对象
  
    update(&csw); // 错误!不能传递一个const SpecialWidget* 变量
     // 给一个处理SpecialWidget*类型变量的函数
  
    update(const_cast(&csw));
       // 正确,csw的const被显示地转换掉(
       // csw和sw两个变量值在update
      // 函数中能被更新)
  
    update((SpecialWidget*)&csw);
     // 同上,但用了一个更难识别
     // 的C风格的类型转换
  
    Widget *pw = new SpecialWidget;
  
    update(pw); // 错误!pw的类型是Widget*,但是
     // update函数处理的是SpecialWidget*类型
  
    update(const_cast(pw));
     // 错误!const_cast仅能被用在影响
     // constness or volatileness的地方上。
     // 不能用在向继承子类进行类型转换。
  
    到目前为止,const_cast最普通的用途就是转换掉对象的const属性。
  
    第二种特殊的类型转换符是dynamic_cast,它被用于安全地沿着类的继承关系向下进行类型转换。这就是说,你能用dynamic_cast把指向基类的指针或引用转换成指向其派生类或其兄弟类的指针或引用,而且你能知道转换是否成功。失败的转换将返回空指针(当对指针进行类型转换时)或者抛出异常(当对引用进行类型转换时):
  
    Widget *pw;
    …
    update(dynamic_cast(pw));
     // 正确,传递给update函数一个指针
     // 是指向变量类型为SpecialWidget的pw的指针
     // 如果pw确实指向一个对象,
    // 否则传递过去的将使空指针。
    void updateViaRef(SpecialWidget& rsw);
    updateViaRef(dynamic_cast(*pw));
     //正确。 传递给updateViaRef函数
     // SpecialWidget pw 指针,如果pw
     // 确实指向了某个对象
    // 否则将抛出异常
  
    dynamic_casts在帮助你浏览继承层次上是有限制的。它不能被用于缺乏虚函数的类型上(参见条款24),也不能用它来转换掉constness:
  
    int firstNumber, secondNumber;
    …
    double result = dynamic_cast(firstNumber)/secondNumber;
     // 错误!没有继承关系
    const SpecialWidget sw;
    …
    update(dynamic_cast(&sw));
     // 错误! dynamic_cast不能转换
     // 掉const
  
    如你想在没有继承关系的类型中进行转换,你可能想到static_cast。如果是为了去除const,你总得用const_cast。
  
    这四个类型转换符中的最后一个是reinterpret_cast。这个操作符被用于的类型转换的转换结果几乎都是实现时定义(implementation-defined)。因此,使用reinterpret_casts的代码很难移植。
  
    reinterpret_casts的最普通的用途就是在函数指针类型之间进行转换。例如,假设你有一个函数指针数组:
  
    typedef void (*FuncPtr)(); // FuncPtr is 一个指向函数
     // 的指针,该函数没有参数
     // 也返回值类型为void
    FuncPtr funcPtrArray[10]; // funcPtrArray 是一个能容纳
     // 10个FuncPtrs指针的数组
  
    让我们假设你希望(因为某些莫名其妙的原因)把一个指向下面函数的指针存入funcPtrArray数组:
  
    int doSomething();
  
    你不能不经过类型转换而直接去做,因为doSomething函数对于funcPtrArray数组来说有一个错误的类型。在FuncPtrArray数组里的函数返回值是void类型,而doSomething函数返回值是int类型。
  
    funcPtrArray[0] = &doSomething; // 错误!类型不匹配
  
    reinterpret_cast
  
    可以让你迫使编译器以你的方法去看待它们:
  
    funcPtrArray[0] = // this compiles
    reinterpret_cast(&doSomething);
  
    转换函数指针的代码是不可移植的(C++不保证所有的函数指针都被用一样的方法表示),在一些情况下这样的转换会产生不正确的结果(参见条款31),所以你应该避免转换函数指针类型,除非你处于着背水一战和尖刀架喉的危急时刻。一把锋利的刀。一把非常锋利的刀。
  
    如果你使用的编译器缺乏对新的类型转换方式的支持,你可以用传统的类型转换方法代替static_cast, const_cast, and reinterpret_cast。也可以用下面的宏替换来模拟新的类型转换语法:
  
    #define static_cast(TYPE,EXPR) ((TYPE)(EXPR))
    #define const_cast(TYPE,EXPR) ((TYPE)(EXPR))
    #define reinterpret_cast(TYPE,EXPR) ((TYPE)(EXPR))
  
    你可以象这样使用使用:
  
    double result = static_cast(double, firstNumber)/secondNumber;
    update(const_cast(SpecialWidget*, &sw));
    funcPtrArray[0] = reinterpret_cast(FuncPtr, &doSomething);
  
    这些模拟不会象真实的操作符一样安全,但是当你的编译器可以支持新的的类型转换时它们可以简化你把代码升级的过程。
  
    没有一个容易的方法来模拟dynamic_cast的操作,但是很多函数库提供了函数,安全地在派生类与基类之间的进行类型转换。如果你没有这些函数而你有必须进行这样的类型转换,你也可以回到C风格的类型转换方法上,但是这样的话你将不能获知类型转换是否失败。当然,你也可以定义一个宏来模拟dynamic_cast的功能,就象模拟其它的类型转换一样:
  
    #define dynamic_cast(TYPE,EXPR) (TYPE)(EXPR)
  
    请记住,这个模拟并不能完全实现dynamic_cast的功能,它没有办法知道转换是否失败。
  
    我知道,是的,我知道,新的类型转换操作符不是很美观而且用键盘键入也很麻烦。如果你发现它们看上去实在令人讨厌,C风格的类型转换还可以继续使用并且合法。然而正是因为新的类型转换符缺乏美感才能使它弥补了在含义精确性和可辨认性上的缺点,并且使用新类型转换符的程序更容易被解析(不论是对人工还是对于工具程序),它们允许编译器检测出原来不能发现的错误。这些都是放弃C风格类型转换方法的强有力的理由,还有第三个理由:也许让类型转换符不美观和键入麻烦是一件好事。
  

分类:VC++ 相关 | 评论:0 | 浏览:310 | 举报 | 收藏 |

网友评论:我要评论
暂无评论!

发布评论:









指针与引用的区别

    指针与引用看上去完全不同(指针用操作符’*’和’->’,引用使用操作符’.’),但是它们似乎有相同的功能。指针与引用都是让你间接引用其他对象。你如何决定在什么时候使用指针,在什么时候使用引用呢?
  
    首先,要认识到在任何情况下都不能用指向空值的引用。一个引用必须总是指向某些对象。因此如果你使用一个变量并让它指向一个对象,但是该变量在某些时候也可能不指向任何对象,这时你应该把变量声明为指针,因为这样你可以赋空值给该变量。相反,如果变量肯定指向一个对象,例如你的设计不允许变量为空,这时你就可以把变量声明为引用。
  
    “但是,请等一下”,你怀疑地问,“这样的代码会产生什么样的后果?”
  
    char *pc = 0; // 设置指针为空值
  
    char& rc = *pc; // 让引用指向空值
  
    这是非常有害的,毫无疑问。结果将是不确定的(编译器能产生一些输出,导致任何事情都有可能发生),应该躲开写出这样代码的人除非他们同意改正错误。如果你担心这样的代码会出现在你的软件里,那么你最好完全避免使用引用,要不然就去让更优秀的程序员去做。我们以后将忽略一个引用指向空值的可能性。
  
    因为引用肯定会指向一个对象,在C里,引用应被初始化。
  
    string& rs; // 错误,引用必须被初始化
    string s(“xyzzy”);
    string& rs = s; // 正确,rs指向s
  
    指针没有这样的限制。
    string *ps; // 未初始化的指针
     // 合法但危险
  
    不存在指向空值的引用这个事实意味着使用引用的代码效率比使用指针的要高。因为在使用引用之前不需要测试它的合法性。
  
    void printDouble(const double& rd)
    {
     cout < rd;="">
    }       // 肯定指向一个double值
  
    相反,指针则应该总是被测试,防止其为空:
  
    void printDouble(const double *pd)
    {
     if (pd)
     {// 检查是否为NULL
      cout <>
     }
    }
  
    指针与引用的另一个重要的不同是指针可以被重新赋值以指向另一个不同的对象。但是引用则总是指向在初始化时被指定的对象,以后不能改变。
  
    string s1(“Nancy”);
    string s2(“Clancy”);
  
   string& rs = s1; // rs 引用 s1
    string *ps = &s1; // ps 指向 s1
    rs = s2; // rs 仍旧引用s1
     // 但是 s1的值现在是”Clancy”
  
   ps = &s2; // ps 现在指向 s2;// s1 没有改变
  
    总的来说,在以下情况下你应该使用指针,一是你考虑到存在不指向任何对象的可能(在这种情况下,你能够设置指针为空),二是你需要能够在不同的时刻指向不同的对象(在这种情况下,你能改变指针的指向)。如果总是指向一个对象并且一旦指向一个对象后就不会改变指向,那么你应该使用引用。
  
    还有一种情况,就是当你重载某个操作符时,你应该使用引用。最普通的例子是操作符[]。这个操作符典型的用法是返回一个目标对象,其能被赋值。
  
    vector v(10); //建立整形向量(vector),大小为10
     //向量是一个在标准C库中的一个模板(见条款35)
    v[5] = 10; // 这个被赋值的目标对象就是操作符[]返回的值
  
    如果操作符[]返回一个指针,那么后一个语句就得这样写:
  
    *v[5] = 10;
  
    但是这样会使得v看上去象是一个向量指针。因此你会选择让操作符返回一个引用。  
    当你知道你必须指向一个对象并且不想改变其指向时,或者在重载操作符并为防止不必要的语义误解时,你不应该使用指针。而在除此之外的其他情况下,则应使用指针 。
   
  

分类:VC++ 相关 | 评论:0 | 浏览:326 | 举报 | 收藏 |

上一篇:COM技术纵横谈
网友评论:我要评论
暂无评论!

发布评论:









windows的消息宏定义

    MFC的设计者们在设计MFC时,紧紧把握一个目标,那就是尽可能使得MFC的代码要小,速度尽可能快。为了这个目标,他们使用了许多技巧,其中很多技巧体现在宏的运用上,实现MFC的消息映射的机制就是其中之一。
  
    同MFC消息映射机制有关的宏有下面几个:
  
    DECLARE_MESSAGE_MAP()宏
  
    BEGIN_MESSAGE_MAP(theClass, baseClass)和END_MESSAGE_MAP()宏
  
    弄懂MFC消息映射机制的最好办法是将找出一个具体的实例,将这些宏展开,并找出相关的数据结构。
  
    DECLARE_MESSAGE_MAP()
  
    DECLARE_MESSAGE_MAP()宏的定义如下:
  
    #define DECLARE_MESSAGE_MAP()
  
    private:
  
    static const AFX_MSGMAP_ENTRY _messageEntries[];
  
    protected:
  
    static AFX_DATA const AFX_MSGMAP messageMap;
  
    virtual const AFX_MSGMAP* GetMessageMap() const;
  
  从上面的定义可以看出,DECLARE_MESSAGE_MAP()作下面三件事:
  
    定义一个长度不定的静态数组变量_messageEntries[];
  
    定义一个静态变量messageMap;
  
    定义一个虚拟函数GetMessageMap();
  
  在DECLARE_MESSAGE_MAP()宏中,涉及到MFC中两个对外不公开的数据结构
  
  AFX_MSGMAP_ENTRY和AFX_MSGMAP。为了弄清楚消息映射,有必要考察一下这两个数据结构的定义。
  
    AFX_MSGMAP_ENTRY的定义
  
    struct AFX_MSGMAP_ENTRY
  
    {
  
     UINT nMessage; // windows message
  
     UINT nCode; // control code or WM_NOTIFY code
  
     UINT nID; // control ID (or 0 for windows messages)
  
     UINT nLastID; // used for entries specifying a range of control id’s
  
     UINT nSig; // signature type (action) or pointer to message #
  
     AFX_PMSG pfn; // routine to call (or special value)
  
    };
  
  结构中各项的含义注释已经说明得很清楚了,这里不再多述,从上面的定义你是否看出,AFX_MSGMAP_ENTRY结构实际上定义了消息和处理此消息的动作之间的映射关系。因此静态数组变量_messageEntries[]实际上定义了一张表,表中的每一项指定了相应的对象所要处理的消息和处理此消息的函数的对应关系,因而这张表也称为消息映射表。再看看AFX_MSGMAP的定义。
  
    (2)AFX_MSGMAP的定义
  
    struct AFX_MSGMAP
  
    {
  
     const AFX_MSGMAP* pBaseMap;
  
     const AFX_MSGMAP_ENTRY* lpEntries;
  
     };
  
  不难看出,AFX_MSGMAP定义了一单向链表,链表中每一项的值是一指向消息映射表的指针(实际上就是_messageEntries的值)。通过这个链表,使得在某个类中调用基类的的消息处理函数很容易,因此,“父类的消息处理函数是子类的缺省消息处理函数”就“顺理成章”了。在后面的“MFC窗口的消息处理”一节中会对此作详细的讲解。
  
    BEGIN_MESSAGE_MAP()和END_MESSAGE_MAP()
  
    它们的定义如下:
  
    #define BEGIN_MESSAGE_MAP(theClass, baseClass)
  
    const AFX_MSGMAP* theClass::GetMessageMap() const
  
    { return &theClass::messageMap; }
  
    AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap =
  
    { &baseClass::messageMap, &theClass::_messageEntries[0] };
  
    AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] =
  
    {
  
     #define END_MESSAGE_MAP()
  
     {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 }
  
     };
  
  对应BEGIN_MESSAGE_MAP()的定义可能不是一下子就看得明白,不过不要紧,举一例子就很清楚了。对于BEGIN_MESSAGE_MAP(CView, CWnd),VC预编译器将其展开成下面的形式:
  
    const AFX_MSGMAP* CView::GetMessageMap() const
  
    {
  
     return &CView::messageMap;
  
     }
  
    AFX_COMDAT AFX_DATADEF const AFX_MSGMAP CView::messageMap =
  
    {
  
     &CWnd::messageMap,
  
     &CView::_messageEntries[0]
  
    };
  
    AFX_COMDAT const AFX_MSGMAP_ENTRY CView::_messageEntries[] =
  
    {
  
    至于END_MESSAGE_MAP()则不过定义了一个表示映射表结束的标志项,我想大家对于这种简单的技巧应该是很熟悉的,无需多述。
  到此为止,我想大家也已经想到了象ON_COMMAND这样的宏的具体作用了,不错它们只不过定义了一种类型的消息映射项,看看ON_COMMAND的定义:
  
    #define ON_COMMAND(id, memberFxn)
  
    { WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSig_vv, (AFX_PMSG)&memberFxn },
  
     根据上面的定义,ON_COMMAND(ID_FILE_NEW, OnFileNew)将被VC预编译器展开
  
     如下:
  
    {WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSig_vv,
  
    (AFX_PMSG)&OnFileNew},
  
  到此,MFC的消息映射机制已经清楚了,现在提出并解答两个问题以作为对这一节的小结。
  
    为什么不直接使用虚拟函数实现消息处理函数呢?这是一个GOOD QUESTION。前面已经说过,MFC的设计者们在设计MFC时有一个很明确的目标,就是使得“MFC的代码尽可能小,速度尽可能快”,如果采用虚拟函数,那么对于所有的窗口消息,都必须有一个与之对应的虚拟函数,因而对每一个从CWnd派生的类而言,都会有一张很大的虚拟函数表vtbl。但是在实际应用中,一般只对少数的消息进行处理,大部分都交给系统缺省处理,所以表中的大部分项都是无用项,这样做就浪费了很多内存资源,这同MFC设计者们的设计目标是相违背的。当然,MFC所使用的方法只是解决这类问题的方式之一,不排除还有其他的解决方式,但就我个人观点而言,这是一种最好的解决方式,体现了很高的技巧性,值得我们学习。
  
    至于这第二个问题,是由上面的问题引申出来的。如果在子类和父类中出现了相同的消息出来函数,VC编译器会怎么处理这个问题呢?VC不会将它们看作错误,而会象对待虚拟函数类似的方式去处理,但对于消息处理函数(带afx_msg前缀),则不会生成虚拟函数表vtbl。
  

c++cli

C++/CLI描绘的是一种多元组合,此处的 C++ 当然是指 Bjarne Stroustrup 在 Bell 实验室发明的C++编程语言。它支持速度和执行 文件的大小都得到优化的静态对象模型。但除了堆内存分配以外,它不支持运行时程序对对象的更改。它允许对底层机器进行无限制的访问,但对于正在运行的程序中的活动类型、以及相关的程序基础构造,它的访问能力却非常有限、或者根本就不可能。我在微软的同事 Herb Sutter,也是C++/CLI的主架构师,认为C++是一个混凝土语言。
“CLI”即公共语言基础结构(Common Language Infrastructure),这是一个支持动态组件编程模型的多层架构。在许多方面,它所表示的对象模型和C++的完全相反。它是一个运行时软件层 ,一个虚拟执行系统,运行在应用程序和底层操作系统之间。对底层机器的访问受到相当严格的限制。支持对运行中程序的活动类型以及关联程序的基础构造 进行存取——发现和建构。斜线“/”表示 C++ 和 CLI 之间的一种绑定(binding),有关这种绑定的细节构成本专栏的常规主题。
所以,对于“什么是C++/CLI?”这个问题第一个最近似的答案是:它是静态C++对象模型到动态CLI组件对象模型的一种绑定, 简言之,它就是你如何用C++进行.NET编程,而不是用C#或Visual Basic.NET。象C#和CLI自己一样,C++/CLI正在经历 ECMA(欧洲计算机制造商协会) 标准化并最终要经历ISO标准认证。
公共语言运行时(CLR)是微软版的CLI,专门用于 Windows 操作系统,同样,Visual C++ 2005是C++/CLI 的实现。
第二个近似答案是:我觉得C++/CLI在C++内集成.NET编程模型与以前贝尔实验室在当时的C++中用模板集成泛型编程一样有异曲同工之处。两种情况中,你在现有C++代码库上的投资以及你现有的C++专业技术都得到保护。这是C++/CLI设计的一个基本 要求。
学习C++/CLI
一种C++/CLI语言的设计有三个层面,这三个层面也适用于所有语言:语言层语法到公共类型系统(CTS) 的映射;选择为程序员直接操作而公开的底层CLI基本组织结构 的详细程度;以及选择要提供的超越CLI直接支持的附加功能。
第一个层面是所有CLI语言在很大程度上都共有的,第二个层面和第三层面是某一CLI语言区别于其它语言的地方。根据所要解决的问题,你可以选择某一 种语言,也可以将多种CLI语言结合起来。学习C++/CLI语言需要掌握这三个设计层面。
怎样将C++/CLI 映射到CTS?
了解底层CTS 对学习C++/CLI非常有帮助,它主要包括三个常规类类型:
多态引用类型,其用于所有的类继承;
非多态值类型,其用于实现需要运行时效率的具体类型,如数字类型;
抽象接口类型,其用于定义一个实现该接口的一组引用类型或值类型共同使用的公共操作集;
在设计方面,虽然CTS到一组内置的语言类型的映射对于所有CLI语言来说都是共同的,当然,每一种CLI语言的语法各不相同。例如,在C#中,我们可以 这样来定义一个抽象基类 Shape,从这个类派生特定的几何模型对象。
abstract class Shape { … } // C#
而在C++/CLI中,可以象下面这样写,以表示完全相同的底层引用类型:
ref class Shape abstract { … }; // C++/CLI
在底层 IL(中间语言)中,以上两种声明以完全相同的方式表示。同样,在C#中,我们可以用下面的代码来定义一个具体的 Point2D 类 :
struct Point2D { … } // C#
而在C++/CLI中写成:
value class Point2D { … }; // C++/CLI
借助 C++/CLI 支持的类类型家族表现了一种本机方式的 CTS 集成。它确定了你的语法选择,例如:
class native {};
value class V {};
ref class R {};
interface class I {};
CTS 也支持枚举类类型,其行为方式与本机枚举稍微有些区别,C++/CLI对二者都提供支持:
enum native { fail, pass };
enum class CLIEnum : char { fail, pass};
同样,CTS支持其自己的数组类型,其行为也与本机数组类型有一定差别,微软同样对二者提供支持:
int native[] = { 1,1,2,3,5,8 };
array<int>^ managed = { 1,1,2,3,5,8 };
那种认为任何一种 CLI 语言比另一种语言更接近或几乎就是到底层CLI的映射是不精确的。相反,每一种CLI语言都只是表达了自己对底层CLI对象模型的一种 见解。在下一节你将更清楚地看到这一点。
CLI 的细节标准
在设计CLI语言时必须考虑的第二个设计层面是要将什么程度的底层CLI实现模型结合到该语言中。这个语言解决什么样的问题?要解决这些问题必须要什么样的工具? 此外,该语言很可能吸引哪一类程序员?
下面,我们利用发生在托管堆中的值类型问题。在许多情况下,值类型可以在托管堆中找到自己:
通过隐式的框入/框出操作(boxing)——当值类型的某个实例被赋值给一对象时,或者通过某个未被改写的值类型调用一个虚拟方法时;
当值类型被当作为引用类类型的成员时;
当值类型被当作CLI数组元素存储时;
是否允许程序员处理这种值类型地址是设计CLI语言时必须要解决的问题。
存在的问题?
位于托管堆中的任何对象在垃圾回收器进行清扫收缩的过程中都有可能遭遇重新分配,指向这些对象的任何指针必须被追踪并在运行时得到更新,而程序员 无法自己手动追踪它们,因此,如果你被允许用某个可能在托管堆中的值类型的地址,那么除了本机指针外,还需要一个追踪形态的指针。
到底该怎样去权衡呢?一方面,需要考虑简洁和安全。直接引入对一个或一组追踪指针的支持会使语言变得更复杂。如果不提供这种支持,由于所需的复杂程度降低,从而可以找到的程序员人群就会增加。此外,允许程序员访问这些生命期短暂的值类型,则增加了程序员出错的可能性。她经意或不经意地对内存做一些危险动作。不支持追踪指针,可以潜在地创建较安全的运行时环境。
另一方面,必须考虑效率和灵活性。每次将值类型赋值给相同的对象,该值都会发生新的框入/框出操作。允许访问这种经过框入/框出操作的值类型 ,就允许在内存中进行更新操作,这样便可能提供重要的性能改进。没有某种形式的追踪指针,你将无法用指针算法遍历CLI数组,这意味着CLI数组将不能 融入STL(标准模板库)中的迭代器模式,也无法与泛型算法协同工作。允许访问框入/框出值类型将会大大提高设计的灵活性。
在C++/CLI 中,微软选择提供一系列在托管堆中处理值类型的寻址模式:
int ival = 1024;
int^ boxedi = ival;
array<int>^ ia = gcnew array<int>{1,1,2,3,5,8};
interior_ptr<int> begin = &ia[0];
value struct smallInt { int m_ival; … } si;
pin_ptr<int> ppi = &si.m_ival;
典型的 C++/CLI 开发人员是一个经验丰富的系统程序员,其任务是提供底层架构以及作为基础的核心应用,以此为基础来构建未来。她必须解决可伸缩性和性能相关的问题,并且必须从系统一级来看待底层 CLI。某种 CLI 语言的细节标准反映了其程序员的面貌。
复杂性本身并不是对质量的否定,人类生命比单核细胞复杂得多,这当然不是一件坏事,然而,当单一概念的表达变得复杂化以后,这常常被认为是一件坏事。在C++/CLI中,CLI开发团队已经 尝试提供一种优雅的方式来表达一个复杂的主体。
附加功能
第三个设计层面是特定语言层功能要超过被CLI直接支持的功能,这样就需要建立一种语言层支持与CLI底层实现模型之间的映射。 在某些情况下,这是做不到的,因为该语言无法调解CLI的行为,在基类的构造函数和析构函数中解决虚函数便是例子。为了在这种情况中反映ISO-C++语义,需要在每个基类的构造函数和析构函数中 重新安排虚表。这是不可能的,因为虚表操作是由运行时托管的,而非单独的语言托管。 因此,这一设计层面是优越性和可行性的折中。C++/CLI 提供的附加功能主要有三个方面:
引用类型的资源获取(Resource Acquisition)形式是Initialization(RAII), 尤其是为被称作占据稀有资源的垃圾回收类型确定性终止化(deterministic finalization)提供一个自动化的机制;
与C++拷贝构造函数和拷贝赋值操作符相关的深度拷贝语义形式,但它不能扩展到值类型;
除了 CLI泛型机制之外——这原来是我第一个专栏的主题,还为CTS类型提供C++模板的直接支持,另外,还提供用于 CLI 类型的 STL 可验证版本;
让我们看一个简单的例子:确定性终止化问题。与对象关联的内存被垃圾回收器回收之前,若存在与之相关连的 Finalize 方法,该方法将会被调用。你可以把 该方法看作是一种超级析构函数,因为它不依赖于该对象程序的生命期,它被称为终止化。调用 Finalize 方法的时间,甚至是否调用它是未定义的。这就是垃圾回收器不确定的终止化操作含义之所在。
不确定性终止化在进行动态内存管理时可以很有效地工作,当可用内存空间严重不足时,垃圾回收器会发挥作用并解决问题。但是当对象涉及的是某些重要资源,比如数据库连接、某种 类型的锁、本地堆内存时,

VC++ADO开发实践1

分类: 开发语言:VC/MFC/C++
作者: 王骏
出处: http://www.pconline.com.cn
加入时间: 2002-2-28 9:51:59

一、ADO简介

ADO(ActiveX Data Object)是Microsoft数据库应用程序开发的新接口,是建立在OLE DB之上的高层数据库访问技术,请不必为此担心,即使你对OLE DB,COM不了解也能轻松对付ADO,因为它非常简单易用,甚至比你以往所接触的ODBC API、DAO、RDO都要容易使用,并不失灵活性。本文将详细地介绍在VC下如何使用ADO来进行数据库应用程序开发,并给出示例代码。

本文示例代码

二、基本流程

万事开头难,任何一种新技术对于初学者来说最重要的还是“入门”,掌握其要点。让我们来看看ADO数据库开发的基本流程吧!

(1)初始化COM库,引入ADO库定义文件
(2)用Connection对象连接数据库
(3)利用建立好的连接,通过Connection、Command对象执行SQL命令,或利用Recordset对象取得结果记录集进行查询、处理。
(4)使用完毕后关闭连接释放对象。

准备工作:
为了大家都能测试本文提供的例子,我们采用Access数据库,您也可以直接在我们提供的示例代码中找到这个test.mdb。
下面我们将详细介绍上述步骤并给出相关代码。

【1】COM库的初始化
我们可以使用AfxOleInit()来初始化COM库,这项工作通常在CWinApp::InitInstance()的重载函数中完成,请看如下代码:

BOOL CADOTest1App::InitInstance() { AfxOleInit(); ……

【2】用#import指令引入ADO类型库
我们在stdafx.h中加入如下语句:(stdafx.h这个文件哪里可以找到?你可以在FileView中的Header Files里找到)

#import “c:program filescommon filessystemadomsado15.dll” no_namespace rename(“EOF”,”adoEOF”)
这一语句有何作用呢?其最终作用同我们熟悉的#include类似,编译的时候系统会为我们生成msado15.tlh,ado15.tli两个C++头文件来定义ADO库。

几点说明:
(1) 您的环境中msado15.dll不一定在这个目录下,请按实际情况修改
(2) 在编译的时候肯能会出现如下警告,对此微软在MSDN中作了说明,并建议我们不要理会这个警告。
msado15.tlh(405) : warning C4146: unary minus operator applied to unsigned type, result still unsigned

【3】创建Connection对象并连接数据库
首先我们需要添加一个指向Connection对象的指针:
_ConnectionPtr m_pConnection;
下面的代码演示了如何创建Connection对象实例及如何连接数据库并进行异常捕捉。

BOOL CADOTest1Dlg::OnInitDialog() {
CDialog::OnInitDialog();
HRESULT hr;
try {
hr = m_pConnection.CreateInstance(“ADODB.Connection”);///创建Connection对象

if(SUCCEEDED(hr))
{
hr = m_pConnection->Open(“Provider=Microsoft.Jet.OLEDB.4.0;Data Source=test.mdb”,””,””,adModeUnknown);
///连接数据库 ///上面一句中连接字串中的Provider是针对ACCESS2000环境的,对于ACCESS97,需要改为:Provider=Microsoft.Jet.OLEDB.3.51;
}
}
catch(_com_error e)///捕捉异常
{
CString errormessage; errormessage.Format(“连接数据库失败!rn错误信息:%s”,e.ErrorMessage()); AfxMessageBox(errormessage);///显示错误信息 }

在这段代码中我们是通过Connection对象的Open方法来进行连接数据库的,下面是该方法的原型
HRESULT Connection15::
Open
(_bstr_t ConnectionString, _bstr_t UserID, _bstr_t Password, long Options )
ConnectionString为连接字串,UserID是用户名, Password是登陆密码,Options是连接选项,用于指定Connection对象对数据的更新许可权,

Options可以是如下几个常量:
adModeUnknown:缺省。当前的许可权未设置
adModeRead:只读
adModeWrite:只写
adModeReadWrite:可以读写
adModeShareDenyRead:阻止其它Connection对象以读权限打开连接
adModeShareDenyWrite:阻止其它Connection对象以写权限打开连接
adModeShareExclusive:阻止其它Connection对象打开连接
adModeShareDenyNone:允许其它程序或对象以任何权限建立连接

我们给出一些常用的连接方式供大家参考:
(1)通过JET数据库引擎对ACCESS2000数据库的连接

m_pConnection->Open(“Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:test.mdb”,””,””,adModeUnknown);

(2)通过DSN数据源对任何支持ODBC的数据库进行连接:

m_pConnection->Open(“Data Source=adotest;UID=sa;PWD=;”,””,””,adModeUnknown);

(3)不通过DSN对SQL SERVER数据库进行连接:

m_pConnection->
Open(
“driver={SQLServer};Server=127.0.0.1;DATABASE=vckbase;UID=sa;PWD=139″,””,””,adModeUnknown
);

其中Server是SQL服务器的名称,DATABASE是库的名称

Connection对象除Open方法外还有许多方法,我们先介绍Connection对象中两个有用的属性ConnectionTimeOut与State
ConnectionTimeOut用来设置连接的超时时间,需要在Open之前调用,例如:

m_pConnection->ConnectionTimeout = 5;///设置超时时间为5秒

m_pConnection->Open(“Data Source=adotest;”,””,””,adModeUnknown);

State属性指明当前Connection对象的状态,0表示关闭,1表示已经打开,我们可以通过读取这个属性来作相应的处理,例如:

if(m_pConnection->State) m_pConnection->Close(); ///如果已经打开了连接则关闭它

【4】执行SQL命令并取得结果记录集
为了取得结果记录集,我们定义一个指向Recordset对象的指针:_RecordsetPtr m_pRecordset;
并为其创建Recordset对象的实例: m_pRecordset.CreateInstance(“ADODB.Recordset”);
SQL命令的执行可以采用多种形式,下面我们一进行阐述。

(1)利用Connection对象的Execute方法执行SQL命令

Execute方法的原型如下所示:

_RecordsetPtr Connection15::
Execute ( _bstr_t CommandText, VARIANT * RecordsAffected, long Options )
其中CommandText是命令字串,通常是SQL命令。参数RecordsAffected是操作完成后所影响的行数, 参数Options表示CommandText中内容的类型,Options可以取如下值之一:

adCmdText:表明CommandText是文本命令
adCmdTable:表明CommandText是一个表名
adCmdProc:表明CommandText是一个存储过程
adCmdUnknown:未知

Execute执行完后返回一个指向记录集的指针,下面我们给出具体代码并作说明。

_variant_t RecordsAffected;
///执行SQL命令:CREATE TABLE创建表格users,users包含四个字段:整形ID,字符串username,整形old,日期型birthday

m_pConnection->

Execute(“CREATE TABLE users(ID INTEGER,username TEXT,old INTEGER,birthday DATETIME)”,&RecordsAffected,adCmdText);
///往表格里面添加记录

m_pConnection->

Execute(“INSERT INTO users(ID,username,old,birthday) VALUES (1, ‘Washington’,25,’1970/1/1′)”,&RecordsAffected,adCmdText);
///将所有记录old字段的值加一

m_pConnection->

Execute(“UPDATE users SET old = old+1”,&RecordsAffected,adCmdText);
///执行SQL统计命令得到包含记录条数的记录集

m_pRecordset = m_pConnection->

Execute(“SELECT COUNT(*) FROM users”,&RecordsAffected,adCmdText);
_variant_t vIndex = (long)0;
_variant_t vCount = _pRecordset->

GetCollect(vIndex);

///取得第一个字段的值放入vCount变量

m_pRecordset->Close();
///关闭记录集

CString message;
message.Format(“共有%d条记录”,vCount.lVal);
AfxMessageBox(message);
///显示当前记录条数

(2)利用Command对象来执行SQL命令

_CommandPtr m_pCommand;
m_pCommand.CreateInstance(“ADODB.Command”);
_variant_t vNULL;
vNULL.vt = VT_ERROR;
vNULL.scode = DISP_E_PARAMNOTFOUND;
///定义为无参数

m_pCommand->ActiveConnection = m_pConnection;
///非常关键的一句,将建立的连接赋值给它

m_pCommand->CommandText = “SELECT * FROM users”;
///命令字串

m_pRecordset = m_pCommand->Execute(&vNULL,&vNULL,adCmdText);
///执行命令,取得记录集

在这段代码中我们只是用Command对象来执行了SELECT查询语句,Command对象在进行存储过程的调用中能真正体现它的作用。下次我们将详细介绍。

VC++ADO开发实践2

  分类: 开发语言:VC/MFC/C++
  作者: 王骏
  出处: http://www.pconline.com.cn
  加入时间: 2002-2-28 9:54:00
  
  一、前言
  
  在上一篇文章《ADO第一次亲密接触》中我们详细介绍了ADO基本的操作方法,在实际的开发过程中我们常常需要存储较大的二进制数据对象,比如:图像、音频文件、或其它二进制数据,这些数据我们称之为二进制大对象BLOB(Binary Large Object),其存取的方式与普通数据有所区别。本文将介绍利用ADO在数据库中存取BLOB数据的具体实现过程,并给出实现图像存取显示的完整示例工程。
  
  二、前期准备
  
  首先我们建立一张名为userinfo的表,包含三个字段:id,username,old,photo,其中photo是一个可以存储二进制数据的字段。
  
  2.1 在SQL SERVER中我们可以在Query Analyzer中直接输入如下语句创建:
  
  CREATE TABLE [dbo].[userphoto] (
  [id] [int] IDENTITY (1, 1) NOT NULL ,
  [username] [varchar] (50) NULL ,
  [old] [int] NULL ,
  [photo] [image] NULL
  ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
  其中photo我们定义为image类型的字段。
  
  2.2 在ACCESS中创建的方法如下:
  
  建立一张新表包括id,username,old,photo四个字段,然后打开表,选视图菜单中设计视图,将id设置为自动编号的递增长整型,username为文本,old为数字,photo为OLE对象。
  在我们的示例工程中已经包含了一个建立好的ACCESS2000的库,你可以直接拿来使用。
  
  三、具体步骤
  
  3.1 BLOB数据的保存
  
  BLOB类型的数据无法用普通的方式进行存储,我们需要使用AppendChunk函数,AppendChunk包含在Field对象中,原型如下:
  HRESULT AppendChunk (const _variant_t & Data );
  从函数原型中可以看到关键的问题是我们需把二进制数据赋值给VARIANT类型的变量,下面我们给出具体的代码并作简单的分析:
  
  ///假设m_pBMPBuffer指针指向一块长度为m_nFileLen的二进制数据,并且已经成功打开了记录集对象m_pRecordset///
  
  char *pBuf = m_pBMPBuffer ;
  
  VARIANT varBLOB;
  
  SAFEARRAY *psa;
  
  SAFEARRAYBOUND rgsabound[1];
  
  m_pRecordset->AddNew(); ///添加新记录
  
  m_pRecordset->PutCollect(“username”,_variant_t(“小李”)); ///为新记录填充username字段
  
  m_pRecordset->PutCollect(“old”,_variant_t((long)28); ///填充old字段
  
  if(pBuf){
  
  rgsabound[0].lLbound = 0;
  
  rgsabound[0].cElements = m_nFileLen;
  
  psa = SafeArrayCreate(VT_UI1, 1, rgsabound); ///创建SAFEARRAY对象
  
  for (long i = 0; i < (long)m_nfilelen;="" i++)="">
  
  SafeArrayPutElement (psa, &i, pBuf++); ///将pBuf指向的二进制数据保存到SAFEARRAY对象psa中
  
  varBLOB.vt = VT_ARRAY | VT_UI1;///将varBLOB的类型设置为BYTE类型的数组
  
  varBLOB.parray = psa; ///为varBLOB变量赋值 m_pRecordset->GetFields()->GetItem(“photo”)->AppendChunk(varBLOB); ///加入BLOB类型的数据
  
  }
  
  m_pRecordset->Update(); ///保存我们的数据到库中
  
  至此我们的数据已经成功地保存到了数据库中,接下来我们所要做的工作便是将该数据提取出来,让我们继续吧!
  
  3.2 BLOB数据的读取
  
  对应于保存数据时我们所使用的AppendChunk函数,读取数据应该使用GetChunk函数,GetChunk的原型如下:
  _variant_t GetChunk (long Length );
  给出数据的长度后GetChunk将返回包含数据的VARIANT类型变量,然后我们可以利用SafeArrayAccessData函数得到VARIANT变量中指向数据的char *类型的指针,以方便我们的处理,具体代码如下:
  
  long lDataSize = m_pRecordset->GetFields()->GetItem(“photo”)->ActualSize;
  ///得到数据的长度
  
  if(lDataSize > 0)
  
  {
  
  _variant_t varBLOB;
  
  varBLOB = m_pRecordset->GetFields()->GetItem(“photo”)->GetChunk(lDataSize);
  
    if(varBLOB.vt == (VT_ARRAY | VT_UI1)) ///判断数据类型是否正确
  
    {
  
    char *pBuf = NULL;
  
    SafeArrayAccessData(varBLOB.parray,(void **)&pBuf);
    ///得到指向数据的指针 /*****在这里我们可以对pBuf中的数据进行处理*****/
  
    SafeArrayUnaccessData (varBLOB.parray);
  
    }
  
  }
  
  以上我们成功实现了BLOB数据在数据库中的存取,为了让大家有现成的例子可以参考,本文提供了示例工程,在示例工程中我们在数据库中保存图像数据,并可以对这些图像进行浏览、修改,该例子还涉及到如何用char *指向的BMP文件数据创建BITMAP对象然后显示出来。
  
  点这里下载示例工程
  
  四、后记
  
  在上一篇文章《ADO第一次亲密接触》发表后,得到了广大网友的大力支持和鼓励,还指正了文章中的错误之处,这些都促使我继续写下了这篇文章,在此向这些网友表示衷心的感谢,对于本文的错误之处也希望批评指正!
  
  
  

VC++ADO开发实践3

  在Visual C++中如何利用UDL文件来建立ADO连接
  
   使用通用数据连接文件(*.UDL,以下简称文件)来创建ADO连接,可以和ODBC一样可视化地定义要连接的数据源,从而实现数据访问的透明性。
  
  
  1.使用UDL文件来创建ADO连接
  创建ADO的连接,首先要设置ADO连接对象的ConnectionString属性,该属性提供所要连接的数据库类型、数据所处服务器、要访问的数据库和数据库访问的安全认证信息。比较专业的方法是在ConnectionString中直接提供以上信息,下面是访问不同类型数据源设置ConnectionString的标准:
  访问ODBC数据
  ”Provider=MSDASQL;DSN=dsnName;UID=userName;PWD=userPassword;”
  访问ORACLE数据库
  ”Provider=MSDAORA;Data Source=serverName;User ID=userName; Password=userPassword;”
  访问MS SQL数据库
  ”Provider=SQLOLEDB;Data Source=serverName;Initial Catalog=databaseName; User ID=userName;Password=userPassword;”
  访问ACCESS 数据库
  ”Provider=Microsoft.Jet.OLEDB.4.0;Data Source=databaseName;User ID=userName;Password=userPassword;”
  上述的连接属性设置标准随着数据源的类型不同而变化,软件用户常常不习惯这种设置方式,都希望有可视化的数据源设置方法。为此Microsoft提供了通用数据连接文件(.UDL)来建立和测试ADO连接属性。ADO连接对象可以很方便地使用UDL文件来连接数据源,下面例子使用my_data1.udl来创建ADO连接。
  _ConnectionPtr m_pDBConn;
  m_pDBConn.CreateInstance(__uuidof(Connection));
  m_pDBConn->ConnectionString =”File Name=c:mydirmy_data1.udl”;
  m_pDBConn->Open(“”,””,””,NULL);
  这样一来无论数据源如何变化,在软件中都可以用统一的方法编程。当数据源改变时,只要双击相应的udl文件即可可视化地设置数据源,无需更改软件。
  因为ADO是COM接口,为了软件的可靠性,打开ADO连接时,可以加入异常处理代码。
  try{
  m_pDBConn->Open(“”,””,””,NULL);
  }catch(_com_error &e){
  //处理异常的代码
  . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
  m_pDBConn=NULL;
  }
  因为_ConnectionPtr m_pDBConn是智能指针,应在处理异常代码时将智能指针设为NULL后将自动将引用计数降为0。
  如果不出现异常,只要在使用完m_pDBConn,只要引用Close方法即可。
  2.创建你所需的UDL文件
  在你所想创建UDL文件的目录中单击右键,选择从菜单 新建|Microsoft 数据连接,然后将新创建的UDL文件更改为你所希望的文件名(.UDL扩展名不能改变)。
  注:如果操作系统是Window 2000,先创建一个文本文件,再将该文本文件的扩展名改为 “udl”。
  然后双击所创建的UDL文件,即可视化地完成数据源的设定。
  使用UDL文件必须在系统中先安装Microsoft MDAC,Win 98第二版,Win 2000中都自动包含了该组件,需要该组件最新版本时可以到Microsoft网站去下载。
  
  
  

VC++ADO开发实践4

  在VC中使用ADO开发数据库应用程序
  
  
  1.引入ADO库文件
  使用ADO前必须在工程的stdafx.h文件里用直接引入符号#import引入ADO库文件,
  以使编译器能正确编译。代码如下所示:
  #import “c:program filescommon filessystemadomsado15.dll”
  no—namespaces rename(“EOF” adoEOF”)
  这行语句声明在工程中使用ADO,但不使用ADO的名字空间,并且为了避免冲突,将EOF改
  名为adoEOF。
  
  2.初始化OLE/COM库环境
  必须注意的是,ADO库是一组COM动态库,这意味应用程序在调用ADO前,必须初始
  化OLE/COM库环境。在MFC应用程序里,一个比较好的方法是在应用程序主类的
  InitInstance成员函数里初始化OLE/COM库环境。
  
    //初始化OLE/COM库环境
  
    BOOL CADOApp::InitInstance()
    {
  if(!AfxOleInit())
  {
  AfxMessageBox(“OLE初始化出错!”);
  return FALSE;
  }
  ……
  }
  
  函数AfxOleInit在每次应用程序启动时初始化OLE/COM库环境。
  
  3.ADO接口简介
  ADO库包含三个基本接口:
  __ConnectionPtr接口、
  __CommandPtr接口和、
  __RecordsetPtr接口,
  
  __ConnectionPtr接口返回一个记录集或一个空指针。通常使用它来创建一个数据连接或
  执行一条不返回任何结果的SQL语句,如一个存储过程。用__ConnectionPtr接口返回一
  个记录集不是一个好的使用方法。通常同CDatabase一样,使用它创建一个数据连接,然
  后使用其它对象执行数据输入输出操作。
  
  __CommandPtr接口返回一个记录集。它提供了一种简单的方法来执行返回记录集的存储
  过程和SQL语句。在使用__CommandPtr接口时,可以利用全局__ConnectionPtr接口,也
  可以在__CommandPtr接口里直接使用连接串。如果只执行一次或几次数据访问操作,后
  者是比较好的选择。但如果要频繁访问数据库,并要返回很多记录集,那么,应该使用
  全局__ConnectionPtr接口创建一个数据连接,然后使用__CommandPtr接口执行存储过程
  和SQL语句。
  
  __RecordsetPtr是一个记录集对象。与以上两种对象相比,它对记录集提供了更多
  的控制功能,如记录锁定,游标控制等。同__CommandPtr接口一样,它不一定要使用一
  个已经创建的数据连接,可以用一个连接串代替连接指针赋给__RecordsetPtr的
  connection成员变量,让它自己创建数据连接。如果要使用多个记录集,最好的方法是
  同Command对象一样使用已经创建了数据连接的全局—ConnectionPtr接口,然后使用
  __RecordsetPtr执行存储过程和SQL语句。
  
  4.使用__ConnectionPtr接口
  
  __ConnectionPtr是一个连接接口,它类似于CDatabase和CDaoDatabase。首先创建一个
  __ConnectionPtr接口实例,接着指向并打开一个ODBC数据源或OLE DB数据提供者
  (Provider)。以下代码分别创建一个基于DSN和非DSN的数据连接。
  
    //使用__ConnectionPtr(基于DSN)
  
    __ConnectionPtr MyDb;
  
    MyDb.CreateInstance(__uuidof(Connection));
  
    MyDb-〉Open(“DSN=samp;UID=admin;PWD=admin”,””,””,-1);
  
    //使用—ConnectionPtr (基于非DSN)
  
    __ConnectionPtr MyDb;
  
    MyDb.CreateInstance(__uuidof(Connection));
  
    MyDb-〉Open(“Provider=SQLOLEDB;SERVER=server;DATABASE=samp;UID=admin;
  
    PWD=admin”,””,””,-1);
  
  
  5.使用__RecordsetPtr接口
  __RecordsetPtr接口的使用方法和CDaoDatabase类似,通过以下代码的比较,你会发现
  使用—RecordsetPtr接口非常简单(以下代码使用上面已经创建的数据连接):
  
    //使用CDaoDatabase执行SQL语句
  
    CDaoRecordset MySet = new CDaoRecordset(MyDb);
  
    MySet-〉Open(AFX__DAO__USE__DEFAULT__TYPE,”SELECT  FROM t__samp”);
  
    Now using ADO:
  
    //使用__RecordsetPtr执行SQL语句
  
    __RecordsetPtr MySet;
  
    MySet.CreateInstance(__uuidof(Recordset));
  
    MySet-〉Open(“SELECT  FROM some__table”,
  
    MyDb.GetInterfacePtr(),adOpenDynamic,adLockOptimistic,adCmdText);
  
    现在我们已经有了一个数据连接和一个记录集,接下来就可以使用数据了。从以下
  代码可以看到,使用ADO的__RecordsetPtr接口,就不需要像DAO那样频繁地使用大而复
  杂的数据结构VARIANT,并强制转换各种数据类型了,这也是ADO的优点之一。假定程序
  有一个名称为m__List的ListBox控件,下面代码我们用__RecordsetPtr接口获取记录集
  数据并填充这个ListBox控件:
  
    //使用ADO访问数据
  
    __variant__t Holder
  
    try{while(!MySet-〉adoEOF)
  
    { Holder = MySet-〉GetCollect(“FIELD__1”);
  
    if(Holder.vt!=VT__NULL)
  
    m__List.AddString((char)__bstr__t(Holder));
  
    MySet-〉MoveNext();} }
  
    catch(__com__error  e)
  
    { CString Error = e-〉ErrorMessage();
  
     AfxMessageBox(e-〉ErrorMessage());
  
    } catch(…)
  
    { MessageBox(“ADO发生错误!”);}
  
    必须始终在代码中用try和catch来捕获ADO错误,否则ADO错误会使你的应用程序崩
  溃。当ADO发生运行错误时(如数据库不存在),OLE DB数据提供者将自动创建一个
  __com__error对象,并将有关错误信息填充到这个对象的成员变量。
  
  6.使用__CommandPtr接口
  __CommandPtr接口返回一个Recordset对象,并且提供了更多的记录集控制功能,以下代
  码示例使用__CommandPtr接口的方法:
  
    //使用__CommandPtr接口获取数据
  
    __CommandPtr pCommand;
  
    __RecordsetPtr MySet;
  
    pCommand.CreateInstance(__uuidof(Command));
  
    pCommand-〉ActiveConnection=MyDb;
  
    pCommand-〉CommandText=”select  from some—table”;
  
    pCommand-〉CommandType=adCmdText;
  
    pCommand-〉Parameters-〉“refresh”();
  
    MySet=pCommand-〉Execute(NULL,NULL,adCmdUnknown);
  
    __variant__t TheValue = MySet-〉GetCollect(“FIELD__1”);
  
    CString sValue=(char)__bstr__t(TheValue);
  
  7.关于数据类型转换
    由于COM对象是跨平台的,它使用了一种通用的方法来处理各种类型的数据,因此
  CString 类和COM对象是不兼容的,我们需要一组API来转换COM对象和C++类型的数
  据。__vatiant__t和__bstr__t就是这样两种对象。它们提供了通用的方法转换
COM对象
  和C++类型的数据。
  
  
  

VC++ADO开发实践6

  在Visual C++中用ADO进行数据库编程
  作者:蒋东宇
  
  
  
    ActiveX数据对象(ADO)是OLE DB上面的高层数据库API。我们在C++程序中也可以调用ADO。本文将在VC 6.0环境下做一个小小的例子解释如何使用ADO。
  
    1. 生成应用程序框架并初始化OLE/COM库环境
  
    创建一个标准的MFC AppWizard(exe)应用程序,然后在应用程序类的InitInstance函数中初始化OLE/COM库(因为ADO库是一个COM DLL库)。
  
    BOOL CADOTestApp::InitInstance()
  
    { //初始化OLE/COM库环境
  
  AfxOleInit();}
  
    2. 引入ADO库文件
  
    使用ADO前必须在工程的stdafx.h文件里用直接引入符号#import引入ADO库文件,以使编译器能正确编译。代码如下:
  
     #include 〈comdef.h〉
  
     #import “c:program filescommon filessystemadomsado15.dll”
  
     no_namespace
  
     rename (“EOF”,”adoEOF”)
  
    头文件comdef.h使我们的应用程序能够使用Visual C++中的一些特殊COM支持类,这些类使得处理OLE自治更为容易一些,OLE自治是ADO使用的数据类型。后三行使用#import指令在我们的应用程序中输入ADO类库定义。
  
    ADO类的定义是作为一种资源存储在ADO DLL(msado15.dll)中,在其内部称为类型库。类型库描述了自治接口,以及C++使用的COM vtable接口。当使用#import指令时,在运行时Visual C++需要从ADO DLL中读取这个类型库,并以此创建一组C++头文件。这些头文件具有.tli 和.tlh扩展名,读者可以在项目的目录下找到这两个文件。在C++程序代码中调用的ADO类要在这些文件中定义。
  
    程序的第三行指示ADO对象不使用名称空间。在有些应用程序中,由于应用程序中的对象与ADO中的对象之间可能会出现命名冲突,所以有必要使用名称空间。如果要使用名称空间,则可把第三行程序修改为: rename_namespace(“AdoNS”)。第四行代码将ADO中的EOF(文件结束)更名为adoEOF,以避免与定义了自己的EOF的其他库冲突。
  
    3.利用智能指针进行数据库操作
  
    在CaboutDlg头文件中定义两个ADO智能指针类实例,并在对话框中加入一个ListCtrl。
  
  _ConnectionPtr m_pConnection;
  
  _RecordsetPtr m_pRecordset;
  
    ClistCtrl m_List;
  
    ADO库包含三个智能指针:_ConnectionPtr、_CommandPtr和_RecordsetPtr。
  
  _ConnectionPtr通常被用来创建一个数据连接或执行一条不返回任何结果的SQL语句,如一个存储过程。
  
  _CommandPtr返回一个记录集。它提供了一种简单的方法来执行返回记录集的存储过程和SQL语句。在使用_CommandPtr接口时,可以利用全局_ConnectionPtr接口,也可以在_CommandPtr接口里直接使用连接串。
  
  _RecordsetPtr是一个记录集对象。与以上两种对象相比,它对记录集提供了更多的控制功能,如记录锁定、游标控制等。
  
    在OnInitDialog()中加入以下代码:
  
    BOOL CAboutDlg::OnInitDialog()
  
    {
  
  CDialog::OnInitDialog();
  
  _variant_t TheValue;
  
  m_List.ResetContent();
  
    m_pConnection.CreateInstance(_uuidof(Connection));
  
    m_pRecordset.CreateInstance(_uuidof(Recordset));
  
    try{
  
  m_pConnection->Open(“DSN=ADOTest”,””,””,0); //连接叫作ADOTest的ODBC数据源
  
  m_pRecordset->Open(“SELECT * FROM BlockDefine”,(IDispatch*)m_pConnection,
  adOpenDynamic,
  adLockOptimistic,
  adCmdText);
  
    //执行SQL语句得到一个记录集
  
     while(!m_pRecordset->adoEOF)
  
    //遍历所有记录
  
     {
  
     TheValue = m_pRecordset->GetCollect(“BlockIndex”);
  
    //得到字段BlockIndex的值
  
     if(TheValue.vt!=VT_NULL)
  
     m_List.AddString((char*)_bstr_t(TheValue)); //将该值加入到列表控件中
  
     m_pRecordset->MoveNext();
  
    }
  
     m_pRecordset->Close();
  
  m_pConnection->Close();
  
     }
  
     catch(_com_error e) //异常处理
  
     {
  
  AfxMessageBox(e->ErrorMessage());
  
    }
  
    m_pRecordset = NULL;
  
  m_pConnection = NULL;
  
    return TRUE; // return TRUE unless you set the focus to a control
  
    }
  
    程序中通过_variant_t和_bstr_t转换COM对象和C++类型的数据, _variant_t类封装了OLE自治VARIANT数据类型。在C++中使用_variant_t类要比直接使用VARIANT数据类型容易得多。
  
    好,编译后该程序就能运行了,但记住运行前要创建一个叫ADOTest的ODBC数据源。该程序将把表BlockDefine中的BlockIndex字段值显示在列表控件中。