`

使用MFC开发ActiveX(ocx)控件

阅读更多

作者:  中国电波传播研究所青岛分所 郎锐

时间:  2004-03-09

出处:  yesky

一、前言
二、建立工程框架
三、属性、方法以及事件的添加
四、实现属性表
五、在包容程序中使用ActiveX控件
六、小结

  前言

  ActiveX控件是一种实现了一系列特定接口而使其在使用和外观上更象一个控件的COM组件。ActiveX控件这种技术涉及到了几乎所有的COM和OLE的技术精华,如可链接对象、统一数据传输、OLE文档、属性页、永久存储以及OLE自动化等。

   ActiveX控件作为基本的界面单元,必须拥有自己的属性和方法以适合不同特点的程序和向包容器程序提供功能服务,其属性和方法均由自动化服务的 IDispatch接口来支持。除了属性和方法外,ActiveX控件还具有区别于自动化服务的一种特性--事件。事件指的是从控件发送给其包容程序的一 种通知。与窗口控件通过发送消息通知其拥有者类似,ActiveX控件是通过触发事件来通知其包容器的。事件的触发通常是通过控件包容器提供的 IDispatch接口来调用自动化对象的方法来实现的。在设计ActiveX控件时就应当考虑控件可能会发生哪些事件以及包容器程序将会对其中的哪些事 件感兴趣并将这些事件包含进来。与自动化服务不同,ActiveX控件的方法、属性和事件均有自定义(custom)和库存(stock)两种不同的类 型。自定义的方法和属性也就是是普通的自动化方法和属性,自定义事件则是自己选取名字和Dispatch ID的事件。而所谓的库存方法、属性和事件则是使用了ActiveX控件规定了名字和Dispatch ID的"标准"方法、属性和事件。

   ActiveX控件可以使COM组件从外观和使用上能与普通的窗口控件一样,而且还提供了类似于设置Windows标准控件属性的属性页,使其能够在包 容器程序的设计阶段对ActiveX控件的属性进行可视化设置。ActiveX控件提供的这些功能使得对其的使用将是非常方便的。本文下面即以MFC为工 具对ActiveX控件的开发进行介绍。

  建立工程框架

  通过"MFC ActiveX ControlWizard"向导可以非常容易的建立一个MFC ActiveX控件工程框架。按照默认的选项将建立如图1所示的工程结构:


图1 使用缺省选项建立的ActiveX控件工程结构

   其中,_DSample68和_DSample68Events这两个接口将为客户程序提供本控件的属性、方法以及可能响应的事件。全局函数 DllRegisterServer()和DllUnregisterServer()分别用于控件在注册表的注册和注销,一般不需要对其进行改动。

   应用程序类从COleControlModule继承。而COleControlModule有是从CWinApp派生,提供了初始化控件模块的功能。 CSample68PropPage的基类是COlePropertyPage,CDialog类的派生类,主要负责对属性页中对图形界面下用户控件属性 的显示。控件类CSample68Ctrl类是这几个类中比较重要的一个类,大部分实质性工作都在该类完成,其基类为COleControl,从CWnd 和CCmdTarget继承,因此能够为控件对象提供与MFC窗口对象相同的功能同时也提供了一系列事件触发函数和一个分发映射表,使ActiveX控件 能够同包容器程序有效地进行交互。该类的派生类将可以在满足特定的条件时向控件的包容器发送消息或是触发事件,以通知包容器程序在控件内有一些重要的事件 发生。分发映射表是其中很重要的一个部分,负责向包容器程序暴露控件提供的方法和属性。图2展示了COleControl类在控件与包容器通信中所起的作 用。可以看出,ActiveX控件与其包容器之间的所有通信过程都是由COleControl来完成的:


图2 COleControl在ActiveX控件与包容器通信中的作用

  控件类对基类COleControl的OnDraw()函数进行了重载,向导生成了如下缺省代码,其作用是在控件的客户区绘制一个椭圆。在编程过程中通常要对其进行替换:

 

 

void CSample68Ctrl::OnDraw(
CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)
{
 // TODO: Replace the following code with your own drawing code.
 pdc->FillRect(rcBounds, CBrush::FromHandle((HBRUSH)GetStockObject(WHITE_BRUSH)));
 pdc->Ellipse(rcBounds);
}



图3 插入ActiveX控件


图4 插入的待测试控件

   对向导生成的代码进行编译后,将产生扩展名为ocx的ActiveX控件。ActiveX控件并不能独立运行,只能在包容器程序中才能够运行。通常,为 了调试方便而多使用VC++附带的ActiveX Control Test Container工具以在测试阶段对ActiveX控件进行调试。在测试工具的客户区点击鼠标右键,并选中弹出菜单的"Insert New Control…"菜单项,将弹出图3所示的对话框,左侧的列表框中列出了当前系统中所有注册的ActiveX控件,选中要测试的控件并将其插入到测试程 序即可通过"Control"菜单下的各菜单项对控件的方法、属性以及事件等进行测试。在位于下方的分割视图中将跟踪显示出调试记录(参见图4)。

  属性、方法以及事件的添加


图5 属性的添加


图6 方法的添加

   对ActiveX控件属性、方法和事件的添加均有库存和自定义两种。其中对属性和方法的添加在MFC ClassWizard对话框的Automation页中通过按钮"Add Property…"和"Add Method…"弹出如图5和图6所示的添加属性和添加方法的对话框来完成。对于库存属性和方法,可以直接从External name组合框的下拉列表中选取,Implementation项将自动设置为Stock。对于自定义属性和方法的添加与在自动化对象中为接口添加属性和 方法的过程一样,ClassWizard将在.odl文件和控件类生成相应的代码,下面给出的是在控件类中实现的部分分发映射代码:

 

 

……
// Dispatch maps
//{{AFX_DISPATCH(CSample68Ctrl)
CString m_message;
afx_msg void OnMessageChanged();
afx_msg short GetXPos();
afx_msg void SetXPos(short nNewValue);
afx_msg short GetYPos();
afx_msg void SetYPos(short nNewValue);
afx_msg short MessageLen();
//}}AFX_DISPATCH
DECLARE_DISPATCH_MAP()
// Dispatch and event IDs
public:
enum {
//{{AFX_DISP_ID(CSample68Ctrl)
dispidMessage = 1L,
dispidXPos = 2L,
dispidYPos = 3L,
dispidMessageLen = 4L,
//}}AFX_DISP_ID
};
……
BEGIN_DISPATCH_MAP(CSample68Ctrl, COleControl)
//{{AFX_DISPATCH_MAP(CSample68Ctrl)
DISP_PROPERTY_NOTIFY(CSample68Ctrl, "Message", m_message, OnMessageChanged, VT_BSTR)
DISP_PROPERTY_EX(CSample68Ctrl, "XPos", GetXPos, SetXPos, VT_I2)
DISP_PROPERTY_EX(CSample68Ctrl, "YPos", GetYPos, SetYPos, VT_I2)
DISP_FUNCTION(CSample68Ctrl, "MessageLen", MessageLen, VT_I2, VTS_NONE)
DISP_STOCKPROP_BACKCOLOR()
DISP_STOCKPROP_CAPTION()
DISP_STOCKPROP_FORECOLOR()
//}}AFX_DISPATCH_MAP
END_DISPATCH_MAP()
……


  在这里共添加了一个自定义方法MessageLen()和三种库存属性BackColor、Caption和ForeColor(分别 表示控件的背景色、标题和前台色)、两个以Get/Set方式获取的自定义属性XPos、YPos和一个以成员变量方式实现的自定义属性Message。 这几个自定义属性分别表示要显示字符串的x、y坐标和要显示的内容。对于采取Get/Set方式获取的属性,应当在控件类中为其添加相应的成员函数,并修 改其Get、Set成员函数的实现过程:

 

short m_nYPos;
short m_nXPos;
……
short CSample68Ctrl::GetXPos()
{
 return m_nXPos;
}
void CSample68Ctrl::SetXPos(short nNewValue)
{
 m_nXPos = nNewValue;
 SetModifiedFlag();
}
short CSample68Ctrl::GetYPos()
{
 return m_nYPos;
}
void CSample68Ctrl::SetYPos(short nNewValue)
{
 m_nYPos = nNewValue;
 SetModifiedFlag();
}


  对于以成员变量方式创建的属性Message,向导还为其生成了一个消息响应函数:

 

void CSample68Ctrl::OnMessageChanged()
{
SetModifiedFlag();
}


  只要该属性的值被更改,OnMessageChanged()函数即会被调用。

  为了使上述属性设置如背景色、前景色等能够与控件实际联系起来,需要替换控件类OnDraw()函数中由向导生成的那部分代码。例如,下面这段代码即以前面添加的属性设置作为参数值,在控件中显示一串字符:

 

// 用背景色设置画刷
CBrush Brush(TranslateColor(GetBackColor()));
// 用前台色设置字体颜色
pdc->SetTextColor(TranslateColor(GetForeColor()));
// 绘制背景
pdc->FillRect(rcBounds, &Brush);
// 设置字体背景透明
pdc->SetBkMode(TRANSPARENT);
// 显示字符
pdc->TextOut(m_nXPos, m_nYPos, m_message);


  为了使属性设置更改后,其效果能够立即在控件上显示出来,应当在与属性设置相关的函数实现中调用InvalidateControl()以更新控件的显示。

   可以编译程序并在ActiveX Control Test Container工具中对其进行测试。在插入控件后,通过"Invoke Methods…"菜单项弹出如图7所示的对话框。在Method Name组合框中可以选择要测试的属性和方法。其中,对于属性的测试分别有ProgGet和ProgSet的说明以指出是对属性值的获取与设置。在 Parameter编辑框中输入要设置的参数及其对应的参数类型,点击SetValue按钮将把该参数值添加到参数列表框,最后点击Invoke按钮将在 控件应用设置的属性并执行指定的方法。对于有返回值的方法,其执行结果将在Return编辑框中显示。如果出现了异常操作,在Exception编辑框中 将会显示出相应的异常错误信息。图8给出了经过属性设置的控件界面。


图7 对属性、方法的测试


图8 设置了属性后的控件

   对于控件属性的添加,在MFC ClassWizard对话框的ActiveX Events页中通过"Add Event…"按钮弹出如图9所示的"Add Event"事件添加对话框。与方法、属性的添加类似,在External name组合框中可以输入要添加的自定义事件名称,也可以从下拉列表选择库存事件。Implementation项将根据所要添加的事件类型而自动设置 Stock或Custom选项。ActiveX控件将通过添加的事件来通知容器程序有特定的事件发生,库存事件多为键盘、鼠标事件,将由 COleControl自动进行处理。对于自定义事件,则只是在.odl文件和控件类中添加了事件映射表等必要的代码(代码附下),至于应当在何种条件下 触发该事件须由开发人员自行编写代码。


图9 事件的添加

 

dispinterface _DSample68Events
{
 properties:
  // Event interface has no properties
 methods:
  // NOTE - ClassWizard will maintain event information here.
  // Use extreme caution when editing this section.
  //{{AFX_ODL_EVENT(CSample68Ctrl)
  [id(1)] void MsgOut();
  //}}AFX_ODL_EVENT
};
……
// Event maps
//{{AFX_EVENT(CSample68Ctrl)
void FireMsgOut()
{FireEvent(eventidMsgOut,EVENT_PARAM(VTS_NONE));}
//}}AFX_EVENT
DECLARE_EVENT_MAP()
// Dispatch and event IDs
public:
enum {
 //{{AFX_DISP_ID(CSample68Ctrl)
 ……
 eventidMsgOut = 1L,
 //}}AFX_DISP_ID
};
……
BEGIN_EVENT_MAP(CSample68Ctrl, COleControl)
//{{AFX_EVENT_MAP(CSample68Ctrl)
EVENT_CUSTOM("MsgOut", FireMsgOut, VTS_NONE)
//}}AFX_EVENT_MAP
END_EVENT_MAP()


  上述代码添加了一个MsgOut的自定义事件,可以在通过调用FireMsgOut()来激发。下面对Message属性的 OnMessageChanged()消息响应函数进行修改,每当Message属性内容被更改都会调用该函数,在该函数中调用此前添加的 MessageLen()方法以确定更改后的Message属性的字符串长度,在长度大于10时调用FireMsgOut()触发MsgOut事件:

 

void CSample68Ctrl::OnMessageChanged()
{
 InvalidateControl();
 if (MessageLen() >= 10)
  FireMsgOut();
  SetModifiedFlag();
}



图10 选择要记录的事件

   在用ActiveX Control Test Container对刚添加的事件进行测试时,首先通过"Control"菜单下的"Logging…"菜单项弹出如图10所示的对话框,并 从"Events"属性页中选中要跟踪记录的事件。当通过Invoke Methods对话框设置Message属性的内容超过10个字符后,位于程序框架下方的分割视图将记录控件所触发的MsgOut事件(如图11所示)。


图11 对事件的测试

  实现属性表

  属性 表是ActiveX控件所特有的一种技术,可以在包容器程序处于设计阶段时为其提供一个可视化的人机交互界面,并可以通过其对控件的自定义属性和库存属性 进行设置。在用向导生成程序框架的同时即已经生成了一个空的用于管理自定义属性的属性页。在代码上通过控件类实现文件中的属性页ID表对其进行维护:

 

 

BEGIN_PROPPAGEIDS(CSample68Ctrl, 1)
PROPPAGEID(CSample68PropPage::guid)
END_PROPPAGEIDS(CSample68Ctrl)


  这里的CSample68PropPage类是从COlePropertyPage派生出来的,而COlePropertyPage的 基类又是CDialog,因此不难发现CSample68PropPage与通常的对话框类是比较相似的。可以象处理对话框一样在资源视图中为缺省的属性 页添加与自定义属性相关的交互用控件,并通过ClassWizard将这些控件与类成员变量建立绑定关系。但是有一点不同,就是在绑定成员变量时还要与控 件中的相应属性建立起对应关系。如图12所示,在Optional property name组合框中输入自定义属性名或是直接从下拉列表选择库存属性名,ClassWizard向导将在属性页类的DoDataExchange()函数中 添加控件、变量和属性的绑定代码:

 

void CSample68PropPage::DoDataExchange(CDataExchange* pDX)
{
 //{{AFX_DATA_MAP(CSample68PropPage)
 DDP_Text(pDX, IDC_MESSAGE, m_sMessage, _T("Message") );
 DDX_Text(pDX, IDC_MESSAGE, m_sMessage);
 DDP_Text(pDX, IDC_TITLE, m_sCaption, _T("Caption") );
 DDX_Text(pDX, IDC_TITLE, m_sCaption);
 DDP_Text(pDX, IDC_XPOS, m_nXPos, _T("XPos") );
 DDX_Text(pDX, IDC_XPOS, m_nXPos);
 DDP_Text(pDX, IDC_YPOS, m_nYPos, _T("YPos") );
 DDX_Text(pDX, IDC_YPOS, m_nYPos);
//}}AFX_DATA_MAP
DDP_PostProcessing(pDX);
}



图12 成员变量、控件与属性的绑定

   这里只是在向导生成的缺省属性页中实现了自定义属性的可视化设置。虽然也可以用相同的方法为库存属性进行设置,但是更多的还是采用添加库存属性页ID的 方法来直接使用库存属性页来对其进行维护。例如,对于库存属性BackColor和ForeColor,可以通过ID号为 CLSID_CcolorPropPage的库存属性页来进行设置,在将其添加到属性页ID表的同时一定要注意修改 BEGIN_PROPPAGEIDS()宏的属性页计数,否则将会引起系统的崩溃:

 

BEGIN_PROPPAGEIDS(CSample68Ctrl, 2)
PROPPAGEID(CSample68PropPage::guid)
PROPPAGEID(CLSID_CColorPropPage)
END_PROPPAGEIDS(CSample68Ctrl)


  继续在ActiveX Control Test Container中测试控件,将其插入后选择"Edit"菜单的"Properties…"菜单项,将弹出入图13所示的属性表。该属性表共有三个属性 页,其中第一个属性页为刚才编辑的自定义属性页,第二个属性页(如图14所示)即为CLSID_CcolorPropPage所指定的颜色属性页(为库存 属性页),最后一个属性页则是向导自动添加的扩展属性页。在属性表中设置了相应的属性后,点击"应用"按钮即可让控件使用新的属性。这与在"Invoke Methods"对话框中所完成的功能一样,但显然要方便的多。而且在包容器程序的设计阶段,也是通过该属性表来完成控件与客户的属性设置交互的。


图13 控件的属性表


图14 颜色属性页

   在包容程序中使用ActiveX控件

  对于ActiveX控件的包容器程序,并不需要象使用OLE文档服务器或ActiveX文档服务器对象那样编写特定的包容器程序框架,直接将控件添加到工程并在对话框上创建即可对其进行使用。

   通过"Project"菜单下的"Add To Project"菜单项弹出的"Components and Controls…"子菜单项打开一个"Components and Controls Gallery"对话框,进入到Registered ActiveX Controls目录下,选取前面创建的ActiveX控件,并将其添加到工程。向导将会在工程中添加一个关于此ActiveX控件的包装类,并 在"Controls"工具栏中添加一个表示此控件的图标。可以象使用其他的标准控件一样将其放置到对话框资源中,并修改其缺省属性。除此之外,还可以在 程序中通过对控件包装类成员函数的使用来动态更改控件的属性设置。例如,下面这段代码通过包装类对象m_ctrlTest在程序运行期间动态设置了控件的 XPos、YPos 以及Message属性:

 

 

// 更新显示
UpdateData();
// 动态更改控件的Message属性
m_ctrlTest.SetMessage(m_sInput);
// 设置显示坐标
m_ctrlTest.SetXPos(10);
m_ctrlTest.SetYPos(10);



图15 添加事件响应函数

   在资源视图中用鼠标右键点击放置于对话框上的ActiveX控件,并从弹出菜单中选择"Events…"菜单项,将弹出如图15所示的对话框,在左边的 列表框中显示了控件提供的事件,双击事件将在包容器程序中添加相应的事件处理函数和事件映射表,并可以在响应控件发出的事件后进行相应的处理:

 

BEGIN_EVENTSINK_MAP(CSample69Dlg, CDialog)
//{{AFX_EVENTSINK_MAP(CSample69Dlg)
ON_EVENT(CSample69Dlg, IDC_SAMPLE68CTRL1, 1 /* MsgOut */, OnMsgOutSample68ctrl1, VTS_NONE)
//}}AFX_EVENTSINK_MAP
END_EVENTSINK_MAP()
……
void CSample69Dlg::OnMsgOutSample68ctrl1()
{
 // 得到输入字符数
 int nNum = m_ctrlTest.MessageLen();
 // 回显信息
 m_sInput.Format("输入字符太多,共输入了%d个字符", nNum);
 // 显示信息
 UpdateData(FALSE);
}


  从上述对ActiveX控件的使用过程可以看出其与标准控件的使用并没有什么太大的区别,通过包装类使得在客户程序中对控件属性、方法的使用可以象使用普通MFC类一样简单。另外,在控件的包装类中还提供有Create()方法,使在程序运行期间也能够动态创建控件。

  小结

   尽管ActiveX控件从技术上集成了COM和OLE的许多精华技术,但由于MFC对ActiveX控件提供了强大的支持,使得对ActiveX控件的 开发成为一件非常容易的事情。但要深刻理解ActiveX控件技术,还要对一些基础技术有一个基本的概念,本文的目的并不在于介绍如何编写一个 ActiveX控件,而是通过对控件的创建过程的分析而使读者能够对ActiveX控件的开发有一个新的认识。本文所述代码在Windows 2000 Professional下由Microsoft Visual C++ 6.0编译通过。

分享到:
评论

相关推荐

    VC2005从开发MFC ActiveX ocx控件到发布到.net网站的全部过程

    【VC2005开发MFC ActiveX OCX控件并发布到.NET网站的全过程】 在Visual Studio 2005 (VC2005)中开发MFC ActiveX控件是一个常见且实用的任务,这使得开发人员能够创建可以在Web环境中使用的自定义组件。ActiveX控件...

    基于MFC的OCX控件开发教程及示例源代码

    本教程将深入探讨如何使用MFC进行OCX控件的开发,同时提供实际示例,包括对海康摄像头的云台控制和预置点设置等功能。首先,我们需要了解MFC中的CWnd类,它是所有窗口对象的基类,也是OCX控件的基础。通过继承CWnd类...

    VC2005从开发MFC ActiveX ocx控件到发布到.net网站的全部过程.

    在本文中,我们将深入探讨如何使用Visual Studio 2005 (VC2005) 开发MFC ActiveX OCX控件,并将其发布到.NET网站的过程。首先,我们需要了解ActiveX控件在Web环境中的作用,它们允许开发者创建可嵌入网页的组件,...

    VC编写ActiveX OCX控件--Timer

    在本文中,我们将深入探讨如何使用Visual C++(VC)来创建一个ActiveX OCX控件,特别是关于计时器(Timer)的功能。ActiveX OCX控件是一种可重用的软件组件,它允许开发者在多种应用程序中集成特定功能。在Windows...

    使用MFC开发ActiveX控件

    在本文中,我们将深入探讨如何使用Microsoft Foundation Class (MFC) 框架来开发ActiveX控件。ActiveX控件是一种可重用的软件组件,通常以OCX(OLE Control Extension)文件形式存在,可以在多种支持ActiveX技术的...

    vc开发activex,ocx控件的很好的教程.rar

    本教程“vc开发activex,ocx控件的很好的教程.rar”显然是一份专注于使用Microsoft Visual C++(简称VC)开发ActiveX控件的资源包。它可能包含了一系列步骤、示例代码和指导,帮助开发者掌握如何利用MFC(Microsoft ...

    MFC实现OCX控件示例

    总的来说,这个示例项目展示了MFC和OCX控件的结合使用,提供了自定义按钮控件的实现,包括消息处理、属性设置和用户交互。开发者可以通过研究这些源代码文件,学习如何扩展标准控件的功能,以满足特定的用户界面需求...

    VC2005从开发MFC_ActiveX_ocx控件到发布到net网站的全部过程

    VC2005从开发MFC_ActiveX_ocx控件到发布到.net网站的全部过程

    visual c++ vc制作ActiveX OCX控件.zip

    通过这个教程,开发者可以学习到如何利用Visual C++的MFC(Microsoft Foundation Classes)库和其他支持库来创建具有特定功能的ActiveX OCX控件,同时理解控件事件处理和交互机制。这在Windows应用程序开发中是非常...

    VC MFC日历控件ocx activex

    在Windows环境中,OCX控件可以被任何支持ActiveX的宿主应用程序(如Visual C++,VB,Delphi等)所使用。日历控件OCX可以通过拖放的方式添加到MFC应用程序中,然后通过编程设置其属性、响应其事件,以实现特定的功能...

    .NET中MFC制作activex(ocx)控件步骤、带图示

    .NET 中 MFC 制作 ActiveX(OCX)控件是一种常见的控件开发方式,以下是制作和使用 OCX 控件的详细步骤: Step 1. 安装 IDE(.NET) 在开始制作 OCX 控件之前,需要安装 .NET IDE,例如 Visual Studio .NET 等。...

    vc编写ATL ActiveX OCX控件

    通过以上步骤,开发者可以利用Visual C++和ATL高效地创建ActiveX OCX控件,这些控件可以嵌入到网页、其他应用程序中,提供自定义的功能和服务。ATL的轻量级特性使得它在性能上相比MFC(Microsoft Foundation Classes...

    mfc ocx控件创建及使用

    **MFC OCX控件创建及使用** 在Windows应用程序开发中,MFC(Microsoft Foundation Classes)框架提供了一种高效的方法来构建用户界面,而OCX(ActiveX Control,旧称OLE Control)则是一种可重用的组件,可以方便地...

    利用MFC开发ActiveX控件

    - 本文档详细介绍了如何使用MFC开发ActiveX控件,并通过一个具体的按钮控件案例,展示了开发过程中的关键技术点。 - 在开发过程中,需要注意保持控件的小型化、最小化持久数据、实现功能单一等特点,以提高控件的...

    使用C++制作ocx控件

    1. 注册控件使用命令 regsvr32 控件所在目录/*.ocx若注册不成功,可能是系统缺少一些动态库,检测缺少的动态库方法: 2. 注册成功后,打开工程项目,直接将 OCX 控件拖入控件资源框中即可使用,为控件添加变量后,...

    c++开发activex控件和js传参,js调用ocx控件接口函数的代码(

    vs2010开发activex(MFC)控件/ie插件(一),测试ocx控件加载 vs2010开发activex(MFC)控件/ie插件(二),js传参数 vs2010开发activex(MFC)控件/ie插件(三),js调用ocx控件的接口函数

    activexkj_ocx控件VS开发_ocx控件_

    在IT行业中,ActiveX控件和OCX控件是Windows应用程序开发中的重要组成部分,尤其是在Visual Studio(VS)环境下。ActiveX技术是由微软公司提出的一种组件对象模型,它允许开发者创建可重用的软件组件,而OCX(Object...

Global site tag (gtag.js) - Google Analytics