您的位置:首页 > 其它

深入分析基于VCL派生的ActiveX控件的实现原理及应用

2015-11-22 22:21 585 查看
深入分析[b]基于VCL派生的ActiveX控件的实现原理及应用[/b]

前言
这篇文章虽然是以VCL为题,但却是基于BCB的,也就是说是在VCL基础上使用ATL实现的ActiveX的原理分析,如果你是Delphi程序员,这篇文章可能不适合你,不过作者如果有时间会再写一篇“Delphi版的深入分析”,本篇文章比较深入的分析了VCL实现ActiveX控件的原理、事件机制、属性页和ActiveX控件编写的相关知识。希望大家已经掌握了VCL编写元件(Component)的知识,COM原理及ATL/模板的相关知识,因为作者不会对文章中探讨的相关知识做详细介绍,所以你可能因为缺乏相应知识而遇到一些困难,不过作者会尽量用简单的语言来阐述一切。另,作者本来水平有限,在写这篇文章的时候仅是参考了帮助文档、分析了VCL源代码再加上一些猜测,如果有任何理解错误敬请大家指教,好我们开始吧!
从向导开始

为了有分析的对象,我们就从最基本的开始,由向导从TButton派生一个TButtonX的ActiveX控件(以下出现TButtonX都是指ActiveX控件),注意在向导中选中“产生关于对话框”的选项,这样生成的TButtonX会有一个关于对话框。现在我们得到了ButtonImpl,ButtonXContrl_TLB这两个主要的单元,所有的奥秘也在这里,我们进入看看。
在ButtonImpl.h文件中,有这样的申明:
class ATL_NO_VTABLE TButtonXImpl:

VCLCONTROL_IMPL(TButtonXImpl, ButtonX, TButton, IButtonX, DIID_IButtonXEvents)

{



}

这个TButtonXImpl实现了TButtonX,一般情况下针对ActiveX的工作都可以在这个单元内完成,不过这个类看起来还是很奇怪:首先ATL_NO_VTABLE是个什么东西?这是一个宏,经过豫编译处理后,这个宏最终替换为__declspec(novtable),而这又是一个编译器指示字,用于指示编译器不要产生vtable(准确的说,是不初始化vtable指针,这样连接器可以排除那些需要vtable才能调用的函数,比如虚函数),这又为什么?

我们知道COM是语言无关的,但是vtable的机制不是所有的语言都有,比如VB等,而我们编写ActiveX控件肯定是要拿到这些开发工具上使用的,所以为了使得C++开发的COM元件可以在VB下使用,我们不能使用vtable机制,而且ActiveX还要是自动化对象,这在后面再讨论。
还有一个宏VCLCONTROL_IMPL,这个宏是关键,它隐藏了VCL实现ActiveX控件的全部奥秘,看来必须分析它才能拨开全部迷雾:
VCLCONTROL_IMPL 宏,封装了ActiveX控件继承的基类,展开后:
#define VCLCONTROL_IMPL(cppClass, CoClass, VclClass, intf, EventID) /

public TVclControlImpl<cppClass, VclClass, &CLSID_##CoClass, /

&IID_##intf, &EventID, LIBID_OF_##CoClass>,/

public IDispatchImpl<intf, &IID_##intf, LIBID_OF_##CoClass>, /

public TEvents_##CoClass<cppClass>

cppClass是C++实现的类的名字,

CoClass是ActiveX的类名,

VclClass是ActiveX控件继承的VCL基类的名字,

intf是ActiveX控件实现的IDispatch接口,

EventID是ActiveX控件事件接口的标示符。
TVclControlImpl它封装了VclClass指定的VCL类,通过它的窗口管理和消息处理机制,使得它可以工作在ActiveX的宿主环境下。TVclControlImpl实现了标准的ActiveX控件需要的接口,因为它间接继承自CComObjectRootEx和CComCoClass,使得它可以工作在C++ Builder的基与ATL的COM应用程序中。
IDispatchImpl,它实现了IDispatch接口,所以从这个类派生的子类的属性和方法可以被自动化(Automation)操作。上面说了ActiveX必须是自动化对象,而自动化对象必须继承自IDispatch接口,这里正好说明这一点。
TEvents_##CoClass(或者TEvents_CoClassName),提供了VCL控件的事件触发机制,IDE会根据创建的VCL控件自动产生这个类,通过这个类,ActiveX控件可以在事件触发的时候调用相应的方法来处理这些事件。
注意上面的##的编译器指示字,它是用来连接2个宏参数的,比如TEvents_##CoClass会被替换为TEvents_ButtonX,这也是一个类,不过是用IDE自动产生的,用于支持事件机制。
而上面展开代码的关键在于TVclControlImpl这个类,我们再看看它:
template <class T, // User class implementing Control

class TVCL, // Underlying VCL type used in One-Step Conversion

const CLSID* pclsid, // Class ID of Control

const IID* piid, // Primary interface of Control

const IID* peventsid, // Event (outgoing) interface of Control

const GUID* plibid> // GUID of TypeLibrary

class ATL_NO_VTABLE TVclControlImpl:

public CComObjectRootEx<CComObjectThreadModel>,

public CComCoClass<T, pclsid>,

public TVclComControl<T, TVCL>,

public IProvideClassInfo2Impl<pclsid, peventsid, plibid>,

public IPersistStorageImpl<T>,

public IPersistStreamInitImpl<T>,

public IQuickActivateImpl<T>,

public IOleControlImpl<T>,

public IOleObjectImpl<T>,

public IOleInPlaceActiveObjectImpl<T>,

public IViewObjectExImpl<T>,

public IOleInPlaceObjectWindowlessImpl<T>,

public IDataObjectImpl<T>,

public ISpecifyPropertyPagesImpl<T>,

public IConnectionPointContainerImpl<T>,

public IPropertyNotifySinkCP<T, CComDynamicUnkArray>,

public ISupportErrorInfo,

public ISimpleFrameSiteImpl<T>

{



}

可以看到,这个类实现了所有ActiveX控件必要实现的接口,除此之外,这个类也是VCL和ATL转换的关键,他有很多关键的方法,比如:
HRESULT OnDraw(ATL_DRAWINFO& di)

{

try

{

if (m_VclCtl)

m_VclCtl->PaintTo(di.hdcDraw, di.prcBounds->left, di.prcBounds->top);

}

catch (Exception& e)

{

return (static_cast<T*>(this))->Error(e.Message.c_str());

}

return S_OK;

}

这个方法可以把VCL元件的界面画在ActiveX宿主窗体上。
经过层层拨丝,我们现在终于搞明白了TButtonXImpl的实现框架,但ActiveX运作的原理和如何同VCL交互的还是不清楚,好,我们现在再来看看,TButtonXImpl的实现代码:
void __fastcall ClickEvent(TObject *Sender);

void __fastcall KeyPressEvent(TObject *Sender, char &Key);

public:

TVclControlImpl

void InitializeControl()

{

m_VclCtl->OnClick = ClickEvent;

m_VclCtl->OnKeyPress = KeyPressEvent;

}

BEGIN_COM_MAP(TButtonXImpl)

VCL_CONTROL_COM_INTERFACE_ENTRIES(IButtonX)

END_COM_MAP()

DECLARE_VCL_CONTROL_PERSISTENCE(TButtonXImpl, TButton);

DECLARE_ACTIVEXCONTROL_REGISTRY("ButtonXControl.ButtonX", 1);

protected:

STDMETHOD(_set_Font(IFontDisp** Value));

STDMETHOD(AboutBox());

STDMETHOD(DrawTextBiDiModeFlagsReadingOnly(long* Value));

...

又是很多的宏,不过作者不打算介绍了,他们在BCB生成的代码注释(这里被删除)里解释的很清楚了,大家可以自己看,我就提示一点,很多朋友问:如何改变ActiveX控件的图标?更改这个

DECLARE_ACTIVEXCONTROL_REGISTRY(“ButtonXControl.ButtonX”, 1);

宏的参数就可以了,比如你已经将图标资源(Bitmap),加入工程,并且这个资源ID为2,则你可以这样更改:

DECLARE_ACTIVEXCONTROL_REGISTRY(“ButtonXControl.ButtonX”, 2);

再看,在protected下有很多属性、方法的申明,在cpp文件中,这些申明也得到了实现,但问题在于为什么是保护类型的?这样ActiveX控件岂不是访问不到这些属性、方法?申明了又有什么用?

是否还记得TButtonXImpl继承了IButtonX接口呢?我们现在要到那里去看看,为此我们要分析一下ButtonXContrl_TLB单元,这个单元文件是由IDE维护的,一般情况是不需要理会这个文件的内容,Borland也不建议你更改这个文件,不过今天我们必须要跨入禁区了,于是就有了IButtonX的实现代码:
interface IButtonX : public IDispatch

{

public:

virtual HRESULT STDMETHODCALLTYPE get_Cancel(VARIANT_BOOL* Value/*[out,retval]*/) = 0; // [1]

virtual HRESULT STDMETHODCALLTYPE set_Cancel(VARIANT_BOOL Value/*[in]*/) = 0; // [1]

virtual HRESULT STDMETHODCALLTYPE get_Caption(BSTR* Value/*[out,retval]*/) = 0; // [-518]

...

#if !defined(__TLB_NO_INTERFACE_WRAPPERS)
VARIANT_BOOL __fastcall get_Cancel(void)

{

VARIANT_BOOL Value;

OLECHECK(this->get_Cancel((VARIANT_BOOL*)&Value));

return Value;

}

...

__property VARIANT_BOOL Cancel = {read = get_Cancel, write = set_Cancel};

__property BSTR Caption = {read = get_Caption};

__property VARIANT_BOOL Default = {read = get_Default, write = set_Default};

__property short DragCursor = {read = get_DragCursor, write = set_DragCursor};

...

}

可以看到IButtonX同样继承自IDispatch接口,所以这也是一个自动化的接口,而且终于有了public,所以那些接口方法和属性被公开了,我们不难得出这样一张类的布局图:

另外请大家注意:
VARIANT_BOOL __fastcall get_Default(void)

{

VARIANT_BOOL Value;

OLECHECK(this->get_Default((VARIANT_BOOL*)&Value));

return Value;

}

上面的实现代码,OLECHECK用于检查函数的执行结果,如果有错误,那么还有一个机会去处理错误。

方法和属性都有了,对于一个ActiveX控件还差事件,没有事件支持的ActiveX控件就像一个没有发条的钟是不会动的,下面,我们再来看一下VCL是如何实现ActiveX控件的事件机制的。
事件机制
还是上面的代码:
void __fastcall ClickEvent(TObject *Sender);

void __fastcall KeyPressEvent(TObject *Sender, char &Key);

public:

TVclControlImpl

void InitializeControl()

{

m_VclCtl->OnClick = ClickEvent;

m_VclCtl->OnKeyPress = KeyPressEvent;

}

...

看起来像是事件的处理代码,啊?好像?有没有搞错?没有搞错,确实是好像,而且是表面的


m_VclCtl->OnClick = ClickEvent;

m_VclCtl->OnKeyPress = KeyPressEvent;

是标准的VCL消息处理函数机制,m_VclCtl通过模板参数最终对应于相应的VCL原类,这样m_VclCtl的OnClick事件的处理就会转交给ClickEvent函数,而OnKeyPress事件的处理也就交给了KeyPressEvent函数处理,有VCL经验的人,都能猜到ClickEvent和KeyPressEvent函数是如何实现的,例如:

void __fastcall TButtonXImpl::KeyPressEvent(TObject *Sender, char &Key)

{

short TempKey;

TempKey = (short)Key;

Fire_OnKeyPress(&TempKey);

Key = (short)TempKey;

}

又跳转了,忽略Sender参数,然后把Key又传递给了Fire_OnKeyPress函数处理,为了保证VCL KeyPress事件的结构,Key参数先被保存到TempKey,然后传递,最后返回Key参数,注意:TempKey参数可能在ActiveX事件处理中被修改,这也符合Visual Basic的KeyPress事件结构。

不过问题在于Fire_OnKeyPress函数是从哪里来的?要搞清楚这个问题,我们还要看看前面那个复杂的宏定义:
#define VCLCONTROL_IMPL(cppClass, CoClass, VclClass, intf, EventID) /

public TVclControlImpl<cppClass, VclClass, &CLSID_##CoClass, /

&IID_##intf, &EventID, LIBID_OF_##CoClass>,/

public IDispatchImpl<intf, &IID_##intf, LIBID_OF_##CoClass>, /

public TEvents_##CoClass<cppClass>

其中,TEvents_##CoClass(或者TEvents_CoClassName),提供了VCL控件的事件触发机制,IDE会根据创建的VCL控件自动产生这个类,通过这个类,ActiveX控件可以在事件触发的时候调用相应的方法来处理这些事件。
注意上面的##的编译器指示字,它是用来连接2个宏参数的,比如TEvents_##CoClass会被替换为TEvents_ButtonX,这也是一个类,不过是用IDE自动产生的,用于支持事件机制。

所以所有的事件奥秘都应该隐藏在这个TEvents_ButtonX类里,如果你够大胆的话,你可以猜测那个Fire_OnKeyPress函数就在这个类里?再次跨越禁区,我们得到代码:
template <class T>

class TEvents_ButtonX : public IConnectionPointImpl<T,

&DIID_IButtonXEvents,

CComUnkArray<CONNECTIONPOINT_ARRAY_SIZE> >

{

public:

void Fire_OnClick(void);

void Fire_OnKeyPress(short* Key);

void Fire_OnMouseMove(int Button, int X, int Y);

protected:

IButtonXEventsDisp m_EventIntfObj;

};

看到了确实如此,我们再来看看Fire_OnKeyPress是如何实现的?
template <class T> void

TEvents_ButtonX<T>::Fire_OnKeyPress(short* Key)

{

T * pT = (T*)this;

pT->Lock();

IUnknown ** pp = m_vec.begin();

while (pp < m_vec.end())

{

if (*pp != NULL)

{

m_EventIntfObj.Attach(*pp);

m_EventIntfObj.OnKeyPress(Key);

m_EventIntfObj.Attach(0);

}

pp++;

}

pT->Unlock();

}

剔除不必要的多线程访问互斥代码、对控件数组事件的支持代码,关键在于:

m_EventIntfObj.OnKeyPress(Key);

看来我们又要跳转了,最后来到:
template <class T> void __fastcall

IButtonXEventsDispT<T>::OnKeyPress(short* Key/*[in,out]*/)

{

_TDispID _dispid(/* OnKeyPress */ DISPID(8));

TAutoArgs<1> _args;

_args[1] = Key /*[VT_I2:1]*/;

OleProcedure(_dispid, _args);

}

至此,如果有ATL/COM知识的人,都可以看出来这是一套标准的OLE方法调用机制,如果你还想跟踪下去,你会发现它就是调用IDispatch接口的Invoke方法来负责方法、属性的调用的,不过这里还可以注意一下:

_TDispID _dispid(/* OnKeyPress */ DISPID(8));

这里DISPID(8)是接口方法的标识符,这个值来自于你设计IButtonXEvents接口时定义的ID号,所以Invoke方法会唯一的定位到这个方法来完成事件机制的最后一步:调用客户代码-也就是你在VB中提供的事件的代码。

于是我们又得到了这样一幅消息、事件流向图:

VCL就是这样一步一步实现ActiveX控件的事件机制的,可以看出来,他的实现还是挺麻烦的,不过考虑到COM原理本来的复杂性,这样的实现复杂度还是可以接受的。

简单的测试

由控件向导生成的TButtonX代码,不需要任何改动,直接编译就会产生一个TButtonX ActiveX控件,我们现在测试一下,点Register Active Server菜单,注册这个控件,然后在开启Visual Basic开发环境,加入刚才注册的控件,发现它确实是按照我们的设计工作的,注意:如果你在开始创建这个控件的时候,选择了生成About对话框的选项,那么还有一个About属性用于显示关于对话框。

那么这个关于对话框又是怎么回事?代码为我们展示这点:
void ShowButtonXAbout(void)

{

TButtonXAbout* Form;

Form = new TButtonXAbout(NULL);

try

{

Form->ShowModal();

}

catch(...)

{

Form->Free();

return;

}

Form->Free();

}

在TButtonImpl的AboutBox函数中调用了上面的函数来显示对话框,不过有一点作者也不太清楚,就是:

Form->Free();

本来按照Borland的说法,在BCB中不推荐使用Free来释放内存,而应该使用delete这个关键字,但这里为什么这样使用?不过作者做了测试,使用delete也是可以的,没有发现什么问题,所以作者猜测,这里可能是Borland没有更新那个代码向导以适应BCB的开发(可能本来是为Delphi设计的,而Borland只是简单的做了一下Delphi到BCB的转换)。

最后需要说明的是AboutBox函数的DISPID必须是-552,这样ActiveX会把这个函数作为About来对待,其实DISPID的设置还是有强制性,很多标准属性必须是特定的DISPID,这些DISPID都是负值,有兴趣的朋友可以看看MSDN或者COM原理的书籍。

重用 继承?

上面的TButtonX控件简单的通过继承VCL的TButton的实现了一个按钮的AcitiveX控件,如果是其他的VCL能不能同样这样简单的继承就可以方便的生成ActiveX控件呢?在问答前,我们先做一个试验,把刚才这个TButtonX放在Visual Basic开发环境中,然后使用Spy++这样工具(这里作者使用作者自己开发的MySpy,可以到http://siney.yeah.net下载)看看他的类名:

发现它的类名并不是ActiveX的控件的TButtonX,而是原来VCL的TButton,这能说明什么?这从一个侧面说明了,在BCB下我们开发ActiveX控件其实就是设计相应的VCL控件,而在最后把他再封装成为ActiveX控件,那么到底什么样的VCL控件都可以封装成为ActiveX控件呢?通常来说只要从TWinControl继承的VCL元件都可以封装成为ActiveX。而这样VCL元件具有如下特征:

? 可以获得焦点

? 可以包含其他控件(仅是具有这样能力,不代表一定具备)

? 拥有窗口句柄

还记得这段代码吗:
HRESULT OnDraw(ATL_DRAWINFO& di)

{

try

{

if (m_VclCtl)

m_VclCtl->PaintTo(di.hdcDraw, di.prcBounds->left, di.prcBounds->top);

}

catch (Exception& e)

{

return (static_cast<T*>(this))->Error(e.Message.c_str());

}

return S_OK;

}

前面说这是BCB实现ActiveX机制的关键类TVclControlImpl所具有的代码,用于把控件画在ActiveX宿主窗体上,而这个方法就是来源于TWinControl,所以ActiveX控件的必须继承自这个类(当然如果是自己实现了,就另当别论),这样,很容易想到的是像TLabel这样的VCL控件是无法实现为ActiveX控件的。

那么是不是从TWinControl继承的VCL元件都可以被封装为ActiveX呢?这也不一定,如果你已经把相应的的unit添加进入工程或者已经把它安装了,则这个VCL元件可能不会出现在那个DropDown 列表里,还有就是这个类没有用RegisterNonActiveX函数注册,这个函数专门用来设置那些类不能被封装为ActiveX,这个函数很复杂:

extern PACKAGE void __fastcall RegisterNonActiveX(System::TMetaClass*,

const * ComponentClasses,

const int ComponentClasses_Size,

TActiveXRegType AxRegType);

具体的使用方法可以参考帮助。在CSDN上和Borland新闻组作者也看到过网友询问“为什么我的控件不能出现在AcitveX的生成向导里”,希望下次看过这篇文章的朋友下次可以解决这个问题。

再完善一些

再回到刚才Visual Basic的开发环境,我们来看看到底那些属性和方法被表露了:

而同样的TButton在BCB中却具有非常多的属性,为什么呢?开始作者认为所有的VCL元件都是表露这些基本的属性和方法,但后来作者又做了一个试验,就是简单的封装了TEdit后发现他表露其他的更多属性和方法,参考了一下帮助文档,发现原来有如下规则:

数据感知属性不表露。
任何与自动化不兼容的类型定义不表露。
可以表露在VCL中未发布的属性,但这样做不保证持久性(persist)。

如果VCL的属性或方法不符合上述规则,我们就需要自己实现相应ActiveX代码来表露他们;相反如果符合上述规定,而你又不想表露给最终用户,你可以在Type Library Editor中删除它们,刷新代码后再删除单元文件中相应的生成代码。

这里需要强调的是:在BCB中设计ActiveX控件在很大程度上是先设计对应的VCL后再封装为ActiveX,而不是像VC那样直接开发ActiveX(其实BCB也可以像VC那样开发ActiveX,毕竟都是使用ATL嘛),这样设计不管是从难易程度还是调试都是非常轻松的。

知道了原理,我们现在要做的是在这个半成品的TButtonX中加入新的事件和属性页,使它看起来更像一个专业的ActiveX控件,对于属性和方法,因为编写这些代码非常简单,为了节省篇幅,这里就不实际添加属性和方法了。

从TButton封装得到的AcitiveX缺少了一个重要的事件,OnMouseMove,下面我们就写代码来表露这个事件,根据我们上面讲述的原理,完成这部分很容易,首先就是在Type Library Editor里的事件支持接口添加相应方法,如图:

刷新后IDE自动产生相应代码,在Impl单元文件中,加入VCL的OnMouseMove消息处理的转移代码,如下黑体部分:
class ATL_NO_VTABLE TButtonXImpl:

VCLCONTROL_IMPL(TButtonXImpl, ButtonX, TButton, IButtonX, DIID_IButtonXEvents)

{

void __fastcall ClickEvent(TObject *Sender);

void __fastcall KeyPressEvent(TObject *Sender, char &Key);

void __fastcall MouseMoveEvent(TObject *Sender, TShiftState Shift, int X,

[b]int Y);

public:
[/b]
void InitializeControl()

{

m_VclCtl->OnClick = ClickEvent;

m_VclCtl->OnKeyPress = KeyPressEvent;

m_VclCtl->OnMouseMove = MouseMoveEvent;

}

这里的m_VclCtl其实就是TButton,他是通过模板参数替换的,所以通过上面的代码TButton的OnMouseMove消息的处理过程转向了MouseMoveEvent,现在我们最后的工作就是编写MouseMoveEvent这个函数处理OnMouseMove消息:
void __fastcall TButtonXImpl::MouseMoveEvent(TObject *Sender,

TShiftState Shift, int X, int Y)

{

int ss=0;

if(Shift.Contains(ssLeft))

ss=1;

else if(Shift.Contains(ssRight))

ss=2;

Fire_OnMouseMove(ss,X,Y);

}

忽略Sender参数后,再次转发消息流,Fire_OnMouseMove这个函数是由IDE自动产生的,我们直接调用就可以了,它的代码如下:
template <class T> void

TEvents_ButtonX<T>::Fire_OnMouseMove(int Button, int X, int Y)

{

T * pT = (T*)this;

pT->Lock();

IUnknown ** pp = m_vec.begin();

while (pp < m_vec.end())

{

if (*pp != NULL)

{

m_EventIntfObj.Attach(*pp);

m_EventIntfObj.OnMouseMove(Button, X, Y);

m_EventIntfObj.Attach(0);

}

pp++;

}

pT->Unlock();

}

可以看到这部分代码与缺省的Fire_OnClick是一致的,这样添加OnMouseMove事件支持的代码就完成了。

接下来是添加一个属性页,由于VCL的封装,使得开发设计属性页变得非常简单,首先生成一个新的Property Page,这样BCB会为我们产生一个Form,这个Form与普通的Win32开发中Form的最大区别在于它继承自TPropertyPage,所以它有一些独有的方法是我们在设计需要注意的,为了简单起见,属性页里只简单地更改、反馈Caption属性,在实际开发中复杂的属性页是类似的。

属性页与ActiveX交互就是通过2个函数来进行的,而这两个函数都是来自于TPropertyPage类,在缺省生成的Property Page Form里已经加入了这个两个函数的缺省代码,我们要做就是完成这2个函数:

UpdatePropertyPage(void),在打开属性页时,系统会调用这个函数,你可以在这个函数里添加代码用以反映ActiveX的属性在属性页里的显示。

UpdateObject(void),在应用属性时,系统会调用这个函数,你可以把属性页中改变的数据应用到实际的ActiveX控件中。

按照BCB的帮助这两个函数实现起来非常简单,比如UpdateObject(void),就只需要如下代码:

void __fastcall TPropertyPage1::UpdateObject(void)

{

OleObject.OlePropertySet<WideString>("EditMask", WideString(InputMast->Text).Copy());

}

就可以把ActiveX控件的属性更改为属性页中设置的数据,但在作者的计算机上(BCB6+sp4),上面的代码怎么都无法通过编译,无奈之下,作者只好使用原始的方法,代码如下:
void __fastcall TpageNormal::UpdatePropertyPage(void)

{

// Update your controls from OleObjects

IDispatch* ctrl=OleObject;

CComPtr<IButtonX> btnctrl;

ctrl->QueryInterface<IButtonX>(&btnctrl);

edtcaption->Text=String(btnctrl->get_Caption());

}

//---------------------------------------------------------------------------

void __fastcall TpageNormal::UpdateObject(void)

{

// Update OleObjects from your controls

IDispatch* ctrl=OleObject;

CComPtr<IButtonX> btnctrl;

ctrl->QueryInterface<IButtonX>(&btnctrl);

btnctrl->set_Caption(WideString(edtcaption->Text));

}

对于没有ATL、COM知识的读者上面的代码可能比较难懂,建议找些相关书籍熟悉一下。

通过上面的代码,ActiveX控件和属性页之间就可以完美的交互了,最后在ButtonImpl.h文件中加入如下宏映:

BEGIN_PROPERTY_MAP(TButtonXImpl)

PROP_PAGE(CLSID_pageNormal)

END_PROPERTY_MAP()

关于这些宏的说明和原理因篇幅关系这里就不讨论了,BCB的帮助和源码注释里都写的很清楚,感兴趣的朋友可以自己研究一下。至于上面的CLSID_pageNormal是生成属性页时,IDE自动为改属性页生成的ClassID。

这样一个比较完善的ActiveX控件就写完了,是不是非常简单,详细代码可以到作者网站下载。

写在最后

由于篇幅关系,本来很多内容可以展开详细讨论,但作者都省略了,本文就当抛砖引玉,感兴趣的读者可以再深入研究。作者想再次强调的是,不管开发ActiveX控件还是Active Form,最好的方法都是封装(或者转换)为ActiveX,而不是一切从头来,比如你有一个工程,想以ActiveForm的形式用在Web上,那么最好的方法是拿出单独的Form然后转换为ActiveForm,这不需要多复杂的代码,这也是Borland新闻组上专家给的建议。

由于编写ActiveX控件的调试复杂性,所以保证代码质量非常重要,除此之外就是善于利用一些工具来帮助调试,比如Visual Basic、ActiveX Control Test Container等工具,如果有机会作者会写一些实际开发中可能遇到的调试问题和经验。

参考文献:

Borland C++ Builder5 Help Document
Borland VCL Source and Comment
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: