taocoding


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

返回值为引用的一点测试

发表于 2009-08-25 | 分类于 C++

最近做多人视频时设计了一个简单的类,但是简单的类也很能说明问题,也再次说明有些基础知识还是理解得不够深刻,刚写了个测试程序以便日后查阅,主要是关于返回值为引用类型时的一点测试:

#include <iostream>
using namespace std;

class CConstRef
{
public:
CConstRef(int iA):m_iA(iA)
{
}
const int& GetA()const
{
return m_iA;
}
int& GetA()
{
return m_iA;
}
void PrintAddress()const
{
cout<<"Address of m_iA = "<<&m_iA<<endl;
}
private:
int m_iA;
};

int main(int argc, char* argv[])
{
//常量对象
const CConstRef cCA(8);
//非常量对象
CConstRef CA(9);
//////////////////////////////////////////////////////////////////////////
//注意下面的调用都是返回对象cCA的成员m_iA的引用,且此引用都是用来初始化另一引用
//因此地址都相同
//调用int& GetA()
int& ra = CA.GetA();
CA.PrintAddress();
cout<<"Address of ra = "<<&ra<<endl;
ra = 3;
cout<<"Now the CA.m_iA = "<<CA.GetA()<<endl;

//调用int& GetA()
const int& ra1 = CA.GetA();
cout<<"Address of ra1 = "<<&ra1<<endl;
//////////////////////////////////////////////////////////////////////////
//调用int& GetA()
int va = CA.GetA();
CA.PrintAddress();
cout<<"Address of va = "<<&va<<endl;
va = 3;
cout<<"Now the CA.m_iA = "<<CA.GetA()<<endl;

//调用int& GetA()
const int va1 = CA.GetA();
cout<<"Address of va1 = "<<&va1<<endl;
//////////////////////////////////////////////////////////////////////////
//下面的rb就是cCA.m_iA
const int& rb = cCA.GetA();

cCA.PrintAddress();
cout<<"Address of rb = "<<&rb<<endl;

/*
error: invalid initialization of reference of type 'int&' from expression of type 'const int'
上面的错误时G++提示的,这么说来其实编译器给我们返回的是const int而不是const int&
*/
// int& rb1 = cCA.GetA();

//下面的的vb和vb1都是是cCA.m_iA的一个副本
const int vb = cCA.GetA();
cout<<"Address of vb = "<<&vb<<endl;

int vb1 = cCA.GetA();
cout<<"Address of vb1 = "<<&vb1<<endl;
vb1 = 10;

cout<<"Now the cCA.m_iA = "<<cCA.GetA()<<endl;

return 0;
}

下面是在MinGW的G++下输出的结果

返回引用

 

 

路路漫漫其修远兮,吾将上下而求索!

C++设计模式之一 工厂模式(简单工厂、工厂和抽象工厂)

发表于 2009-07-21 | 分类于 设计模式

原文地址:http://www.cnblogs.com/Seasky/archive/2009/02/06/1385609.html

实例代码下载 
结构类图下载 

       今天开始这个系列之前,心里有些恐慌,毕竟园子里的高手关于设计模式的经典文章很多很多,特别是大侠李会军、吕震宇 老师的文章更是堪称经典。他们的文笔如行云流水,例子活泼生动,讲解深入浅出。好在他们都是用C#描述,也没有提供必要的源码下载,所以我这里用C++实现。首先我想声明的是我的文笔绝对不如他们的好,例子也没有他们的形象,不过我打算把C++的代码实现和类图提供给大家,就算作为一种补充吧。

       开始设计模式自然而然到提到几个原则:I、开闭法则(OCP);II、里氏代换法则(LSP);III、依赖倒置法则(DIP);IV、接口隔离法则(ISP);V、合成/聚合复用原则(CARP);VI、迪米特法则(LoD),这几个法则在吕震宇 老师的设计模式(二)和设计模式(三)中有非常详尽的阐述和深入浅出的举例分析。有兴趣的朋友打开链接看一下就可以了。

      补充说明:

  • 我这里所以代码都是用VS2005的C++编译器实现。所以不能保证在其他IDE中能顺利编译,但是我想如果你使用其他编译器,也应该不会有太大问题,主要也应该是stdafx.h文件中包含的头文件问题。 里面出行的结构图都是用微软的Visio2003 绘制,大家下载后可以直接用Visio打开。 在以后所有的模式例子中都有客户程序,客户程序这个角色不是模式本身的内容,它是模式之外的部分,但是正是这个客户程序完成了对模式的使用,模式本身的结构是讲解的重点,但是客户程序如何使用模式也是理解模式的一个重要方面,因此在我后续的介绍中都有客户程序这个角色,并会说明究竟调用模式中的哪些角色完成对模式的使用。

简单工厂模式

生活例子

      吃饭是人的基本需求,如果人类不需要吃饭,可能我们就能活得清闲许多,也就不需要像现在一样没日没夜的工作,学习。我们学习是为了找到更好的工作,好工作为了赚更多的钱,最终为了吃饱饭,吃好饭。因此可以说吃饭是与人息息相关,下面就从吃饭的例子来引入工厂模式的学习。

           如果你想吃饭了,怎么办自己做吗?自己做就相当于程序中直接使用new。当然是自己下个指令,别人来做更爽。那就把做饭的任务交给你的老婆吧,那么她就是一个做饭的工厂了,你告诉她要要吃红烧肉,等会她就从厨房给你端出来一盘香喷喷的红烧肉了,再来个清蒸鱼吧,大鱼大肉不能太多,那就再来个爆炒空心菜,最后再来个西红柿鸡蛋汤。下图 1) 就是这个问题的模型。
 
(图1)
       显然到了这里,你是Client,你老婆就是工厂,她拥有做红烧肉的方法,做清蒸鱼的方法,做爆炒空心菜、西红柿鸡蛋汤的方法,这些方法返回值就是食物抽象。红烧肉、清蒸鱼、爆炒空心菜、西红柿鸡蛋汤就是食物的继承类,到这里你就可以大吃二喝了。简单工厂模式也成型了。哈哈,娶一个手艺不错的老婆还真好,吃的好,吃的爽,又清闲。

       下面来看标准的简单工厂模式的分析。 

意图

把一系列拥有共同特征的产品的创建封装

结构图

 
            (图2)

角色分析
产品基类: 工厂创建的所有产品的基类, 它负责描述所有实例所共有的公共接口。它用来作为工厂方法的返回参数。
代码实现:
        //—这时一个系列的产品基类
class Product
{
protected:
Product(void);
public:
virtual ~Product(void);
public:
virtual void Function() = 0;
};
//cpp
Product::Product(void)
{
}
Product::~Product(void)
{
}

具体产品类:产品1和产品2,这个角色实现了抽象产品角色所定义的接口。
代码实现:
        //产品A
class ConcreteProductA:public Product
{
public:
ConcreteProductA(void);
public:
virtual ~ConcreteProductA(void);
public:
virtual void Function();
};
//cpp
ConcreteProductA::ConcreteProductA()
{
cout<<”创建 A 产品”<<endl;
}
ConcreteProductA::~ConcreteProductA()
{
cout<<”释放 A 产品”<<endl;
}
void ConcreteProductA::Function()
{
cout<<”这是产品 A 具有的基本功能”<<endl;
}
//产品B与A类似不这里不再给出,大家可以下载源码


工厂类:负责具体产品的创建,有两种方式实现产品的创建,I、创建不同的产品用不同的方法;II、创建不同产品用相同的方法,然后通过传递参数实现不同产品的创建。本实例中两种模式都给出了,大家自行分析。

//简单工厂,此类不需要继承,直接硬编码实现生成的产品
class SimpleFactory
{
public:
SimpleFactory(){}
public:
~SimpleFactory(){}
public:
Product CreateProduct(int ProuctType);
Product
CreateProductA();
Product CreateProductB();
};
//CPP
Product
SimpleFactory::CreateProduct(int ProductType=0)
{
Product p = 0;
switch(ProductType)
{
case 0:
p= new ConcreteProductA();
break;
case 1:
p= new ConcreteProductB();
break;
default:
p= new ConcreteProductA();
break;
}
return p;
}
Product
SimpleFactory::CreateProductA()
{
return new ConcreteProductA();
}
Product SimpleFactory::CreateProductB()
{
return new ConcreteProductB();
}

客户端程序:访问的角色包括产品基类、工厂类。不直接访问具体产品类。通过基类指针的多态实现产品功能的调用。
访问描述:客户程序通过调用工厂的方法返回抽象产品,然后执行产品的方法。
//调用代码
SimpleFactory sf;
Product p = sf.CreateProductA();
p->Function();
delete p;
p = sf.CreateProductB();
p->Function();
delete p;

优缺点说明

优点:1) 首先解决了代码中大量New的问题。为何要解决这个问题,好处的说明我想放到结尾总结中。
          2) 用工厂方法在一个类的内部创建对象通常比直接创建对象更灵活。
缺点:对修改不封闭,新增加产品您要修改工厂。违法了鼎鼎大名的开闭法则(OCP)。

附加说明
  • 大家可以参看 吕震宇 老师的C#设计模式(四)参看这个模式的分析,里面还给出了这个模式的两个变体,实现比较简单,有兴趣的朋友可以自行用C++实现一下。* 产品基类的代码中构造函数我用了Protected,而没有使用Public,主要是为了体现编码中的一个最小权限原则。说明此类不许用户直接实例化。虽然这里使用了virtual void Function() = 0;编译器也会控制不让用户直接实例化,不过我依然认为使用私有化构造函数来保护类不直接实例化是一个良好的编程风格。

工厂方法模式

生活例子:

           人是最贪得无厌的动物,老婆手艺再好,总有不会做的菜,你想吃回锅肉,怎么办,让老婆学呗,于是就给她就新增了做回锅肉的方法,以后你再想吃一个新菜,就要给你老婆新加一个方法,显然用老婆做菜的缺点也就暴露出来了,用程序设计的描述就是对修改永远不能封闭。当然优点也是有的,你有了老婆这个工厂,这些菜不用你自己做了,只要直接调用老婆这个工厂的方法就可以了。

          面对上面对修改不能封闭的问题,有没有好的解决方案吗,如果你有钱,问题就迎刃而解了,把老婆抽象变成一个基类,你多娶几个具体的老婆,分别有做鱼的,做青菜的,炖汤的老婆,如果你想吃一个新菜,就再新找个女人,从你的老婆基类继承一下,让她来做这个新菜。显然多多的老婆这是所有男人的梦想,没有办法,法律不允许,那么咱们只是为了做饭,老婆这个抽象类咱们不叫老婆了,叫做厨师吧,她的子类也自然而然的该叫做鱼的厨师、炖汤的厨师了。现在来看这个模式发生了变化,结构中多了一个厨师的抽象,抽象并不具体的加工产品了,至于是炖汤还是炖鱼,是由这个抽象工厂的继承子类来实现,现在的模式也就变成工厂方法模式了,这个上面的结构图1)就变成了下面的图3的结构了。

       (图3)

          现在再来分析现在的模式,显然简单工厂的缺陷解决了,新增加一个菜只需要新增加一个厨师就行了,原来的厨师还在做原来的工作,这样你的设计就对修改封闭了。你看把老婆解放出来,招聘大量的厨师到你家里这个方案多么的完美,你老婆也会爱死你了。当然前提就是你要有多多的钱噢,当然这里的钱的多少在软件领域应该看你的客户软件投资方的要求。
下面来一下标准的工厂模式的实现

意图
  • 定义一个用户创建对象的接口,让子类决定实例化哪一个类。Factory Method使一个类的实例化延迟到其子类。* 上面是GOF关于此模式的意图描述,我想补充的是您可以这样理解:为了改善简单工厂对修改不能关闭的问题。
结构

 
                         图4

角色分析
产品基类:同简单工厂的产品基类,其实就是用和简单工厂中的是同一个类,这里并没有重写。
具体产品类:也是用的简单工厂的具体产品类,为了体现对修改的关闭这里为系统新添加了一个具体产品类,就是“新产品”,代码中叫做“ConcreteProductANew”
工厂基类:定义了工厂创建产品的接口,但是没有实现,具体创建工作由其继承类实现。
代码实例

//工厂模式,此模式的工厂只定义加工产品的接口,具体生成交予其继承类实现
//只有具体的继承类才确定要加工何种产品
class Factory
{
public:
Factory(void);
public:
virtual ~Factory(void);
public:
virtual Product CreateProduct(int ProductType = 0) =0;
};
//CPP
Factory::Factory(void)
{
}
Factory::~Factory(void)
{
}

具体工厂类:工厂基类的具体实现,由此类决定创建具体产品,这里 ConcreteFactory1 对于与图中的 工厂实现,ConcreteFactory2 对于与图中的新工厂。
下面给出实现代码

//工厂实现
class ConcreteFactory1:public Factory
{
public:
ConcreteFactory1();
public:
virtual ~ConcreteFactory1();
public :
Product CreateProduct(int ProductType);
};
//新工厂,当要创建新类是实现此新工厂
class ConcreteFactory2:public Factory
{
public:
ConcreteFactory2();
public:
virtual ~ConcreteFactory2();
public :
Product CreateProduct(int ProductType);
};
//CPP
ConcreteFactory1::ConcreteFactory1()
{
}
ConcreteFactory1::~ConcreteFactory1()
{
}
Product
ConcreteFactory1::CreateProduct(int ProductType = 0)
{
Product p = 0;
switch(ProductType)
{
case 0:
p= new ConcreteProductA();
break;
case 1:
p= new ConcreteProductB();
break;
default:
p= new ConcreteProductA();
break;
}
return p;
}
ConcreteFactory2::ConcreteFactory2()
{
}
ConcreteFactory2::~ConcreteFactory2()
{
}
Product
ConcreteFactory2::CreateProduct(int ProductType = 0)
{
return new ConcreteProductANew();
}

客户端调用:访问角色(产品基类、工厂基类、工厂实现类)
调用描述:客户程序通过工厂基类的方法调用工厂实现类用来创建所需要的具体产品。从而实现产品功能的访问。
代码实现

Factoryfct = new ConcreteFactory1();
Product
p = fct->CreateProduct(0);
p->Function();
delete p;
p = fct->CreateProduct(1);
p->Function();
delete p;
delete fct;
fct = new ConcreteFactory2();
p=fct->CreateProduct();
delete p;
delete fct;

优缺点分析

优点

  • 简单工厂具有的优点* 解决了简单工厂的修改不能关闭的问题。系统新增产品,新增一个产品工厂即可,对抽象工厂不受影响。

缺点:对于创建不同系列的产品无能为力

适用性
  • 当一个类不知道它所必须创建的对象的类的时候。 当一个类希望由它的子类来指定它所创建的对象的时候。 当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。
其他参考
  • 吕震宇的C#设计模式(5)-Factory Method Pattern
  • TerryLee 的.NET设计模式(5):工厂方法模式(Factory Method)

 

抽象工厂模式

生活例子

      世事多变,随着时间的推移,走过的地方越来越多,你天南海北的朋友也越来越多。你发现菜原来还分了许多菜系,鲁菜、粤菜、湘菜等等,它们各有各的风味,同样是红烧肉由不同菜系出来的味道也各不相同, 你招待不同的朋友要用不同的菜系,这下难办了,你的厨师都是鲁菜风味,怎么办,广东的朋友来了吃不惯。现在我们再回到简单工厂模式(就是老婆做菜的模式),我们把红烧肉再向下继承,生成鲁菜红烧肉、粤菜红烧肉、湘菜红烧肉;清蒸鱼向下继承为鲁菜清蒸鱼、粤菜清蒸鱼、湘菜清蒸鱼,其它也以此类推。我们也修改一下老婆的这个类,不让其返回食物基类,而是返回红烧肉、清蒸鱼、爆炒空心菜、西红柿鸡蛋汤这一层次,并把这些方法抽象化,作为菜系工厂基类,然后再从此基类继承出,鲁菜工厂、粤菜工厂、湘菜工厂等等,再由这些具体工厂实现创建具体菜的工作,哈哈你如果招待广东朋友就用粤菜工厂,返回的就是一桌粤菜菜系的红烧肉、清蒸鱼、空心菜和西红柿鸡蛋汤了,你的广东朋友一定会吃的非常合乎胃口了。噢,非常好,你已经实现了抽象工厂模式了。结构模型图也变成了下图 6)的样子了。

                      (图6)
       现在可以看到,想新来做一个菜系,只需新聘请一个厨师就可以了,多么完美,但是你先别高兴太早,如果你想新增加一个菜就变得非常困难了。

意图

       提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
结构

角色分析
产品基类:这里包含产品基类A和产品基类B,实际上在我的示例代码中,这两个产品都从共同的基类继承而来,但是这个继承关系却是在这个模式之外的部分,而本身这个模式关心的是这两个产品基类的差异部分。
代码实现:这里的代码就是借用的简单工厂模式中具体产品类的代码实现部分,为了大家阅读方便,下面重新给出一下。

        //产品A
class ConcreteProductA:public Product
{
public:
ConcreteProductA(void);
public:
virtual ~ConcreteProductA(void);
public:
virtual void Function();
};
//cpp
ConcreteProductA::ConcreteProductA()
{
cout<<”创建 A 产品”<<endl;
}
ConcreteProductA::~ConcreteProductA()
{
cout<<”释放 A 产品”<<endl;
}
void ConcreteProductA::Function()
{
cout<<”这是产品 A 具有的基本功能”<<endl;
}
//产品B与A类似不这里不再给出,大家可以下载源码



具体产品
类:这里的具体产品类是产品A1,A2,B1、B2等,
代码实现:A1对应的实现就是“”

class ConcreteProductA1:public ConcreteProductA
{
public:
ConcreteProductA1(void);
public:
virtual ~ConcreteProductA1(void);
public:
virtual void Function();
};
//CPP
ConcreteProductA1::ConcreteProductA1()
{
cout<<”创建 A1 产品”<<endl;
}
ConcreteProductA1::~ConcreteProductA1()
{
cout<<”释放 A1 产品”<<endl;
}
void ConcreteProductA1::Function()
{
cout<<”这时产品 A1 具有的基本功能”<<endl;
}


工厂抽象接口:定义了创建产品的接口,这里返回参数是返回的产品A,产品B,而本身产品A和B的共同基类,小弟认为正是这个特征构成了抽象工厂和工厂模式的区别。
代码实现

//抽象工厂模式
class AbstractFactory
{
public:
AbstractFactory();
public:
virtual ~AbstractFactory();
public:
virtual ConcreteProductA CreateA() = 0;
virtual ConcreteProductB
CreateB() = 0;
};
//CPP
AbstractFactory::AbstractFactory()
{
}
AbstractFactory::~AbstractFactory()
{
}


具体工厂实现类:工厂1和工厂2。新增加系列,只需新实现一个工厂。
代码实现: 工厂1的就是ConcreteAbsFactory1,工厂2的代码类似,这里没有给出,可以在下载代码中看到

////工厂1—–
class ConcreteAbsFactory1:public AbstractFactory
{
public:
ConcreteAbsFactory1();
public:
virtual ~ConcreteAbsFactory1();
public:
virtual ConcreteProductA CreateA();
virtual ConcreteProductB
CreateB();
};
//CPP
ConcreteAbsFactory1::ConcreteAbsFactory1()
{
}
ConcreteAbsFactory1::~ConcreteAbsFactory1()
{
}
ConcreteProductA ConcreteAbsFactory1::CreateA()
{
return new ConcreteProductA1();
}
ConcreteProductB
ConcreteAbsFactory1::CreateB()
{
return new ConcreteProductB1();
}


客户端访问: 访问角色(产品基类、抽象工厂、具体工厂实现类)
访问描述: 通过抽象工厂的指针访问具体工厂实现来创建对应系列的产品,然后通过产品基类指针访问产品功能。
调用代码:

          AbstractFactory absfct = new ConcreteAbsFactory1();
ConcreteProductA
cpa = absfct->CreateA();
cpa->Function();
delete cpa;
ConcreteProductB *cpb = absfct->CreateB();
cpb->Function();
delete cpb;
delete absfct;
absfct = new ConcreteAbsFactory2();
cpa = absfct->CreateA();
cpa->Function();
delete cpa;
cpb = absfct->CreateB();
cpb->Function();
delete cpb;

 

和工厂模式的分析比较

       现在可以和工厂模式对比一下,抽象工厂返回的接口不再是产品A和产品B的共同基类Product了,而是产品A、产品B基类(在工厂模式中它们为具体实现类,这里变成了基类)了。此时工厂的抽象和简单工厂中的工厂方法也很类似,就是这些特征区使其别于工厂模式而变成抽象工厂模式了,因此抽象工厂解决的是创建一系列有共同风格的产品(鲁菜还是粤菜),而工厂方法模式解决的创建有共同特征的一系列产品(红烧肉、清蒸鱼它们都是食物)。当然简单工厂的缺陷在抽象工厂中又再次出现了,我要新增加一个产品,工厂抽象接口就要改变了。因此抽象工厂并不比工厂模式完美,只不过是各自的适用领域不同而已。其实,这里如果把抽象工厂模式的接口返回产品A和产品B的共同基类(工厂模式返回的参数),你会发现,奇怪这个模式怎么这么眼熟,它不是恰恰退化成工厂模式了。
       类模式与对象模式的区别讨论:先看定义类“模式使用继承关系,把对象的创建延迟的子类,对象模式把对象的创建延迟到另一个对象中”。 分析:首先它们创建对象都不是在基类中完成,都是在子类中实现,因此都符合类模式的概念;但是工厂模式的创建产品对象是在编译期决定的,要调用某个工厂固定的,而抽象工厂模式对产品的创建是在运行时动态决定的,只有到运行时才确定要调用那个工厂,调用工厂随运行环境而改变。(这里我一直很混乱,欢迎大家讨论)

适用性
  • 一个系统要独立于它的产品的创建、组合和表示时 一个系统要由多个 产品系列中的一个来配置时 当你要强调一个系列相关的产品对象的设计以便进行联合使用时* 当你提供一个产品类库,而只想显示它们的接口而不是实现时。
参考
  • 吕震宇的C#设计模式(6)* TerryLee 的.NET设计模式(3):抽象工厂模式(Abstract Factory)

总结

          工厂本质就是用工厂方法替代直接New来创建对象。这里不是指的让用户重载一个新操作符号来进行创建对象的操作,而是说把New 操作封装在一个方法中,等用户需要创建对象时调用此方法而避免直接使用New而已。这样做的目的就是之一就是封装,避免代码中大量New的运算符,这当然不是主要目的,因为这样虽然New少了,CreateObject方法却多了,但是如果产品类的构造函数变了,我想常用工厂模式的修改源代码的工作应该简便许多吧,当然这算不上这个模式的好处,它的真正强大的功能其实在于适应变化,这也是整个设计模式最根本的目的;还有一点就是体现了抽象于实现的分离,当然创建型模式都具有这个特点,工厂模式非常明显吧了,把具体创建工作放置到工厂中,使客户端程序更专注与业务逻辑的,这样的代码结构也更进行合理。

C++中回调(CallBack)的使用方法

发表于 2009-07-14 | 分类于 C++

回调函数是一个很有用,也很重要的概念。当发生某种事件时,系统或其他函数将会自动调用你定义的一段函数。回调函数在windows编程使用的场合很多,比如Hook回调函数:MouseProc,GetMsgProc以及EnumWindows,DrawState的回调函数等等,还有很多系统级的回调过程。 一般情况下, 我们使用的回调函数基本都是采用C语言风格. 这里介绍一种C++风格的回调对象方法. 采用template实现.

template < class Class, typename ReturnType, typename Parameter >  
class SingularCallBack  
{   
public:  
 
    typedef ReturnType (Class::Method)(Parameter);  
 
    SingularCallBack(Class
_class_instance, Method _method)  
    {  
       //取得对象实例地址,及调用方法地址  
       class_instance = _class_instance;  
       method        = _method;  
    };  
 
    ReturnType operator()(Parameter parameter)  
    {  
       // 调用对象方法  
       return (class_instance->method)(parameter);  
    };  
 
    ReturnType execute(Parameter parameter)  
    {  
       // 调用对象方法  
       return operator()(parameter);  
    };  
 
 
   private:  
 
    Class
  class_instance;  
    Method  method;  
 
}; 
示例:

以下是两个类实现.
class A  
{  
 
   public:  
 
    void output()  
    {  
       std::cout << “I am class A :D” << std::endl;  
    };  
 
};  
 
class B  
{  
 
   public:  
 
    bool methodB(A a)  
    {  
       a.output();  
       return true;  
    }  
 
};  
 

SingularCallBack的各种调用示例:
A a;
B b;

SingularCallBack< B,bool,A >* cb;
cb = new SingularCallBack< B,bool,A >(&b,&B::methodB);

if(cb->execute(a))
{
   std::cout << “CallBack Fired Successfully!” << std::endl;
}
else
{
   std::cout << “CallBack Fired Unsuccessfully!” << std::endl;
}
  
class AClass  
{  
   public:  
 
     AClass(unsigned int _id): id(_id){};  
    ~AClass(){};  
 
     bool AMethod(std::string str)  
     {  
        std::cout << “AClass[“ << id << “]: “ << str << std::endl;  
        return true;  
     };  
 
   private:  
 
    unsigned int id;  
 
};   

typedef SingularCallBack < AClass, bool, std::string > ACallBack;   
  

int main()  
{  
 
   std::vector < ACallBack > callback_list;  
 
   AClass a1(1);  
   AClass a2(2);  
   AClass a3(3);  
 
   callback_list.push_back(ACallBack(&a1, &AClass::AMethod));  
   callback_list.push_back(ACallBack(&a2, &AClass::AMethod));  
   callback_list.push_back(ACallBack(&a3, &AClass::AMethod));  
 
   for (unsigned int i = 0; i < callback_list.size(); i++)  
   {  
      callback_listi;  
   }  
 
   for (unsigned int i = 0; i < callback_list.size(); i++)  
   {  
      callback_list[i].execute(“abc”);  
   }  
 
   return true;  
 
}  
 

引用:

C++ Callback Solution

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/force_eagle/archive/2009/07/14/4347329.aspx

IM的技术原理与发展

发表于 2009-07-09 | 分类于 未分类

一、IM技术概念

IM技术全称Instant Messaging,中文翻译“即时通讯”,它是一种使人们能在网上识别在线用户并与他们实时交换消息的技术,是电子邮件发明以来迅速崛起的在线通讯方式。

IM的出现和互联网有着密不可分的关系,IM完全基于TCP/IP网络协议族实现,而TCP/IP协议族则是整个互联网得以实现的技术基础。 最早出现即时通讯协议是IRC(Internet Relay Chat),但是可惜的是它仅能单纯的使用文字、符号的方式通过互联网进行交谈和沟通。随着互连网变得高度发达,即时通讯也变得远不止聊天这么简单,自1996年第一个IM产品ICQ发明后,IM的技术和功能也开始基本成型,语音、视频、文件共享、短信发送等高级信息交换功能都可以在IM工具上实现,于是功能强大的IM软件便足以搭建一个完整的通信交流平台。目前最具代表性的几款的IM通讯软件有MSN、Google Talk、Yahoo、Messenger 、腾讯QQ等。

二、IM技术原理和工作方式

典型的IM工作方式如下:登陆IM通讯中心(IM通讯服务器),获取一个自建立的历史的交流对象列表(好友列表),然后自身标志为在线状态,当好友列表中的某人在任何时候登录上线并试图通过你的计算机联系你时,IM系统会发一个消息提醒你,然后你能与他建立一个聊天会话通道进行各种消息如键入文字、通过语音等的交流,
从技术上来说,IM的基本技术原理如下:

IM服务器
登陆或注销
用户A通过列表找到B,用户B获得的消息并与之交谈
通过IM服务器指引建立与B单独的通讯通道

第一步,用户A输入自己的用户名和密码登录IM服务器,服务器通过读取用户数据库来验证用户身份,如果验证通过,登记用户A的IP地址、IM客户端软件的版本号及使用的TCP/UDP端口号,然后返回用户A登录成功的标志,此时用户A在IM系统中的状态为在线(Online Presence)。

第二步,根据用户A存储在IM服务器上的好友列表(Buddy List),服务器将用户A在线的相关信息发送给也同时在线的IM好友的PC机,这些信息包括在线状态、IP地址、IM客户端使用的TCP端口(Port)号等,IM好友的客户端收到此信息后将在予以提示。

第三步是IM服务器把用户A存储在服务器上的好友列表及相关信息回送到他的客户端机,这些信息包括也在线状态、IP地址、IM客户端使用的TCP端口(Port)号等信息,用户A的IM客户端收到后将显示这些好友列表及其在线状态。

三、IM通讯方式

1.在线直接通讯
如果用户A想与他的在线好友用户B聊天,他将直接通过服务器发送过来的用户B的IP地址、TCP端口号等信息,直接向用户B的PC机发出聊天信息,用户B的IM客户端软件收到后显示在屏幕上,然后用户B再直接回复到用户A的PC机,这样双方的即时文字消息就不再IM服务器中转,而是直接通过网络进行点对点的通讯,即对等通讯方式(Peer To Peer)。

2.在线代理通讯
用户A与用户B的点对点通讯由于防火墙、网络速度等原因难以建立或者速度很慢,IM服务器将会主动提供消息中转服务,即用户A和用户B的即时消息全部先发送到IM服务器,再由服务器转发给对方。

3.离线代理通讯
用户A与用户B由于各种原因不能同时在线的时候,如此时A向B发送消息,IM服务器可以主动寄存A用户的消息,到B用户下一次登陆的时候,自动将消息转发给B。

4.扩展方式通讯
用户A可以通过IM服务器将信息以扩展的方式传递给B,如短信发送方式发送到B的手机,传真发送方式传递给B的电话机,以email的方式传递给B的电子邮箱等。
早期的IM系统,在IM客户端和IM服务器之间通讯采用UDP协议,UDP协议是不可靠的传输协议,而在IM客户端之间的直接通讯中,采用具备可靠传输能力的TCP协议。随着用户需求和技术环境的发展,目前主流的IM系统倾向于在IM客户端之间、IM客户端和IM服务器之间都采用TCP协议。
即时通讯相对于其他通讯方式如电话、传真、email等的最大优势就是消息传达的即时性和精确性,只要消息传递双方均在网络上可以互通,使用即时通讯软件传递消息,传递延时仅为1秒种

四、兴起的嵌入式IM工具。

传统的IM在统治了互联网即时通讯领域长达十年之久,以其日趋稳定的定能,与较强的用户黏着度,至今仍统治着这个巨大的市场。然而,软件行业的技术精英们,并不满足于此。他们厚积薄发,一直致力于开发出性能更为优越的即时通讯工具。当然,在功能上的不断完善,自然是一个必然的发展方向,在Web2.0时代,如何大力增强用户对网站的黏着度,而不仅仅是对于IM的拥附,已经成为他们的主攻方向了。于是,嵌入式IM工具,应运而生了。

相对以往的传统的即使沟通工具,它们需要用户下载软件包,需要用户进行安装。对于拥有IM产品的网站而言,用户在登陆网站后,不能直接使用其IM工具,对于流量与用户的黏着度,都是有一定影响的。因此在IM与网站相互依存的今天,没有哪家网络公司,愿意将IM工具孤立开来。

于是,目前,一种新型的嵌入式IM工具就应运而生了。这种IM工具,不需要下载安装,当用户登陆网页后,该IM直接嵌套在网页中,可以直接使用。

而在功能上,则一点也不输于传统的IM,无论是传统的文字沟通的速度与效率,还是近年来越来越成为IM工具必备的音频/视频功能,这种嵌入式IM都能提供非常稳定的传输。更值得一提的是,因为嵌入式IM是嵌套在网页上的,软件供应商,可以根据网站需求,设计出适合网站风格的IM产品。而不是像传统的IM工具,千篇一律,毫无个性可言。

目前,这类嵌入式IM在社区、交友、社团及协作等类型的网站上,应用已经较为广泛。在Web2.0时代,将发挥越来越重要的作用。

VS2005内存泄漏检测方法

发表于 2009-07-06 | 分类于 VC
<div>

原文地址: http://ufownl.blog.163.com/blog/static/1250122200861912757566/

非MFC程序可以用以下方法检测内存泄露:

 

1.程序开始包含如下定义:

#define _CRTDBG_MAP_ALLOC

#include <stdlib.h>

#include <crtdbg.h>

 

2.程序退出前添加下面的函数:

_CrtDumpMemoryLeaks();

 

Debug版本程序运行结束后如有内存泄漏,输出窗口中会显示类似信息:
Detected memory leaks!
Dumping objects ->
e:/microsoft visual studio 8/vc/include/crtdbg.h(1150) : {48} normal block at 0x00382F50, 12 bytes long.
 Data: <            > 01 00 00 00 02 00 00 00 03 00 00 00
Object dump complete.

 

 

MFC程序内存泄漏检测方法:

 

1.在 CMyApp 中添加如下三个 CMemoryState 类的成员变量:

#ifdef _DEBUG
protected:
      CMemoryState m_msOld, m_msNew, m_msDiff;

#endif  // _DEBUG

 

2.在 CMyApp::InitInstance() 中添加如下代码:

#ifdef _DEBUG
      m_msOld.Checkpoint();

#endif  // _DEBUG

 

3.在 CMyApp::ExitInstance() 中添加如下代码:

#ifdef _DEBUG
      m_msNew.Checkpoint();
      if (m_msDiff.Difference(m_msOld, m_msNew))
      {
            afxDump<<”/nMemory Leaked :/n”;
            m_msDiff.DumpStatistics();
            afxDump<<”Dump Complete !/n/n”;
      }

#endif  // _DEBUG

 

Debug版本程序运行结束后如有内存泄漏,输出窗口中会显示类似信息:

Memory Leaked :
0 bytes in 0 Free Blocks.
8 bytes in 1 Normal Blocks.
0 bytes in 0 CRT Blocks.
0 bytes in 0 Ignore Blocks.
0 bytes in 0 Client Blocks.
Largest number used: 8825 bytes.
Total allocations: 47506 bytes.
Dump Complete !

Detected memory leaks!
Dumping objects ->
g:/programs/chat/chatdlg.cpp(120) : {118} normal block at 0x00D98150, 8 bytes long.
 Data: <        > A8 7F D9 00 01 00 00 00
Object dump complete.


代理类(Surrogate)

发表于 2009-07-05 | 分类于 C++

      依然是学习<Ruminations On c++>的一点笔记。

问题的提出是:怎样设计一个C++容器,使它有能力包含类型不同而彼此相关的对象呢?

容器通常只能包含一种类型的对象,所以很难在容器中存储对象本身,最容易想到的就是存储指针(我目前在项目中使用的也是这种方法),这样虽然可以通过继承来处理类型不同的问题,但是也增加了内存分配的额外负担。

书中介绍了一种叫做代理的对象来解决这个问题,代理类是句柄类中最简单的一种,下面是一个简单的代理类实现:

// Surrogate.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"

#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>

#include <iostream>
#include <vector>
using namespace std;

class Vehicle
{
public:
virtual double weight()const =0;
virtual void   start()=0;
virtual Vehicle* copy()const=0;//用来复制编译时类型未知的对象
virtual ~Vehicle()//以便调用正确的派生类析构函数
{

}
//...
};

class RoadVehicle : public Vehicle
{
public:
double weight()const
{
cout<<"RoadVehicle::weight()"<<endl;
return 0;
}
void   start()
{
cout<<"RoadVehicle::start()"<<endl;
}
Vehicle* copy()const
{
return new RoadVehicle(*this);
}
/**/
};

class AutoVehicle : public RoadVehicle
{
public:
double weight()const
{
cout<<"AutoVehicle::weight()"<<endl;
return 0;
}
void   start()
{
cout<<"AutoVehicle::start()"<<endl;
}
Vehicle* copy()const
{
return new AutoVehicle(*this);
}
/**/
};

class Aircraft : public Vehicle
{
public:
double weight()const
{
cout<<"Aircraft::weight()"<<endl;
return 0;
}
void   start()
{
cout<<"Aircraft::start()"<<endl;
}
Vehicle* copy()const
{
return new Aircraft(*this);
}
/**/
};

class Helicopter : public Aircraft
{
public:
double weight()const
{
cout<<"Helicopter::weight()"<<endl;
return 0;
}
void   start()
{
cout<<"Helicopter::start()"<<endl;
}
Vehicle* copy()const
{
return new Helicopter(*this);
}
/**/
};

//代理类
class VehicleSurrogate
{
public:
VehicleSurrogate();
VehicleSurrogate(const Vehicle&);
~VehicleSurrogate();
VehicleSurrogate(const VehicleSurrogate&);
VehicleSurrogate& operator=(const VehicleSurrogate&);

//来自类Vehicle的操作
double weight()const;
void start();
private:
Vehicle *vp;
};

VehicleSurrogate::VehicleSurrogate():vp(0)
{

}

VehicleSurrogate::VehicleSurrogate(const Vehicle& v):vp(v.copy())
{

}

VehicleSurrogate::~VehicleSurrogate()
{
delete vp;
}

VehicleSurrogate::VehicleSurrogate(const VehicleSurrogate& v):vp(v.vp?v.vp->copy():0)
{

}

VehicleSurrogate& VehicleSurrogate::operator=(const VehicleSurrogate& v)
{
if (this!=&v)
{
delete vp;
vp=(v.vp?v.vp->copy() : 0);
}
return *this;
}

//直接转发给vp调用
double VehicleSurrogate::weight()const
{
if (vp==0)
{
throw "empty VehicleSurrogate.weight()";
}
return vp->weight();
}

void VehicleSurrogate::start()
{
if (vp==0)
{
throw "empty VehicleSurrogate.start()";
}
vp->start();
}

int _tmain(int argc, _TCHAR* argv[])
{
vector<VehicleSurrogate> vecVS;

RoadVehicle rv;
Aircraft ar;
AutoVehicle av;
Helicopter hc;
vecVS.push_back(rv);
vecVS.push_back(ar);
vecVS.push_back(av);
vecVS.push_back(hc);

vector<VehicleSurrogate>::iterator iter=vecVS.begin();
vector<VehicleSurrogate>::iterator iterEnd=vecVS.end();
while(iter!=iterEnd)
{
iter->weight();
iter->start();
iter++;
}

_CrtDumpMemoryLeaks();

return 0;
}

下面是测试结果,使用VS2005发现有内存泄漏,暂时没找到原因。

result

WM_NCHITTEST的几点心得

发表于 2009-06-29 | 分类于 VC

1:WM_NCHITTEST是作为Non-Client测试使用的,

2:当此消息的返回值为MSDN上面所述的DefWindowProc所规定的相应值时,系统才会进一步产生相应的客户区或者非客户区鼠标消息,而且在你使用OnNcCalcSize时自定义的非客户区内,系统是不会产生任何鼠标消息的,除非你返回一个值以表示当前鼠标在哪个区域,毕竟对于你的区域系统也不知道该返回什么值

3:除了返回HTCLIENT,其他任何值系统都会产生相应的NC鼠标消息

这个消息困惑好久了,今天测试了下才发现是这样的,真是恍然大悟,明天做个例子测试下。

应用器 & 操纵器

发表于 2009-06-17 | 分类于 C++

      操纵器(Manipalator),以某种方式作用于它的参数所表示的数据;应用器(Applicator),是一个重载操作符,它的操作数是一个可操纵的值和一个将作用于这个值得操纵器。

 template <class stype,class vtype >
class fcn_obj
{
public:
  fcn_obj(stype (f)(stype&,vtype),vtype v):
            func(f),val(v)
  {
  }
  stype& operator()(stype& s)const
  {
    return (
func)(s,val);
  }
private:
  stype& (*func)(stype&,vtype),vtype v);
  vtype val;
};

template <class stype,class vtype>
stype& operator<<
(stype& ofile,const fcn_obj<stype,vtype>& im)
{
   return im(ofile);
}

fun_obj<ostream long>
hexconv(long n)
{
   ostream (*my_hex)(ostream&,long)=hexconv;
   return fcn_obj<ostream,long>(my_hex,n);
}

 

上面的代码来自Ruminations On C++,iostream库中使用了这个设计,使得cout<<”Hello,World!”<<endl;用起来很舒服,终于知道了endl为什么叫做操纵器了。

回调函数指南

发表于 2009-06-14 | 分类于 C++

作者: Marius Bancila

翻译:  QiuTao****

介绍****

如果你正在读这篇文章,也许你是想知道什么是回调函数。这篇文章讲解了什么是回调函数,他们有什么优点,以及为什么你要使用他们等等。但是在学习回调函数之前,你应该对函数指针有一定得了解。如果你还不了解,可以参考C/C++书籍或者下面的链接:

  • The Syntax of C and C++ Function Pointers
  • Pointers to member functions
  • Declaring, Assigning, and Using Function Pointers

什么是回调函数?****

对这个问题的一个简单回答就是,回调函数是一个通过函数指针调用的函数。如果你传递一个函数指针(地址)参数给另一个函数,当那个指针被用作去调用它所指的函数时,回调就产生了。

为什么要使用回调函数?****

因为他们将调用者和被调者分开了,调用者不需要关心被调者,它仅仅需要知道被调者的原型以及某些可能的限制(例如,返回值可以为int,但是特定的只有特定的意义)

如果你想知道在它在现实中的用途,想象一下你想写一个提供实现排序算法的库,比如冒泡排序,希尔排序,快速排序等等。但是你不想将排序的逻辑(元素之间的先后关系)放在你的函数中,从而使得库更加易于使用。你希望客户自己实现排序之间的逻辑关系,或者你希望可以用于各种各样的数据类型(int,floats,strings等等)。你该怎么办呢?你可以使用函数指针并进行回调。

回调可以被用作通知,例如。你需要在程序中设置一个定时器,每当一段时间过去后,你的程序必须被通知。但是,定时器的实现机制对你的程序一无所知。它仅仅希望一个给定原型的函数指针,并使用函数指针进行回调,通知你的程序某个事件发生了。确实,API函数SetTimer在一段时间到达后使用一个回调进行通知(如果没有提供回调函数,它将给应用程序队列投递一个消息)。

另外一个使用回调函数的API函数EnumWindow,枚举当前屏幕中所有的顶层窗口。EnumWindow迭代每一个顶层窗口,传递一个窗口句柄给提供的回调函数。如果被调者返回一个值,那么迭代继续,否则迭代停止。EnumWindow并不关系被调用者是什么以及它对传读过去的句柄做了些什么,它仅仅对其返回值感兴趣,因为它是基于该返回值决定是否需要继续执行。

然而,回调函数是从C继承而来的,因此,在C++中应该仅仅在需要与C代码中进行交互时使用,除此之外,你应该使用虚拟函数或者函数对象,而不是回调函数。

一个简单的实现例子****

下面的例子可以再附件文件中找到,我创建了一个叫做sort.dll的动态链接库。它导出了一个叫做CompareFunction类型的函数:



typedef int (__stdcall CompareFunction)(const byte, const byte*);

该函数原型是你的回调函数的类型,另外还导出了两个方法,分别是BubbleSort和QuickSort,这两个函数有一样的原型,但是提供了不一样的排序算法。



void DLLDIR stdcall Bubblesort(byte* array,

                                 int size,

                                 int elem_size,

                                 CompareFunction cmpFunc);

 

void DLLDIR stdcall Quicksort(byte* array,

                                int size,

                                int elem_size,

                                CompareFunction cmpFunc);

各个参数的意义为:

※byte* array:指向数组元素类型的指针(任意类型)

※int size:数组元素个数

※int elem_size:数组中每一个元素的大小,按字节计算

※CompareFunction cmpFunc:回调函数指针

这两个函数的实现都是对一个数组进行排序,但是,每一次都需要决定两个元素中哪个在前面,被作为参数传递的回调函数地址即是进行。对于库编写者,它不需要关心这个函数是否实现了,以及是如何实现的。它所关心的是这个函数带有两个以元素地址为参数类型的参数并返回下面值之一(这是库开发者和用户之间约定的):                                

  • -1: 如果第一个元素小于或者在第二个元素之前
  • 0: 如果两个元素相等或者相对位置没有规定
  • 1:如果第一个元素大于或者在第二个元素之后

在这些约定之后,Bubblesort的实现如下(Quicksort,比这稍微复杂点,可以参见附件):



·         void DLLDIR __stdcall Bubblesort(byte array,

·                                          int size,

·                                          int elem_size,

·                                          CompareFunction cmpFunc)

·         {

·            for(int i=0; i < size; i++)

·            {

·               for(int j=0; j < size-1; j++)

·               {

·                  // make the callback to the comparison function

·                  if(1 == (cmpFunc)(array+jelem_size,

·                           array+(j+1)elem_size))

·                  {

·                     // the two compared elements must be interchanged

·                     byte temp = new byte[elem_size];

·                     memcpy(temp, array+jelem_size, elem_size);

·                     memcpy(array+jelem_size,

·                            array+(j+1)elem_size,

·                            elem_size);

·                     memcpy(array+(j+1)*elem_size, temp, elem_size);

·                     delete [] temp;

·                  }

·               }

·            }

·         }

·         注意:由于实现中使用了memcpy,这个这些库函数只能在POD类型中使用

·         在客户端,他们必须实现一个通过地址传递给Bubblesor函数的回调函数,作为一个简单的实现,我写了一个比较两个整形和字符串常量的例子:



·         int stdcall CompareInts(const byte velem1, const byte velem2)

·         {

·            int elem1 = (int)velem1;

·            int elem2 = (int)velem2;

·          

·            if(elem1 < elem2)

·               return -1;

·            if(elem1 > elem2)

·               return 1;

·          

·            return 0;

·         }

·          

·         int stdcall CompareStrings(const byte velem1, const byte velem2)

·         {

·            const char elem1 = (char)velem1;

·            const char elem2 = (char)velem2;

·          

·            return strcmp(elem1, elem2);

·         }

·         为了将这些进行测试,我编写了一个简单的程序,传递一个包含5个元素的数组以及相应的回调函数指针给Bubblesort()和Quicksort()



·         int main(int argc, char argv[])

·         {

·            int i;

·            int array[] = {5432, 4321, 3210, 2109, 1098};

·          

·            cout << “Before sorting ints with Bubblesort/n”;

·            for(i=0; i < 5; i++)

·               cout << array[i] << ‘/n’;

·          

·            Bubblesort((byte)array, 5, sizeof(array[0]), &CompareInts);

·          

·            cout << “After the sorting/n”;

·            for(i=0; i < 5; i++)

·               cout << array[i] << ‘/n’;

·          

·            const char str[5][10] = {“estella”,

·                                     “danielle”,

·                                     “crissy”,

·                                     “bo”,

·                                     “angie”};

·          

·            cout << “Before sorting strings with Quicksort/n”;

·            for(i=0; i < 5; i++)

·               cout << str[i] << ‘/n’;

·          

·            Quicksort((byte*)str, 5, 10, &CompareStrings);

·          

·            cout << “After the sorting/n”;

·            for(i=0; i < 5; i++)

·               cout << str[i] << ‘/n’;

·          

·            return 0;

·         }

·         如果我希望以降序排列(最大的元素在第一个),需要做的只是改变回调函数代码或者提供另一个期望的回调函数。

调用约定****

在上面的代码中,你可以看到在函数声明部分有个stdcall。由于它以一个双下划线开始,很显然这是一个编译器特定的扩展,更准确的说是微软特定的扩展。任何支持Win32应用程序开发的编译器都提供这样或者与之等价的扩展。一个通过stdcall标记的函数使用标准的调用约定,之所以这样叫是因为Win32API函数(除了部分带有可变参数列表的外)使用它。遵循标准调用约定的函数,在返回调用者之前先清除堆栈中参数。这是源自Pascal的标准调用约定。但是在C/C++中,调用约定是调用者清理堆栈而不是被调用者。为了强制函数使用C/C++函数调用,__cedecl必须被指定。变参参数函数使用c/c++调用约定。

Windows采用Pascal调用约定是因为它可以减少代码体积,这在早期运行在只有640kb RAM的系统上的Windows是非常重要的。

如果你不喜欢__stdcall,你也可以使用CALLBACK宏,在windef.h中定义如下:



#define CALLBACK    __stdcall

or



#define CALLBACK    PASCAL

PASCAL被定义为__stdcall

你可以通过连接Calling Convetions in Microsoft Visual C++.获取更多调用约定的信息

C++**成员函数作为回调函数**

由于你可能正在使用C++,希望使用成员函数作为回调函数。但是,如果你进行下面的尝试:



class CCallbackTester

{

public:

   int CALLBACK CompareInts(const byte velem1, const byte velem2);

};

 

Bubblesort((byte*)array, 5, sizeof(array[0]),

           &CCallbackTester::CompareInts);

如果使用MS编译器,你将会得到下面的编译错误:

error C2664: ‘Bubblesort’ : cannot convert parameter 4 from ‘int (stdcall CCallbackTester::)(const unsigned char ,const unsigned char *)’ to ‘int (stdcall )(const unsigned char ,const unsigned char *)’ There is no context in which this conversion is possible

这是由于non-static成员函数有一个额外参数,this指针。

这将迫使你使用static成员函数,如果static不能接受,你可以使用某些技术解决这个问题,可以通过下面的链接查找:

  • How to Implement Callbacks in C and C++
  • C++ Callback Demo
  • Callbacks in C++ Using Template Functors

Com初识

发表于 2009-06-10 | 分类于 VC

最近决定真正的看看Com了,目前的理解:

必须实现IUnknow的三个接口,需要一个IDL文件,通过QueryInterface来获取接口并调用,直接用C++写比较繁琐,用ATL貌似是比较好的,还没具体研究过,先记到这,后续继续学习。

1…91011…16

billowqiu

157 日志
33 分类
10 标签
GitHub E-Mail
© 2020 billowqiu
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.3