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

今天看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 编辑了这个贴子
在自媒体领域,内容生产效率与作品专业水准日益成为从业者的核心关切。近期推出的Coze工作流集成方案,为内容生产者构建了一套系统化、模块化的创作支持体系。该方案通过预先设计的流程模块,贯穿选题构思、素材整理、文本撰写、视觉编排及渠道分发的完整周期,显著增强了自媒体工作的规范性与产出速率。 经过多轮实践验证,这些标准化流程不仅精简了操作步骤,减少了机械性任务的比重,还借助统一的操作框架有效控制了人为失误。由此,创作者得以将主要资源集中于内容创新与深度拓展,而非消耗于日常执行事务。具体而言,在选题环节,系统依据实时舆情数据与受众偏好模型生成热点建议,辅助快速定位创作方向;在编辑阶段,则提供多套经过验证的版式方案与视觉组件,保障内容呈现兼具美学价值与阅读流畅性。 分发推广模块同样经过周密设计,整合了跨平台传播策略与效果监测工具,涵盖社交网络运营、搜索排序优化、定向推送等多重手段,旨在帮助内容突破单一渠道局限,实现更广泛的受众触达。 该集成方案在提供成熟模板的同时,保留了充分的定制空间,允许用户根据自身创作特性与阶段目标调整流程细节。这种“框架统一、细节可变”的设计哲学,兼顾了行业通用标准与个体工作习惯,提升了工具在不同应用场景中的适应性。 从行业视角观察,此方案的问世恰逢其时,回应了自媒体专业化进程中对于流程优化工具的迫切需求。其价值不仅体现在即时的效率提升,更在于构建了一个可持续迭代的创作支持生态。通过持续吸纳用户反馈与行业趋势,系统将不断演进,助力从业者保持与行业发展同步,实现创作质量与运营效能的双重进阶。 总体而言,这一工作流集成方案的引入,标志着自媒体创作方法向系统化、精细化方向的重要转变。它在提升作业效率的同时,通过结构化的工作方法强化了内容产出的专业度与可持续性,为从业者的职业化发展提供了坚实的方法论基础。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值