In-depth analysis of the realization principle and application of ActiveX control based on VCL derived

zhaozj2021-02-16  49

In-depth analysis of the realization principle and application of ActiveX control based on VCL derived

Aweay

You can reprint, copy, but you must join the author's signature Aweay. If you are used for commercial purposes, you must agree.

Foreword

Although this article is a VCL, it is BCB based on the BCB, that is, the use of ATL's principle analysis based on the VCL, if you are a Delphi programmer, this article may not suit you, but If the author has time to write a "Delphi version in-depth analysis", this article is more in-depth analysis of the principles, event mechanisms, attribute pages, and ActiveX controls written by VCL implementation. I hope that everyone has already mastered the knowledge of the VCL write component, COM principle and the relevant knowledge of the ATL / template, because the author will not introduce the relevant knowledge discussed in the article, so you may have some encounter due to lack of corresponding knowledge Difficulties, but the author will try to explain everything in a simple language. In addition, the author is limited, just referring to the help documentation when writing this article, analyzing the VCL source code plus some guess, if there is any understanding, please advise, we start!

From the wizard

In order to have an object of analysis, we will start from TButton from TButton from TButton from TButton, which is an ActiveX Control from TButton, and pay attention to the "Generation Dialog Box" option in the wizard. The generated TBUTTONX will have a dialog. Now we get two main units of ButtonImpl, ButtonxContrl_TLB, all the mystery is here, we entered the look.

In the ButtonImpl.h file, there is such a declaration:

class ATL_NO_VTABLE TButtonXImpl: VCLCONTROL_IMPL (TButtonXImpl, ButtonX, TButton, IButtonX, DIID_IButtonXEvents) {...} This TButtonXImpl realized TButtonX, work for ActiveX under normal circumstances can be completed within this unit, but the class still looks very strange: First, What is ATL_NO_VTABLE? This is a macro. After the compilation processing, this macro finally replaces __Declspec (NOVTable), and this is another compiler indicator, which is used to indicate whether the compiler does not generate VTABLE (accurately, is not initializing the VTABLE pointer) This allows the connector to exclude functions that need VTABLE to call, such as virtual functions), why? We know that COM is language-independent, but the mechanism of VTABLE is not all languages, such as VB, etc., and we write ActiveX controls must be used on these development tools, so in order to make COM components developed by C can be Used under VB, we can't use the VTable mechanism, and ActiveX is also an automated object, which is discussed later.

There is also a macro vclControl_impl. This macro is the key. It hides the full mystery of the VCL to implement the ActiveX control. It seems that it must be analyzed to open the full fog:

VclControl_impl macro, encapsulated the base class inherited by the ActiveX control:

#define VCLCONTROL_IMPL (cppClass, CoClass, VclClass, intf, EventID) / public TVclControlImpl , / public IDispatchImpl , / public tevents _ ## Coclass CPPClass is the name of the class C implementation class, CoClass is the name of ActiveX, VclClass is the name of the VCL base class inherited by the ActiveX control, intf is an ActiveX control implementation The idispatch interface, EventId is the monogram of the ActiveX control event interface. TVClControlIMPL enables VCLCLASS specified by VCL classes, through its window management and message processing mechanism, so that it can work in the host environment of ActiveX. TVClControlIMPL implements the standards required by the standard ActiveX control, as it indirectly inherits from CComObjectroTex and CCOMCOCLASS, allowing it to work in the C Builder's base and ATL's COM application.

IDispatchImpl, which implements the IDispatch interface, so the properties and methods of subclasses derived from this class can be automated (Automation). The above said ActiveX must be an automated object, and the automation object must inherit from the Idispatch interface, which is just explained here.

TEVENTS _ ## Coclass (or tevents_coclassname) provides an event trigger mechanism for the VCL control, and IDE automatically generates this class based on the created VCL control, through this class, ActiveX control can call the corresponding method when the event is triggered to handle these events. .

Note the above ## compile the word, which is used to connect 2 macro parameters, such as TEVENTS _ ## Coclass will be replaced with tevents_buttonx, which is also a class, but it is automatically generated by IDE for support events. mechanism.

And the key to the code is the class of TvClControlImpl, let's take a look at it:

template // GUID of TypeLibraryclass ATL_NO_VTABLE TVclControlImpl: public CComObjectRootEx , public CComCoClass , public TVclComControl , public IProvideClassInfo2Impl , public IPersistStorageImpl , public IPersistStreamInitImpl , public IQuickActivateImpl , public IOleControlImpl , public IOleObjectImpl , public IOleInPlaceActiveObjectImpl , public IViewObjectExImpl , public IOleInPlaceObjectWindowlessImpl , public IDataObjectImpl , public ISpecifyPropertyPagesImpl , public IConnectionPointContainerImpl , public IPropertyNotifySinkCP , public ISupportErrorInfo, public ISimpleFrameSiteImpl {...} can see that this class implements all ActiveX controls necessary real In addition, this class is also the key to VCL and ATL conversion. He has many key methods, such as: 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 ) -> Error (E.MESSAGE.C_STR ());} Return S_OK; } This method can paint the interface of the VCL component on the ActiveX host form.

After layer, we now finally understand the implementation framework of TButtonxImpl, but the principle of ActiveX operation and how to interact with VCL is still unclear, well, let's take a look at the TButtonxImpl implementation code:

void __fastcall ClickEvent (TObject * Sender); void __fastcall KeyPressEvent (TObject * Sender, char & Key); public: TVclControlImplvoid 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) ... ... is also a lot of macros, but the author is not intended to introduce, they explain it very clearly in the code annotation of BCB (here), everyone can see, I will prompt, many friends ask : How to change the icon of the ActiveX control? Change this declare_activexControl_registry ("ButtonxControl.ButtonX", 1);

The macro parameters can be, for example, you have set the icon resource (Bitmap), join the project, and this resource ID is 2, then you can change:

DECLARE_ACTIVEXCONTROL_REGISTRY ("ButtonxControl.Buttonx", 2);

Looking again, there are many attributes under Protected, the declaration of the method, in the CPP file, these affirms are also achieved, but the problem is why is the protection type? Is this ActiveX control not access these properties, how do you? What is the use of it? Do I still remember TBUTTONXIMPL inherited the IBUTTONX interface? We are going to see there, to analyze the buttonxControl_tlb unit, this unit file is maintained by IDE, and the general situation does not need to pay the content of this file, Borland does not recommend you to change this file, but today we It must be across the penalty area, so there is IBUTTONX implementation code:

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] ... # i! Defined (__ TLB_NO_ITERFACE_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}; ...} can see IButtonX also inherits from the IDispatch interface, so that It is also an automated interface, and finally has public, so those interface methods and attributes are disclosed, and we don't really have such a layout:

Please pay attention to:

Variant_bool__fastcall get_default (void) {variant_bool value; olecheck (this-> get_default ((variant_bool *) & value)); Return Value;} The above implementation code, olecheck is used to check the function of the function, if there is an error, then A chance to deal with errors. Method and attributes have, for an ActiveX control, ActiveX control without event support is like a clock without a clockwork, below, let's take another way to see how the VCL is an event mechanism for ActiveX controls. of.

Event mechanism

Still the above code:

void __fastcall ClickEvent (TObject * Sender); void __fastcall KeyPressEvent (TObject * Sender, char & Key); public: TVclControlImplvoid InitializeControl () {m_VclCtl-> OnClick = ClickEvent; m_VclCtl-> OnKeyPress = KeyPressEvent;} ... looks like The processing code of the event, ah? like? Have you made a mistake? Didn't make a mistake, it is really like, and it is surface

: M_vclctl-> onclick = clickevent; m_vclctl-> onkeypress = keypress

It is a standard VCL message processing function mechanism, and M_VCLCTL finally corresponds to the corresponding VCL original class through the template parameter, so that the process of the M_VCLCTL's onclick event will be transferred to the ClickEvent function, and the processing of the onkeypress event is handed over to the keypressEvent function. People with VCL experience can guess how Clickevent and KeyPressEvent functions are implemented, for example:

void __fastcall TButtonXImpl :: KeyPressEvent (TObject * Sender, char & Key) {short TempKey; TempKey = (short) Key; Fire_OnKeyPress (& TempKey); Key = (short) TempKey;} and jump, and ignored Sender parameter, then the Key Also passed to the Fire_onkeyPress function, in order to ensure the structure of the VCL KeyPress event, the key parameter is saved to TEMPKEY, then passes, finally returns the key parameter, note: TempKey parameters may be modified in ActiveX event processing, which is also Visual Basic KeyPress event structure. However, the problem is where the fire_onkeypress function comes from? To figure out this question, we also have to see the complex macro definition in front:

#define VCLCONTROL_IMPL (cppClass, CoClass, VclClass, intf, EventID) / public TVclControlImpl , / public IDispatchImpl , / public tevents _ ## Coclass where TEVENTS _ ## Coclass (or tevents_coclassname) provides an event trigger mechanism for a VCL control, and IDE automatically generates this class based on the created VCL control. This class, the ActiveX control can call the corresponding method when the event is triggered to process these events.

Note the above ## compile the word, which is used to connect 2 macro parameters, such as TEVENTS _ ## Coclass will be replaced with tevents_buttonx, which is also a class, but it is automatically generated by IDE for support events. mechanism. So all the events should be hidden in this TEVENTS_BUTTONX class. If you are bold enough, you can guess the Fire_onkeyPress function in this class? Throughout the penalty area again, we get the code:

template class TEvents_ButtonX: public IConnectionPointImpl > {public: void Fire_OnClick (void); void Fire_OnKeyPress (short * Key); void Fire_OnMouseMove (int Button, int X, int Y); Protected: ibuttonxeventsdisp m_eventintfobj;}; I have seen this, let's take a look at how Fire_onkeyPress is implemented?

Template voidtevents_buttonx :: 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 ();} Remove unnecessary multi-thread access mutual exclusive code, the key to the support code for the control array event, the key is: m_eventintfobj.onkeypress (key); It seems that we have to jump, and finally come: Template void __fastcallibuttonxeventsdispt < T> :: onkey / * [in, out] * /) {_ tdispid _dispid (/ * onkeypress * / dispid (8)); tautoargs <1> _ARGS; _ARGS [1] = key / * [vt_i2 : 1] * /; oleprocedure;} To this, if there is an ATL / COM knowledge, you can see this is a standard OLE method call mechanism. If you still want to track, you will find It is the invoke method to call the IDispatch interface to be responsible for the method, the attribute call, but you can also pay attention to: _TDispid _dispid (/ * onkeypress * / display (8)); here Dispid (8) is the identifier of the interface method, This value comes from the ID number defined when you design the iButtonXevents interface, so the INVOKE method will be unique to locate the last step to complete the event mechanism: call customer code - the code you provide in VB. So we got such a message, the event flow map:

The VCL is the first step in step by step, it can be seen that his implementation is still troublesome, but considers the complexity of COM principle, such implementation complexity can be accepted.

Simple test

The TBUTTONX code generated by the control wizard does not require any changes. Direct compile will generate a TButtonX ActiveX control. We now test, click the Register Active Server menu, register this control, then open the Visual Basic development environment, join Just registered Controls, found that it is indeed based on our design, pay attention: If you start to create this control, select the option to generate the About dialog box, then there is an About property to display about the dialog. So what is the matter about the dialog? The code shows us this:

Void showbuttonxabout (void) {tbuttonxabout * form; form = new tbuttonXabout (null); trymodal ();} catch (...) {form-> free (); return;} form-> free );} The above function is called to display the dialog in the TButtonImpl's AboutBox function, but a little author is not clear, that is:

FORM-> Free ();

Originally in accordance with Borland, it is not recommended to use Free to release memory in BCB, but use Delete this keyword, but why is this use this? However, the author has tested it. It is also possible to use Delete. If you find any problems, the author speculates that this may not update that code wizard to adapt to the development of BCB (may be designed for Delphi, and Borland is just simple. The transition of Delphi to BCB). Finally, the Dispid of the AboutBox function must be -552 so that ActiveX will treat this function as About. In fact, the DISPID is still mandatory, and many standard properties must be specific DISPID, which is negative, Interested friends can look at MSDN or COM principles books. Re-use inheritance?

The above TButtonX control simply implemented a button's AcTIVEX control by inheriting the VCL TButton, if other VCLs can be the same, the same simple inheritance can easily generate an ActiveX control? Before the question and answer, let's make a trial, put the TButtonx just in the Visual Basic development environment, then use SPY such tools (here the author uses the author's own own Myss, you can go to http://siney.yeah.net download) Look at his name:

Discovering its class name is not TButtonx for ActiveX, but the original VCL TButton, what can this explain? This is illustrated from one side. We develop the ActiveX control under BCB is actually designing the corresponding VCL control, and in the end, it will be packaged into an ActiveX control, so what kind of VCL control can be encapsulated into an ActiveX control? Usually, as long as the VCL components inherited from TwinControl can be packaged into ActiveX. And this VCL component has the following characteristics: Can you get a focus? Can contain other controls (only such capabilities, does not represent each other)? Have the window handle Remember this code:

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 (this)) -> Error (E.MESSAGE.C_STR ());} Return S_OK;} The previous saying that this is the code of key TVCLControlIMPL to implement the ActiveX mechanism, which is used to draw the control in the ActiveX host form. On, and this method is to come from TWINCONTROL, so the ActiveX control must inherit from this class (of course, if it is true, it is another matter), so it is easy to think of the VCL control like TLABEL is unable to be implemented ActiveX control. So is it possible to be encapsulated from the VCL components inherited from TwinControl? This is not necessarily, if you have added the corresponding UNIT to the project or have already installed it, this VCL component may not appear in the DropDown list, and this class does not have the regisennonactivex function to register, this function Specially used to set out those classes that cannot be packaged for ActiveX, this function is complicated:

Extern package void __fastcall registernonactivex (System :: Tmetaclass *, Const * ComponentClasses, Const Int Componentclasses_size, TactiveXRegType AxRegType);

Specific methods can be used for reference. On the 9CBS and the Borland News Group Author also saw that the netizen asked "Why can't my control can appear in the ACITVEX's Generation Wizard." I hope that the next time I can solve this problem next time.

Improve some more

Go back to the development environment of Visual Basic, let's take a look at those properties and methods have been revealed: and the same TButton has a lot of properties in BCB, why? Start the author that all VCL components are exposed these basic properties and methods, but the author has made a test, which is a simple packaged TEDIT and found that he revealed other properties and methods, refer to help documentation, It is found that there is the following rules:

Data sensational properties are not exposed.

Any type definition that is not compatible with the automation is not exposed.

It can be exposed to the attributes not released in the VCL, but do not guarantee persistence (PERSIST).

If the attributes or methods of the VCL do not meet the above rules, we need to implement the corresponding ActiveX code to express them; After deleting the corresponding generated code in the unit file. Here, it is necessary to emphasize that the ActiveX control is designed to be largely designed to be able to design the corresponding VCL. Is it very easy to use ATL, such a design, whether it is from a difficulty or debugging. I know the principles, we have to do this is to join new events and property pages in this semi-finished TButtonx, making it more like a professional ActiveX control, for properties and methods, because writing these codes is very simple, in order to save The space, this is not actually added attributes and methods. ACITIVEX from TButton package lacks an important event, onMousemove, let's write code to express this event, according to the principles we described above, it is easy to complete this part, first in the Type Library Editor Add Corresponding method, as shown:

After refreshing the IDE automatically generates the corresponding code, in the IMPL unit file, add the transfer code of the VCL onMouseMove message processing, the following black body part:

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, int y); public:

void InitializeControl () {m_VclCtl-> OnClick = ClickEvent; m_VclCtl-> OnKeyPress = KeyPressEvent; m_VclCtl-> OnMouseMove = MouseMoveEvent;} m_VclCtl here TButton fact, he was replaced by the template parameters, the above code by OnMouseMove of TButton The process of the message turned to MousemoveEvent, and now our last job is to write MouseMoveEvent this function to process the ONMOUSEMOVE message:

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); After ignoring the sender parameter, forward the message stream again, Fire_onmouseMove This function is automatically generated by the IDE, we can call directly, its code is as follows:

Template voidtevents_buttonx :: fire_onmousemove (int button, int x, int y) {t * pt = (t *) THIS; PT-> Lock (); iUnknown ** pp = m_vec.begin () WHILE (PP unlock ();} You can see that this part of the code is consistent with the default fire_onclick, which is completed by adding the code supported by the OnMouseMove event. Next, add a property page, due to the VCL package, make the development design attribute page very simple, first generate a new Property page, so BCB will generate a form, this form is forms Form with ordinary Win32 development The biggest difference is that it inherits from tpropertyPage, so it has some unique way to note that we need to pay attention to it. For the sake of simplicity, the property page simply changed, feedback the Caption property, the complex property page in actual development is similar of. The attribute page and the ActiveX interaction are made through 2 functions, and these two functions are from the TPropertyPage class, and the default code of this two functions has been added in the default geneting Page Form. We must do That is to complete these two functions: UpdatePropertyPage (Void), when open the property page, you can add the code in this function to reflect the property of the property page in this function. UpdateObject (void), when applying properties, the system calls this function, you can apply data changed in the property page to the actual ActiveX control. According to the BCB's help, it is very simple, such as UpdateObject (Void), just need to code:

void __fastcall tpropertypage1 :: updateObject (void) {oleobject.olePropertySet ("EditMask", WideString (InputMast-> Text) .copy ());} You can change the properties of the ActiveX control to the data set in the property page. But on the author's computer (BCB6 SP4), how can the above code cannot be compiled, helpless, the author has to use the original method, the code is as follows:

void __fastcall TpageNormal :: UpdatePropertyPage (void) {// Update your controls from OleObjectsIDispatch * ctrl = OleObject; CComPtr btnctrl; ctrl-> QueryInterface (& btnctrl); edtcaption-> Text = String (btnctrl-> get_Caption ());} // ------------------------------------------- -------------------------------- Void __fastcall tpagenormal :: updateObject (void) {// update oleobjects from your controlsidispatch * Ctrl = oleObject; ccomptr btnctrl; ctrl-> queryinterface (& btnctrl); btnctrl-> set_caption (WideString (edtcaption-> text));} There is no ATL, COM knowledge readers may be more difficult Recommend, it is recommended to find some related books. By the above code, the ActiveX control and the property page can be perfectly interacting, and finally add the following macro in the ButtonImpl.h file:

Begin_Property_Map (TBUTTONXIMPL) PROP_PAGE (CLSID_PAGENORMAL) END_PROPERTY_MAP ()

The instructions and principles of these macros have not been discussed here. The BCB helps and source annotations are clear, interested friends can study themselves. As for the CLSID_PAGENORMAL above, the IDE automatically generates the ClassID to change the attribute page. Such a relatively complete ActiveX control is over, is it very simple, detailed code can be downloaded by the author website.

Written in the last

Due to the space relationship, the original content can be discussed in detail, but the author is omitted. This article will be in the case of the jade, and the readers of interest can be further studied. The author wants to emphasize again, whether developing ActiveX control or Active Form, the best way is a package (or conversion) is ActiveX, not everything from the head, such as you have an engineering, want to use ActiveForm in the form of Activeform on the Web So the best way is to take out a separate form and convert to ActiveForm, which does not require more complex code, which is also the recommendation given on the Borland news group. Since the debugging of ActiveX controls is written, it is very important to ensure that the quality of code is very important. In addition, it is good at using some tools to help debug, such as Visual Basic, ActiveX Control Test Container, etc. If there is an opportunity author will write some actual development Possible debugging issues and experiences can be encountered. Reference: Borland C Builder5 Help Document

Borland VCL Source and Comment

转载请注明原文地址:https://www.9cbs.com/read-25662.html

New Post(0)