ActiveX Control and It's Container

zhaozj2021-02-08  203

ActiveX control and its container

1.Com foundation

2.ActiveX control and implementation

3.ActiveX control container and implementation

4. Summary

1.Com foundation

COM is a component development technology that is actually a standard for software development methods compatible on a binary layer. COM technology is

Technologies with specific programming languages, as long as they support COM development development tools can be used to develop, and they

In binary compatibility requirements are implemented by various development tools, most of them are implemented by the compiler.

The foundation concept of COM has the following components, 1) Definition and implementation of the interface, 2) IUNKNOWN interface, 3) GUID (COM

There are still many concepts involved, and specific information can be referred to. The following is simple to introduce them.

1). Definition and implementation of the interface

An interface is actually a set of functions that define specific functions, these definitions have no specific implementation.

The righteous class is like a pure emptive definition in C , which defines the return type, parameter number, and function of the interface function. The COM component is

Rely on these interfaces to communicate with each other. A simple example is as follows. (MFC provides us with a lot of convenient macros to define interfaces, and

And in general, we use IDL or ODL to define interfaces instead of using the following form).

Interface iStack: iunknown {

Virtual void pop (int * pvalue) = 0;

Virtual void push (int value) = 0;

}

The above defined is a simple interface iStack. It defines two methods and describes the return type of these two methods.

(void), parameter and type, both functions are implemented with pure virtual functions, do not need these two functions in the file defined

Specific implementation. Under normal circumstances, the implementation of the interface can be done by inheritance of the interface, but a plurality of interfaces have been implemented in one component.

In case of the MFC uses a nested subclass implementation method, and details can be found in other documents.

2) .iunknown interface

In the above example, iStack is inherited from a interface called IUNKNOWN, then what IUNKNOWN interface is one?

What about the appearance? Requirements in the COM specification, any COM component must implement the IUnknown interface, the main thing for the IunkNown interface

It is used to maintain a reference count to maintain the COM component and query the interface implemented by the COM component. Let's take a look at the IunkNown interface.

definition.

Interface iunknown {

Virtual Void QueryInterface (Refiid Riid, Void ** PPVObject) = 0;

Virtual HRESULT AddRef () = 0;

Virtual hResult release () = 0;

}

In the above IUNKNOWN interface, AddRef and Release are used to maintain the reference count. Because a COM component can

For multiple application services, if there is no appropriate mechanism to maintain the survival of the COM component, then when a COM group is used

At the end of the application, this component will also be released simultaneously, then other applications that use this component will appear

The accessible components do not exist. So the COM subsystem is to solve this problem with reference counting, when an application requires

When a component, it increases the reference count of this component. When this application ends, it reduces the reference for this component.

Number, when a reference number is 0, the COM subsystem releases this component.

The QueryInterface method is used to query a way to be implemented in the COM component, because each interface is

There is an ID that can uniquely identify itself, called IID, by passing this IID, we can query if an interface is

COM component implementation, if the component implements the interface, we can use the second parameter of the queryinterface method to return it.

The value is used to use this interface.

3) .GUID

It is mentioned above, each interface is made by a unique identifier of its own ID, IID, and there is also a C class that implements an interface.

Id, called CLSID, in Ole Automation, a technology called type library is widely used, and a type library contains all type information in a COM component, including the interface, enumeration type, interface Method, and interface parameters, etc.

Information, the same type of library is also used in a numerous COM technology in numerous COM technology in numerous COM technology.

A type of COM component, and the COM component is classified, and each class has a category ID, CACID, actually I

They can use this CACID to list all the controls in the system (CACID_Control). All of these IDs mentioned above, actually

It is a type, guid. They are just a different typedef of GUID.

GUID is a number of features generated by a unique number of system time and network cards. This number

It guarantees its uniqueness in time and space. So some of the interfaces and related concepts use the GUID to distinguish, not the use

Their names.

2.ActiveX control and implementation

The earliest prototype of the ActiveX control should be the VBX control that occurs in VB, because the 16-bit structure of the VBX control does not adapt to 32-bit

The requirements of the operating system, then the OCX control is born, the OCX control is a 32-bit self-contained simple application, which is actually a group.

Complete the specified functional function collection. It is actually another way of DLL. OCX control can have its own interface, or no

There is an interface, it has an attribute, method, and an OCX control can trigger a type of event to notify the container.

Changes or changes or events of some external state, implementing an OCX control must implement a series of established interfaces, this

Make the OCX controls a bit large and redundant, because some controls only need to implement part of these interfaces, and for Internet

For this, implementing these excess interfaces undoubtedly increase the volume of the control. So in the 1996 PDC conference, Microsoft puts forward it.

The concept of Activate Internet, and some technologies are renamed to ActiveX technology, and the ActiveX control is in the original OCX control.

The part is born through the cuts of the interface to be implemented, and now you can be called it as long as a COM component implements the iUnknown interface.

ActiveX control. So it can be said that an ActiveX control is a simple IUNKNOWN interface and supports self-registration.

COM components.

However, the control to achieve an IUNKNOWN interface is obviously not actual, so the real ActiveX control is still to be implemented.

Some of the interfaces defined by the original OCX control are used to interact with its containers. The following is a truly explanation.

The implementation of the ActiveX control. In addition to the iUnkown interface, an ActiveX control generally implements a part of the following interface.

IoleObject, IoleinplaceObject, IoleinplaceActiveObject, IOLECONTROL,

IdataObject, iViewObject2, Idispatch, IconnectionPointContainer, ProviderclassInfo

[2], IperPropertyBrowsing, IPERSISTREAM,

IPersistStreaminit, IPersistMemory, IPERSISTStorage,

IPERSISTMONIKER, IPERSISTPROPERTYBAG, IOLCACHE [2], IExternalConnection, IrunNableObject, IclassFactory implementation requires MSDN.

An ActiveX control usually has some properties and events. The properties of the control are generally implemented through the IDispatch interface.

When defining the corresponding control properties, there is a value called DISPID, which is used to be called by other container that uses the control.

Used during properties, because they must call the corresponding properties through the IdisPatch interface. Idispach's method invoke is a key way to call the attribute of the response, but this method is not using the property when calling the properties of the control. name

Word, but is called the ID value of Dispid. In general, a control usually has its own type library, and the container gets the corresponding properties and methods and events list, and obtains their DISPID. , Then you can pass the Invoke method

Operate them.

An ActiveX control generally has three properties, stock properties, environmental properties (Ambient

Property, custom property. Inherent attributes are attributes with most ActiveX controls, such as before

Views, fonts, etc., environmental properties are some properties provided by the control in the container, such as LocaleID, usermode.

These attributes have a fixed DISPID value, which can get the values ​​of these properties through the GetAmbientxxxx method. Custom

Attribute is a control to implement some of its specific features, some properties defined, in the container, these properties can pass class

Type library is obtained by handling the call to the IDispatch interface.

The control of the control is a message or notification triggered by the control. If a control supports the event, it must be implemented

IconnectionPointContainer and iConnectionPoint interface, then the control defines your own interface, this interface

By declaring with the DISPINTERFACE, the Idispatch interface must be used when the container performs an event response to the control.

The method is processed, and the Dispid that is passed according to Invoke calls we can know which event is triggered, according to other

Information, we can process this event.

Let's take a brief introduction to how to use MFC to develop ActiveX controls. First we use AppWizard to generate

The framework of the ActiveX control, actually this framework is already a complete control, and this control has been implemented with the help of the wizard.

Part of the ActiveX control to be implemented, as part of the interface, like the basic support for the event, the support of attributes. We

You can add our own functionality to this control with the help of this framework, add attribute methods and events in this control.

ClassWizard provides a large amount of convenient operation in this regard, providing a pair in ClassWizard's Acitvex Automation page.

The addition of the properties event method for the ActiveX control.

For an ActiveX control, you need to figure out which you want to complete in the control, which is to be implemented in the container. That

If you need the control, you should consider the property or method, and you need a container to complete the parameters to pass events.

The trigger is transmitted to the container and is implemented in the container.

In addition, a more practical problem is what your control will look like. The relatively simple method is when ClassWizard

The fixed control will inherit from that class, thereby having the appearance of this class. But this method is not flexible enough. If you want to customize the appearance of the control, then the most

Good way is your own hand drawn control, or by adding some controls to add some controls in the control. You can be on ONDRAW

(CDC * PDC, Const CRECT & RCBOUNDS, Const CRECT & RCINVALID) Draw control. This function is responsible for control

Draw, where PDC is the environmental equipment used by the current system, RCBounds is the RECT range of the current control, you can use it to locate. Draw

The control is still relatively simple, but the premise is that you have to know the Windows's drawing mechanism. Mainly use CBRUSH, CDC, CFONT, etc.

The basic drawing class of the MFC.

In fact, for ActiveX Control, the programming of it can be used as a variety of MFCs like a general program.

Class, but a lot of classes will have to create, so you have to master the positioning. Mainly, master the redraw and refreshing of all kinds of subclasses

Timing and method.

About the creation of the property sheet, the property table allows the control to display its various properties to show and edit. Properties are usually dialogue

Implementation of the frame. You can change some of the properties of some controls here. This is enough for most controls.

Let's take a look at what the VC's AppWinzard Control has done for us. You use VC AppWinzard Control you

A ActiveX Control can quickly generate two interfaces here: one is used to be responsible for attributes and methods.

The other is to be responsible for the event. This control can be run in the container, but it does nothing. And its appearance is also very simple. Let us re-draw its appearance, which is completed in OnDraw.

/ / Set the current font and retain the original font

CFont * PoldFont;

PoldFont = SELECTFONTOBJECT (PDC, M_Customfont);

// Get all current colors. The TranslateColor is to convert OLE_COLOR to ColorRef.

ColorRef TextBkcolor = :: getSyscolor (color_btnface);

ColorRef textForecolor = this-> TRANSLATECOLOR (this-> getforecolor ());

ColorRef EdgeBkcolor = :: getSyscolor (Color_3dface);

ColorRef EdgeForeColor = :: getSyscolor (Color_3dface);

ColorRef oldbkcolor = pdc-> setbkcolor (TextBkcolor);

ColorRef OldforeColor = PDC-> SetTextColor (TextForeColor);

IF (m_brush.m_hobject = null)

M_brush.createsolidbrush (TextBkcolor);

CBRUSH * POLDBRUSH = PDC-> SelectObject (& m_brush);

PDC-> Rectangle (& RCBounds);

CSIZE OSIZE = PDC-> GetTextExtent (m_cstrcaption);

m_size = iSize;

PDC-> ExtTextout (rcbounds.right-osize.cx) / 2, (rcbounds.bottom-osize.cy) / 2, ETO_CLIPPED & BRVBARETO_OPAQUE, RCBOUNDs,

M_CSTRCAPTION, M_CSTRCAPTION.GETLENGTH (), NULL);

Uint borderStyle = Edge_Raised;

Uint borderflags = bf_rect;

// Sea Box

PDC-> SetBkcolor (EdgeBkcolor);

PDC-> SetTextColor (EdgeForeColor);

PDC-> DrawEdge (LPCRect) RcBounds, BorderStyle, Borderflags;

// Restore settings

PDC-> SetBkcolor (Oldbkcolor);

PDC-> SetTextColor (OldForeColor);

PDC-> SELECTOBJECT (POLDFONT);

PDC-> SELECTOBJECT (POLDBRUSH);

The above code will draw a better look for the control, of course, you can change until you are satisfied. You can ask yourself later.

To add some properties and methods and write relative implementation. Most definitions are maintained by Class Winzard, so you can

Add them easily.

Some suggestions: During the preparation of the property page of the control, you need to connect the standard control on the property page to the properties of the ActiveX control.

Series, this time when you dynamically change the value of the standard control, the properties of the ActiveX control will change. But if you use other

Method To dynamically change the value of the standard control on the property page, the properties of the ActiveX control will not change.

The reason is very simple, the attribute of the ActiveX control does not know that he has changed, so he did not accept the value from the standard control. This pass

The process is done in dd_p functions in DDATAEXChange (). Because you manually change the value of standard controls, you need to make

With setModified () to notify the properties of the ActiveX control changing, the DD_P function will be valid. In addition, the data type in the properties of the ActiveX control has some OLE_XXX types, these types are actually some LONG types, and Colecontrol

Some functions are used to convert them. Try not to use forced conversion during the type conversion process, which may bring some unexpected

Error, encourage the use of buffer mechanisms.

There are still many things about the control section, you can see the MFC or other documentation.

3.ActiveX control container and implementation

The container of the ActiveX control is actually the client of the ActiveX control, which uses the various features provided by the ActiveX control. But

It also provides some properties and other features for controls, making the controls to interact and operate more control. ActiveX

The container of the control is actually an OLE container, and then becomes ActiveX control after implementing the corresponding interface to support the ActiveX control.

Containers. In addition to IUNKNOWN, the container program needs to use part of the following interface: IoleInplaceFrame,

IoleinplaceuiWindow, IoleClientSite, IoleinPlace, IadviseSink, Iolecontrolsite,

IOLCONTROLSITE, IDISPATCH, IPROPERYTNOTIFYSINK, ISRAGE, IOLCONTAINER interface

Please refer to MSDN for a definition.

There is a good example in the example of the MFC, which is the tool ActiveX Control Test included in the VC.

The following examples explain the implementation of an ActiveX control container and the processing of certain issues.

In this example, the wizard of the VC is used to generate an application with Container support, which has one in the generated class.

Used to package each class embedded in the OLE object in the section, is generally called XXXCNTRITEM, in this example is renamed

CTestContainer98Item. Create every ActiveX control through this class to directly generate, this class maintained

ActiveX control Some properties features. And this class supports serialization so that we can save the properties of the control by serialization.

State and other information.

1). Dynamically create controls.

This should be an ActiveX control container most important task. In order to manage controls in the container, first it must be dynamic

Create a control. Because each COM component has a unique ID, CLSID, ActiveX control is no exception, but for the system

There are hundreds of COM objects, how do we determine which one is an ActiveX control? In the basic of COM, we mentioned it faster.

Positioning COM components and load it, the COM subsystem implements a different CAM component to categorize a variety of different COM

Components, the CACID of the ActiveX control is CACID_Control, so we can find all registrations in the system through this information.

Controls, in general, we represent all these controls by generating a list. The following is rewritten

CINSERTCONTROLDLG :: RefreshcontrolList () function

Carray M_AIMPLEMENTEDCATEGORIES;

Clistbox m_lbcontrols;

ICATINFORMATIONPTR M_PCATINFO;

Clist M_LControls;

Void cinsertcontroldlg :: refreshcontrollist ()

{

Bool bdone;

HRESULT HRESULT;

IenumGuidptr penum;

Ulong nimplementcategories;

Catid * pcatidImpl;

CLSID CLSID;

LPolestr pszname;

CString strname;

Ulong iCategory;

IIITEM;

Position PosControl;

Cstring strserverpath; cstring strstring;

// First, empty the list box, and populate PCATIDIMPL with M_AIMPLEMENTCATEGORIES, as a M_PCatinfo function

Enumclassedofcategories' second parameters to get the enumerator of CLSID

M_LbControls.resetContent ();

NIMPLEMENTCATEGORIES = M_AIMPLEMENTCATEGORIES.GETSIZE ();

IF (nimplementcategories == 0)

{

NIMPLEMENTCATEGORIES = (ULONG) -1;

PcatidImpl = NULL;

}

Else

{

/ / Assign memory for PcAtidImpl, pass M_AIMPLEMENTCATEGORIES data to PCATIDIMPL

PcAtidImpl = (CACID *) _ Alloca (NimplementCategories * Sizeof (catid);

Icategory = 0; ictegory

{

PcAtidImpl] = m_aimplementcategories [ictegory];

}

}

// Get the enumerator of CLSID

HRESULT = m_pcatinfo-> Enumclassesofcategories (NimplementCategories, PcatidImpl,

0, NULL, & PENUM;

Failed (HRESULT))

Return;

/ / Then enumerate all ActiveX Control's CLSID by enumerator, and obtain the corresponding user type name, join the list box

in.

BDONE = false;

While (! bdone)

{

HRESULT = Penum-> Next (1, & clsid, null); // get the next ActiveX Control's CLSID

IF (HRESULT == S_OK)

{

pszname = null;

HRESULT = OLEGGTUSERTYPE (CLSID, UserClasStype_full, & pszname); // Get the corresponding user

type name

if (succeeded))

{

Strname = pszname;

Cotaskmemfree (pszname);

pszname = null;

IIITEM = M_LBControls.addstring (Strname);

Poscontrol = m_lcontrols.addtail (CLSID);

M_LbControls.SetItemDataPtr (IIIM, POSCONTROL);

}

}

Else

{

BDONE = TRUE;

}

}

OnControlsselchange ();

}

The above function demonstrates how to extract ActiveX controls from numerous COM components and add them to a list box while

Leave their CLSID. Where M_PCatinfo called CreateInstance in InitDialog to create its own instance.

After our CLSID of an ActiveX control, we can use the CocreateInstanse function to generate it.

Example of the control. As mentioned above, every ActiveX control has a packaging class, and we actually

By this packaging class, let's take a look at it, create a control code in this packaging class (deleted some not very heavy

The code is required.

Bool CactiveXcontainercntritem :: CREATECONTROL (REFCLSID CLSID) {

Iunknown * punknown;

/ / 1. Create an instance of the control, initialize some status of the control in the steps below

HRESULT HRESULT = CocreateInstance (CLSID, NULL, CLSCTX_INPROC & BrVBARCLSCTX_SERVER, IID_IUNKNOWN, (VOID **) & PUNKNOWN

Failed (HRESULT))

Return False;

// 2. Request the IoleObject interface in the control,

HRESULT = punknown-> queryinterface (IID_IOLOleObject, (void **) & m_lpObject);

Failed (HRESULT))

{

Punknown-> Release ();

Return False;

}

Punknown-> Release ();

Cstring strusertype;

GetUsertype (userclasstype_short, strusertype);

// 3. Create a unique name to maintain the uniqueness of each control instance

GetDocument () -> CREATEUNIQUEITEMNAME (this, strusertype, m_strdisplayname);

// 4. Some basic information of the initialization control.

INITControlinfo ();

Bool bquickactivate = false;

// 5. If the control supports the iQUickActivate interface, use the iQUickActive interface to activate the control

BQUickActivate = QuickActivate ();

IF (! bquickactivate)

{

// 6. If the control does not support the iQUickActiveX interface, set the control of the control through the IoleObject interface.

M_LPObject-> getMiscstatus (DVASPECT_CONTENT, & M_DWMISCSTATUS);

IF (m_dwmiscstatus & olemisc_setclientsitefirst)

HRESULT = M_LPObject-> setClientSite (getClientSite ());

Failed (HRESULT))

Trace0 ("Can't setClientSite for the control / n");

}

if (succeeded))

{

// 7. Controls that support iQUickActivate interfaces must use the following steps.

IPERSISTREAMINITPTR PPERSISTREAMINIT;

IPERSISTORAGEPTR PPERSISTORAGE;

PPERSISTREAMINIT = M_LPObject;

IF (PPERSISTREAMINIT! = NULL)

{

HRESULT = PPERSISTREAMINIT-> INITNEW ();

IF (HRESULT == E_NOTIMPL)

HRESULT = S_OK;

}

Else

{

PPERSISTORAGE = M_LPOBJECT;

IF (PPERSISTORAGE! = NULL)

{

HRESULT = PPERSISTORAGE-> INITNEW (M_LPStorage);

}

Else

{

HRESULT = S_OK;

}

}

}

Return FinishCreate (HRESULT); // 8 Sets here event processing and property processing information}

Here are some of the content mentioned above,

Note 2 The IoleObject interface is used to prepare some of the following settings, because this interface is going to make many places

Use, so save it in a member variable.

Note 3 is used to distinguish a number of instances, this method is implemented by the document class, which is based on the name of the control and a number

To maintain examples of the same control.

Note 4 is used to initialize some basic information of the control, which is to put the properties and events of the control by reading the control type library.

In their respective lists, it is preferred to respond to attribute changes and events of the control.

Note 5 is for the QuickActivate () method, the QuickActivate () method first requests iQuickActivate

Interface, if the control does not support this interface, return false if the control supports the interface, initialize two structures QAcontainer

And QACONTROL, then use these two structures to call the QuickActivate method of the iQuickActivate interface,

The iQuickActivate interface is designed to improve the loading speed of the ActiveX control. In the call of the iQuickActivate interface

After the QuickActivate () method, iPersist * :: init and ipersist * :: initnew method must be called, the control should

A connection between its connection points and the receiver of the container is established in the QuickActivate method.

IPERSIST * :: init and ipersist * :: initnew, then these connections will not take effect.

The controls here have been established, but this is not involved in content related to the container. Tell the specific container

Now. We can see in the example, the program's document class inherits from Coledocument, in the documentation in Coledocument to us

Now, as an interface that must be implemented by the container, IOLCONTAINER, we can pass GetStartPosition in the program

(), GetNextItem () and other methods to use this interface, the main role of this interface is used to traverse controls in the container or other

OLE object. In addition, there are some interfaces that must be implemented. It is actually implemented in the MFC. We are in general as long as you use

These packaged functions are OK, which mainly tells some questions related to the properties and event processing of the control, in

ActiveX controls and implementations We mention that the properties and events of the controls are generally implemented through iDispatch, in Test

In Container, we can see a code used to implement interface maps.

Begin_INTERFACE_MAP (CTestContainer98Item, ColeClientItem)

Interface_PART (CTestContainer98Item, IID_IServiceProvider, ServiceProvider)

Interface_part (Ctestcontainer98Item, IID_IPROPERTYNOTISINK, PropertyNotifyInsink)

Interface_part (Ctestcontainer98Item, IID_IDISPATCH, AmbientProperties)

Interface_part (Ctestcontainer98Item, IID_IOLECONTROLSITE, OLECONTROLSITE

// Interface_part (CtestContainer98Item, IID_IOLINPLACESITEEX, OleinPlaceSiteWindowless)

// Interface_part (CTestContainer98Item, IID_IOLINPLESITEWINDOWLESS, OleinPlaceSiteWindowless)

END_INTERFACE_MAP ()

We can see that six interfaces appear in the above interface map, but two entrances are commented. Below we are

One explains these interfaces:

The first iServiceProvider is mainly used here to provide the IBIDHOST interface. In fact, when a container is implemented

Waiting, this interface is not necessary.

The second iPropertynNotifySink is a receiver used to achieve changes in the attribute change of the control. If you want your container to be

The corresponding notification is obtained when the attribute changes of the controls are to implement this interface. You can in this interface on the ONCHANGE method.

Get the corresponding DISPID of the corresponding adapted property, with this DISPID, you can further control some of the property characteristics of the control

.

The third interface is used to provide environmental properties for the control. The function of providing environmental attributes for the control is from the IDispatch interface.

Now, each environmental property has a specific DISPID, so when the control is called the GetAmbientxxx method, the control will request

The container provides the implementation of the corresponding attribute, which is implemented by the IDispatch interface.

The fourth interface is the IOLCONTROLSITE. The main role of this interface is to provide some Site objects in the container to embedded

The management of the controls. Implementing this interface is optional.

In the above interface map, we did not see the interface map of the processing of the control's event, in Test Container

In the code we can see the following code.

Begin_Interface_part (EventHandler, Idispatch)

StdMethod (GetidsofNames) (Refiid IID, LPolestr * Ppsznames, uint nNames, LCID

LCID, DISPID * PDISPIDS;

STDMETHOD (GETTYPEINFO) (Uint ITYPEINFO, LCID LCID, ITYPEINFO ** PPTYPEINFO);

STDMETHOD (UINT * PNInfocount);

Stdmethod (Invoke) (Dispid Dispidmember, Refiid IID, LCID LCID, Word WFLAGS,

Dispparams * pdpparams, Variant * Pvarresult, Excepinfo * PEXCEPTIONINFO, UINT *

Piarger);

END_INTERFACE_PART (EVENTHANDLER)

Obviously this code is used to handle events, but why don't you do it in the map of the interface? If you view

When the CTestContainer98item class, you will find a method called getInterfaceHook (), this method has one

The parameter PV of type const void *, this parameter is actually a pointer of the IID type, see the following code:

PIID = (const IID *) PV;

IF (* PIID == m_infoevents.getiid ())

{

Return (& m_xeventhandler);

}

Now we know how the control is handled, the getInterfaceHook () method is a method of ccmdtarget, but

It has no documentation in MSDN. The mapping relationship of several other interfaces is also achieved in this method.

Here we can understand some of the interfaces and related issues that need to implement a container that activeX controls.

The MFC class library has made us a lot of work, and they have implemented some interfaces that must be implemented as control containers, so that we are

There is a good starting point when developing such applications.

4. Summary

What is mentioned above is just some basic concepts and simple implementations, develop an ActiveX control or its container needs

A lot of knowledge and technology, because COM itself is a very large technical specification, which involves a lot of knowledge, and these

It is often the basis for other COM-based technology.

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

New Post(0)