您的位置:首页 > 其它

[web]小实验-帮助理解COM-By ilovesoup

2005-07-14 14:47 405 查看
今天看COM,突然想起来做个好玩的小实验。
为啥COM要做成这个样子?为啥这样的COM就能被其他语言调用?
顺着这个思路好像不是很容易得到答案,那么我想是不是换一个方向来考虑。如果我想要让我的CPP类被重用,那么我该如何?
俺们try一下看看。

建两个文件,caller.c,def.cpp,看清楚后缀哦~一定要一个c一个cpp。VC会把后缀为c的按照c语言的语法来编译。不信试试看struct里面加一个member function看看能不能通过。
由于语言环境不同,俺们不用头文件沟通不同的编译单元。所以,就两个光杆源文件,剩下的交给linker吧。
我们用经典的hello world开局…(很没有创意诶….)
Def.cpp
#include <iostream>

using namespace std;

void test()
{
cout << "hello world!" << endl;
}

Caller.c
#include <stdio.h>

void test();

int main()
{
test();
}

你觉得能不能通过呢?看起来好像很完美的说…
(要是这么顺利我还会写出来么 = =)
果然
Step error LNK2019: unresolved external symbol _test referenced in function _main
CPP会把函数名字偷偷改成很奇怪的样子,因为在CPP里面允许函数重名(重载机制),所以光是一个函数名称没办法区别一个函数。
记得以前教extern “C”的时候说这个东东用来干啥么?
就是干这个的…
于是我们推出ver2.0
Def.cpp

#include <iostream>

using namespace std;

#define CSTYLE_BEGIN extern "C" {
#define CSTYLE_END }

CSTYLE_BEGIN

void test()
{
cout << "hello world!" << endl;
}

CSTYLE_END

哦也…终于done了…好性奋的说= =

由于之前的成功造成野心急剧膨胀,我马上希望能把写的类也重用了。
Caller.c
#include <stdio.h>

void test();

struct apple
{
void speak();
};

int main()
{
struct apple sample;
test();
}

Def.cpp

#include <iostream>

using namespace std;

#define CSTYLE_BEGIN extern "C" {
#define CSTYLE_END }

CSTYLE_BEGIN

void test()
{
cout << "hello world!" << endl;
}

class apple
{
public:
void speak()
{
cout << "hello apple" << endl;
}
};

CSTYLE_END

诶诶…为啥没有通过涅…(众:你刚才不是还说pure c struct不能什么什么来着…)
CPP的member function由于包含在类中,经过改名之后我们已经不可能像普通函数那样容易,加一个extern C就搞定了…Linker在这个时候已经彻底无能为力了。那么俺们怎么办涅…
虽然不能在link的时候静态解析,难道run time也不行么?
我们自然想到了virtual函数。Virtual函数为了支持堕胎..不对,多态,在对象的第一个单元添加一个vptr指针,指向vtbl函数指针列表,在运行时搞定需要调用什么函数的问题。那么如果我们把函数声明成virtual…然后…

于是我们把类声明改成这样:
class apple
{
public:
virtual void speak()
{
cout << "hello apple" << endl;
}
};

而caller.c则变成这样:
#include <stdio.h>

void test();

struct apple_vtbl
{
void (* speak)();
};

struct apple
{
struct apple_vtbl * vptr;
};

int main()
{
struct apple sample;
sample.vptr->speak();
test();
}
这样看似不错诶…不过caller里面的apple不是一个空架子么…这样搞出来的instance和def里面的class apple有什么关系么= =设定vtbl和vptr的工作都是cpp的编译器偷偷完成的,在def的编译单元里…
所以我们肯定不能直接这样生成对象,要如此如此:
Def.cpp
apple * get_apple()
{
return static_cast<apple *>(new apple);
}

Caller.c

struct apple * get_apple();

int main()
{
struct apple * sample = get_apple();
sample->vptr->speak();
test();
}

哦也…过了的说…野心再次膨胀…那么添加一个成员变态…不…变量吧…
class apple
{
public:
apple():i(0){}
virtual void speak()
{
cout << i << endl;
}
private:
int i;
};
看似没啥问题哦~~然后使劲compile…哦也…也碰巧通过了的说…~~
然后…打印出了一串神秘数字…不是0,对,别看,不是0…为什么又错了呢…555555
…看看…函数指针的类型匹配么?不匹配么?匹配么?不匹配么…
突然想起来周胖胖老师上课说:对于cpp member function来说,它们使用特殊的this call。看看msdn…恩…
This is the default calling convention used by C++ member functions that do not use variable arguments. Under thiscall, the callee cleans the stack, which is impossible for vararg functions. Arguments are pushed on the stack from right to left, with the this pointer being passed via register ECX on the x86 architecture. The thiscall calling convention cannot be explicitly specified in a program, because thiscall is not a keyword.
vararg member functions use the __cdecl calling convention. All function arguments are pushed on the stack, with the this pointer placed on the stack last
Because this calling convention applies only to C++, there is no C name decoration scheme.
很小的一个豆腐干…
大致就是说,还有一个this指针指示了当前对象,这个指针要用来对成员变量进行引用,当我们call speak的时候,ecx中的this还没有到位,于是发生了如上惨剧…
不过,难道,可是…我们注定要内嵌汇编代码来完成这个函数靠么?不是吧…我汇编课都是睡觉的说…想想…想不出…使劲想…想起一点点…想起什么?这个…那个…如果把两个函数的调用方式统一一下呢?用那个什么__stdcall试试看来着…恩恩?
我把函数声明统一成了stdcall。于是推出最新版本:
virtual void __stdcall speak()
{
cout << i << endl;
}
而caller中的声明变成这样
struct apple;

struct apple_vtbl
{
void (__stdcall * speak)(struct apple * This);
};

struct apple
{
struct apple_vtbl * vptr;
};
__stdcall会把原来应该送进ecx的this改成函数的第一个参数…哦也…于是…编译试试看…
哦也…竟然对了…赫赫…
这样的类终究不是很完善,我想我们可以把实现和接口分离,这样更符合软件工程的思想。
Caller.c不变,我们更改def.cpp
class IApple
{
virtual void __stdcall speak() = 0;
};

class coclass:public IApple
{
public:
coclass():i(0){}
virtual void __stdcall speak()
{
cout << i << endl;
}
private:
int i;
};

coclass * get_apple()
{
return static_cast<coclass *>(new coclass);
}

恩恩…还是正确的…
再增加一个interface讷?
Def.cpp
class IDog
{
virtual void __stdcall walk() = 0;
};

class coclass:public IApple, public IDog
{
public:
coclass():i(0){}
virtual void __stdcall speak()
{
cout << i << endl;
}

virtual void __stdcall walk()
{
cout << "dog walk!!" << endl;
}
private:
int i;
};

Caller.c

struct apple_vtbl
{
void (__stdcall * speak)(struct apple * This);
void (__stdcall * walk)(struct apple * This);
};

int main()
{
struct apple * sample = get_apple();
sample->vptr->speak(sample);
sample->vptr->walk(sample);
test();
}

试试看…碰地一下崩溃了…汗…
想想为啥?
多继承下面还是一个vptr一个vtbl么…?是么…?不是么…?是么…?我在和你讨论这么问题么…是么…不是么…

看看Inside CPP Object Model?
恩…?
两个的说…没办法了…只能这样…
struct apple_vtbl
{
void (__stdcall * speak)(struct coclass * This);
};

struct dog_vtbl
{
void (__stdcall * walk)(struct coclass * This);
};

struct coclass
{
struct apple_vtbl * apple_vptr;
struct dog_vtbl * dog_vptr;
};

看看…没错…dog也walk了…
那么再测试一下之前的那个使用i的情况呢?
把walk也改成输出i的…和speak一样。再看看…神秘数字又出现了的说…
继续翻inside model…原来多继承的情况下this指针会调整…(你爸爸没和你说么)
逼我…
struct apple_vtbl
{
void (__stdcall * speak)(struct apple * This);
};

struct dog_vtbl
{
void (__stdcall * speak)(struct apple * This);
};

struct IApple
{
struct apple_vtbl * vptr;
};

struct IDog
{
struct dog_vtbl * vptr;
};

侧那…拆开来…
不过…每个接口的指针…还是不会调整啊…而且,get_apple应该返回什么类型呢…
其实cpp的赋值自动会调整的说,为何不像之前get_apple那样把麻烦事情交给有能耐的人呢?
于是我们,恩恩…
下狠手,一次解决…我们重头开始写(其实是我懒得写那么多了…恩…把上午的成果直接贴上去的说…别砸我…)
Def.cpp
#include <iostream>

using namespace std;

#define IID_UNKNOWN 0x00001
#define IID_IBASE 0x00002
#define IID_IBASE2 0x00003

#define CSTYLE_BEGIN extern "C" {
#define CSTYLE_END }

CSTYLE_BEGIN

class IUnknown
{
public:
virtual void __stdcall QueryInterface(int iid, void ** ppv) = 0;
};

class IBase
{
public:
virtual void __stdcall speak() = 0;
virtual int __stdcall count() = 0;
};

class IBase2
{
public:
virtual void __stdcall shout() = 0;
};

class coclass : public IUnknown, public IBase, public IBase2
{
public:
coclass()
{
m_i = 0;
}

void __stdcall QueryInterface(int iid, void ** ppv)
{
if(iid == IID_UNKNOWN)
{
*ppv = static_cast<IUnknown *>(this);
return;
}
if(iid == IID_IBASE)
{
*ppv = static_cast<IBase *>(this);
return;
}

if(iid == IID_IBASE2)
{
*ppv = static_cast<IBase2 *>(this);
return;
}
}

void __stdcall speak()
{
cout << "base::speak" << endl;
}

int __stdcall count()
{
return ++m_i;
}

virtual void __stdcall shout()
{
cout << "shout!!" << endl;
}
private:
int m_i;
};

IUnknown * get_object()
{
return static_cast<IUnknown *>(new coclass);
}

CSTYLE_END

Caller.c

#include <stdio.h>

#define CALLER(x) (x->vptr)

#define IID_UNKNOWN 0x00001
#define IID_IBASE 0x00002
#define IID_IBASE2 0x00003

struct IUnknown;
struct IBase;
struct IBase2;

struct IUnknownVtbl
{
void (__stdcall * QueryInterface)(struct IUnknown * This, int iid, void ** ppv);
};

struct IBaseVtbl
{
void (__stdcall * speak)(struct IBase * This);
int (__stdcall * count)(struct IBase * This);
};

struct IBase2Vtbl
{
void (__stdcall * shout)(struct IBase2 * This);
};

struct IUnknown
{
struct IUnknownVtbl * vptr;
};

struct IBase
{
struct IBaseVtbl * vptr;
};

struct IBase2
{
struct IBase2Vtbl * vptr;
};

struct IUnknown * get_object();

int main()
{
int i;
struct IUnknown * ptr = get_object();

struct IBase * ptr_base = NULL;
struct IBase2 * ptr_base2 = NULL;

CALLER(ptr)->QueryInterface(ptr, IID_IBASE, &ptr_base);
//ptr_base = ptr;
CALLER(ptr)->QueryInterface(ptr, IID_IBASE2, &ptr_base2);
//ptr_base2 = ptr;

CALLER(ptr_base)->speak(ptr_base);
for(i = 0 ; i < 10; i++)
{
printf("%d/n", CALLER(ptr_base)->count(ptr_base));
}

CALLER(ptr_base2)->shout(ptr_base2);

return 0;
}
(可以试试看不用QueryInteface而使用上面的代码里注释掉的直接赋值会怎么样)
有错的话..拍砖欢迎~~

2005-07-13 20:25:32 ilovesoup 编辑了这个贴子[/i]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐