在 .NET Compact Framework 2.0 中宿主 ActiveX 控件

 

简介

Microsoft .NET Compact Framework 版本 2.0 引入了组件对象模型 (COM) 支持 - 这是一项版本 1.0 中没有的功能。以下事实或许能充分说明对该功能的迫切需要:Odyssey Software 已经发布了 CFCOM 产品(该工具用于突破对 COM 支持的限制,允许调用 COM 接口以及宿主 ActiveX 控件的工具),紧接着 .NET Compact Framework 立即发布了测试版。

尽管如此,在 .NET Compact Framework 中提供的 COM Interop 支持仍然是一个巨大进步。通过 COM 接口,Windows CE 中有越来越多的操作系统功能获得支持。这些功能大致包括 UPnP、蓝牙和 SDP、图像处理、消息队列(也称作 MSMQ)、MAPI、Pocket Outlook 对象模型 (POOM),以及其他许多重要的操作系统功能。在 .NET Compact Framework 版本 2.0 推出之前,开发人员不得不使用 C++ 创建填充程序。现在,开发人员可以直接从托管代码使用大多数 COM 接口,因此无需编写或分发特定于处理器的非托管模块。

.NET Compact Framework 2.0 中的 COM 支持与其桌面计算机中对应的功能相比仍存在一些局限性。例如,不支持外部激活。您不能在托管代码中编写独立的 COM 组件;如果可以,那么 COM 组件就可能已经在一个规则的非托管 Win32 进程中实例化了。因为托管代码需要公共语言运行库 (CLR) 才能执行,所以这种安装需要 Win32 进程来宿主 CLR。因为 CLR 宿主不是 .NET Compact Framework 2.0 的功能,所以整个外部激活模型都不可用。

.NET Compact Framework 2.0 中缺少的另一个重要功能是控件宿主。由于时间和资源所限,框架的最终版本中未包含该功能。但是,宿主一个控件所需要的全部功能都包含在框架中。托管到 COM 的调用(运行时可调用的包装 [RCW]);COM 到托管(COM 可调用的包装 [CCW]);连接点;回调;以及对控件句柄的访问(最终!)- 这些功能都包括在内。本文将为您介绍宿主一个控件需要采取的步骤。

控件宿主

本文的内容要求您熟悉 Win32 和 COM。虽然不阅读本文也可以使用所描述的控件宿主框架,但还是建议您阅读本文,因为这会帮助您了解底层的复杂性。

控件和容器

ActiveX 容器是控件宿主的主要概念之一。ActiveX 容器是一个实体,用于为 ActiveX 控件提供运行环境。有关 ActiveX 控件和容器的基本信息越来越难找了。MSDN 网站上或软件开发工具包 (SDK) 中似乎都没有这样的示例:说明如何不使用工具(如活动模板库 (ATL) 或 Microsoft 基础类 (MFC) 库)即可编写容器或控件。不得不承认,ATL 为此提供了巨大的帮助 - 但不是在您需要从头实现一个基本容器时。本文结尾处的文章列表几乎涵盖了 MSDN 网站提供的有关该主题的所有文章。

ActiveX 容器接口

从技术角度看,ActiveX 容器是一个 COM 对象,它支持几个必选接口和几个可选接口。表 1 显示容器使用的接口列表。(该信息来源于 Introduction to ActiveX Control Containers。)

表 1. 容器使用的接口(及其支持)
接口支持注释

IAdviseSink

可选

仅当容器需要通知时,该接口才是必需的,例如,从具有 IDataObject 接口的控件发出的数据更改通知;从具有 IViewObject2 接口的非活动控件发出的视图更改通知;从作为标准嵌入对象的控件发出的其他通知。

IClassFactory2

可选

该接口不是必需的,但建议对其提供支持。

环境属性的 IDispatch

必需

N/A

HYPERLINK "file:///E://library//en-us//oledb//htm//oledbierrorinfo.asp" IErrorInfo

必需

如果容器支持双接口,则该接口是必需的。

IOleClientSite

必需

N/A

IOleContainer

必需

该接口在存储容器站点的文档或窗体对象上实现。控件使用 IOleContainer 接口导航到相同文档或窗体中的其他控件。

IOleControlSite

必需

N/A

IOleInPlaceFrame

必需

N/A

IOleInPlaceSite

必需

N/A

IPropertyNotifySink

可选

只有那些具有自己的属性编辑用户界面 (UI) 的容器才需要该接口。

IOleWindow

必需

该接口在容器中实现,从宿主控件查询,由容器使用。

IOleInPlaceUIWindow

必需

N/A

ISimpleFrameSite

可选

除了对嵌套的简单框架的支持,该接口是可选的。

因为您要编写一个在移动设备上使用,并且具有有限的 OLE Controls 96 (OC 96) 规范支持的容器,所以您要忽略大多数可选功能以及一些必选功能来简化任务。

自身强加的限制

您要编写的容器一次仅限于一个控件。如果需要在窗体上宿主一个以上的 ActiveX 控件,您就必须创建两个不同的容器。因此,您可以不必考虑 ISimpleFrameSite 接口。

您还可以忽略其他接口。例如,您不想提供属性编辑;因此忽略 IPropertyNotifySink 接口。IClassFactory2IDispatch 接口显然不是必要的,因为 CCW(.NET Compact Framework 中的部分 COM 支持)负责类的实例化和 IDispatch 实现。目前,除了通过事件接口传递的通知,您不需要任何其他通知,因此您可以忽略 IAdviseSink 接口。最终,不再需要支持文档或多个控件,因此您可以将 IOleContainer 接口作为存根实现。您也不会在容器上实现 IErrorInfo 接口,因为您将通过异常机制接收错误信息。

表 2 到 7 描述在容器支持的接口中存在的方法。

表 2. IOleClientSite 接口中的方法
IOleClientSite 方法说明注意

SaveObject

保存嵌入对象

忽略该方法,因为容器不支持控件持久性。

GetMoniker

请求对象的名字对象

不支持该方法。其实现是独立的,不会向外部客户端提供任何 COM 接口。

GetContainer

请求指向对象容器的指针

支持该方法。

ShowObject

让容器显示对象

支持该方法。

OnShowWindow

通知容器,对象何时可见或不可见

忽略该方法。

RequestNewObjectLayout

让容器调整显示站点的大小

不支持该方法。

表 3. IOleWindow 接口中的方法
IOleWindow 方法说明注释

GetWindow

获得窗口句柄

支持该方法。

ContextSensitiveHelp

控制对上下文敏感帮助的启用

忽略该方法。容器不实现帮助支持。

表 4. IOleInPlaceSite 接口中的方法
IOleInPlaceSite 方法说明注释

CanInPlaceActivate

确定容器是否可以就地激活对象

该方法返回 true

OnInPlaceActivate

通知容器正在就地激活它的一个对象

使用该方法激活对象。

OnUIActivate

通知容器,将就地激活对象,主菜单将由复合菜单替换

支持该方法。

GetWindowContext

使就绪对象能够检索构成窗口对象层次结构的窗口接口,以及对象的就地激活窗口应该放置的父窗口中的位置

支持该方法。

Scroll

指定容器要滚动对象的像素数

不支持该方法。

OnUIDeactivate

通知容器重新安装其用户界面并获得焦点

支持该方法。

OnInPlaceDeactivate

通知容器不再就地激活对象

支持该方法。

DiscardUndoState

指导容器放弃其撤消状态

忽略该方法。容器将不会跟踪撤消状态。

DeactivateAndUndo

停用对象,并将其恢复为撤消状态

忽略该方法。容器将不会跟踪撤消状态

OnPosRectChange

通知容器,对象的范围已更改

支持该方法。

表 5. IOleControlSite 接口中的方法
IOleControlSite 方法说明注释

OnControlInfoChanged

通知容器,控件的 CONTROLINFO 结构已经更改,容器应该调用控件的 IOleControl::GetControlInfo 方法进行更新

忽略该方法,因为容器没有键盘支持。如果容器支持键盘操作,您可能已经从 CONTROLINFO 结构中抽取键盘快捷键表。

LockInPlaceActive

指示该控件是否应该保持就地激活,而不考虑可能的停用事件

不支持该方法。

GetExtendedControl

请求一个指向扩展控件的 IDispatch 指针,容器使用该控件包装实际的控件

忽略该方法。它始终返回 NULL,因为您不会使用调度接口与容器通信。

TransformCoords

在用 HIMETRIC 单位(它是对象链接与嵌入 [OLE] 中的标准)表示的 POINTL 结构和用容器指定的单位表示的 POINTF 结构之间进行转换

忽略该方法。该容器仅支持 MM_TEXT。

TranslateAccelerator

指导容器处理指定的击键

支持该方法。

OnFocus

指示该控件站点中的嵌入式控件是获得了还是丢失了焦点

忽略该方法。

ShowPropertyFrame

指导容器为该站点中嵌入的控件显示属性表

不支持该方法。

表 6. IOleInPlaceUIWindow 接口中的方法
IOleInPlaceUIWindow 方法说明注释

GetBorder

返回一个 RECT 结构,对象可以在就地激活时将工具栏和类似的控件放在其中

不支持该方法。其实现没有额外的 UI 元素,即只是控件本身。

RequestBorderSpace

确定在对象就地激活时,是否有可用的空间将工具安装在对象窗口框架的周围

不支持该方法。

SetBorderSpace

为请求的边界分配空间

不支持该方法。

SetActiveObject

提供对象与每个框架和文档窗口之间的直接通信信道

支持该方法。

表 7. IOleInPlaceFrame 接口中的方法
IOleInPlaceFrame 方法说明注释

InsertMenus

允许容器插入菜单

不支持该方法。

SetMenu

向窗口框架添加一个复合菜单

不支持该方法。

RemoveMenus

删除一个容器的菜单元素

不支持该方法。

SetStatusText

设置并显示有关容器框架窗口状态行中就绪对象的状态文本

不支持该方法。

EnableModeless

启用或禁用无模式对话框

不支持该方法。

TranslateAccelerator

转换击键

不支持该方法。

与控件交互

尽管文档声明,可以拥有一个只实现 IUnknown 派生的接口的控件,但实际上控件还可以实现其他一些功能。例如,因为编写 ActiveX 控件的主要目标是让自动化客户端可使用它们,这些自动化客户端如 Microsoft Internet Explorer 和 Microsoft Visual Basic(及其移动设备上的对应产品:eMbedded Visual BasicInternet Explorer Mobile),所以市场上几乎任何一种控件都支持 IDispatch 接口和调度事件接口。如果控件具有某种 UI,它还公开 IOleControlIOleInPlaceObject(带有继承的接口)。

其他控件还支持 IPersistStream 接口和其他 IPersistxxx 接口。这些接口用于填充控件属性的初始值,并且对 Visual Basic 中的设计器支持很有帮助。本文不对该主题进行介绍,因为 Visual Studio 中设计器支持的工作方式完全不同。

实现

在完整的框架中,控件宿主功能是在 AxHost 类中实现的。AxHost 类与 CAxHostWindow 类十分相似,后者是一个 ATL 模板类,定义了一个可以宿主 ActiveX 控件的窗口。与 CAxHostWindow 类似,完整框架中的 AxHost 此时能完成的任务大大超出您的需要。在该实现中,您能做到的远不止对工具栏、属性页、框架等的操作。

为了让实现更易于管理,您需要引入几个功能部件来封装整体设计的某些逻辑子集。例如,容器支持将在 AxContainer 类中实现,如图 1 所示。OleSite 类将负责与站点相关的接口,并负责与控件的 IObjectWithSite 接口交互。


1. 控件宿主框架体系结构。

声明并实例化控件

要在 .NET Compact Framework 中宿主控件,您需要完成两个主要任务。需要使用 COM 接口定义创建一个导入库,并构建一个从 AxHost 类派生的控件类。

获得一个 COM 接口定义

要宿主控件,需要在从 AxHost 类派生的类中实现特定于控件的接口(IDispatchIUnknown)。例如,Microsoft Windows Media Player 控件支持这样的调度接口:它们允许获取和设置属性,以及接收事件。它还支持 IUnknown 接口(如 IWMPCoreIWMPPlayer),这些接口可以并且应该直接映射到 .NET Compact Framework 类和接口。AxHost 类允许您通过 GetOcx 方法访问控件的 IUnknown 接口。如果您在 .NET Compact Framework 代码中有一个 COM 接口定义,则只需将 GetOcx 方法返回的对象类型转换为接口类型,框架就会内部执行 QueryInterface 方法。

手动创建接口定义

某些情况下,您会有一个 .idl 文件,它是由控件或 SDK 提供的。这种文件的定义可以手动转换为 .NET Compact Framework。要这么做并不容易,而且很容易出错。该方法是您最后的一招 - 当本文描述的其他方法不可用时。

例如,IWMPCdrom 接口是在完整的 Windows Media Player 10 中定义的,如以下示例代码所示。

[
    object,
    uuid(74C09E02-F828-11d2-A74B-00A0C905F36E),
    dual,
    helpstring("IWMPControls: Public interface."),
    pointer_default(unique)
]
interface IWMPControls : IDispatch
{
    [ id( DISPID_WMPCONTROLS_ISAVAILABLE ), propget ]
        HRESULT isAvailable( [in] BSTR bstrItem, [out, retval] VARIANT_BOOL *pIsAvailable );
    [ id( DISPID_WMPCONTROLS_PLAY ) ]
        HRESULT play();
    [ id( DISPID_WMPCONTROLS_STOP ) ]
        HRESULT stop();
    [ id( DISPID_WMPCONTROLS_PAUSE ) ]
        HRESULT pause();
    [ id( DISPID_WMPCONTROLS_FASTFORWARD ) ]
        HRESULT fastForward();
    [ id( DISPID_WMPCONTROLS_FASTREVERSE ) ]
        HRESULT fastReverse();
    [ id( DISPID_WMPCONTROLS_CURRENTPOSITION ), propget ]
        HRESULT currentPosition( [out, retval] double * pdCurrentPosition );
    [ id( DISPID_WMPCONTROLS_CURRENTPOSITION ), propput ]
        HRESULT currentPosition( [in] double dCurrentPosition );
    [ id( DISPID_WMPCONTROLS_CURRENTPOSITIONSTRING ), propget ]
        HRESULT currentPositionString( [out, retval] BSTR * pbstrCurrentPosition );
    [ id( DISPID_WMPCONTROLS_NEXT ) ]
        HRESULT next();
    [ id( DISPID_WMPCONTROLS_PREVIOUS ) ]
        HRESULT previous();
    [ id( DISPID_WMPCONTROLS_CURRENTITEM ), propget ]
        HRESULT currentItem( [out, retval] IWMPMedia **ppIWMPMedia);
    [ id( DISPID_WMPCONTROLS_CURRENTITEM ), propput ]
        HRESULT currentItem( [in] IWMPMedia *pIWMPMedia );
    [ id( DISPID_WMPCONTROLS_CURRENTMARKER ), propget ]
        HRESULT currentMarker( [out, retval] long *plMarker);
    [ id( DISPID_WMPCONTROLS_CURRENTMARKER ), propput ]
        HRESULT currentMarker( [in] long lMarker);
    [ id( DISPID_WMPCONTROLS_PLAYITEM )]
        HRESULT playItem( [in] IWMPMedia *pIWMPMedia );
}

该接口 (IWMPCdrom) 可在 .NET Compact Framework 代码中定义,如以下示例代码所示。

    [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    [Guid("74C09E02-F828-11D2-A74B-00A0C905F36E")]
    public interface IWMPControls
    {
        [DispId(60)]
        IWMPMedia currentItem { get; set; }
        [DispId(61)]
        int currentMarker { get; set; }
        [DispId(56)]
        double currentPosition { get; set; }
        [DispId(57)]
        string currentPositionString { get; }

        [DispId(54)]
        void fastForward();
        [DispId(55)]
        void fastReverse();
        [DispId(62)]
        bool get_isAvailable(string bstrItem);
        [DispId(58)]
        void next();
        [DispId(53)]
        void pause();
        [DispId(51)]
        void play();
        [DispId(63)]
        void playItem(IWMPMedia pIWMPMedia);
        [DispId(59)]
        void previous();
        [DispId(52)]
        void stop();
    }

前面提到过,这不是您想使用的方法,除非您不得不使用它。您必须显式定义接口的一种情况是:使用 TLBIMP 工具的自动导入产生了不希望的结果,或者出于某种原因您不能使用接口的平台调用定义。

例如,.NET Compact Framework 2.0 版本不支持将接口指针数组封送回调用方(即 ISomeInterface[])。这种类型的参数在 IEnumxxx::Next 方法实现中相当常见,您可以在诸如 IEnumVariantIEnumConnectionPoint 这样的 COM 接口中找到这些方法实现。但如果您想查看对象浏览器,则会看到对于 System.Runtime.Interop.ComTypes.IEnumConnectionPoints 类,Next 方法的定义如下所示。

IEnumConnectionPoints.Next(int celt, IConnectionPoint[] rgelt, IntPtr pceltFetched)

遗憾的是,由于前面提到过的原因,该代码并不起作用(而且,将最后一个参数定义为 IntPtr 也很不方便)。相反,您需要重新定义接口,使该方法如下所示。

void Next (UInt32 cConnections, out IConnectionPoint rgpcn, out UInt32 lpcFetched )

虽然从技术角度讲,该代码不能等同于原始定义,但只要 cConnections 设置为 1,该代码也会很好地运行。显然,将其设置为任何大于 1 的值都会导致缓冲区溢出,使您的应用程序崩溃。

使用该方法,您可以定义自己的接口版本。对本例而言,它名为 IMyEnumConnectionPoint。请注意,对 System.Runtime.InteropServices.ComTypes.IConnectionPointContainer.EnumConnectionPoints() 方法的调用获得原始 IEnumConnectionPoint 接口的实例。那么如何获得 IMyEnumConnectionPoint 接口呢?您只需将 IEnumConnectionPoint 接口的实例转换为 IMyEnumConnectionPoint 接口。因为二者都声明为 ComImport,所以这种类型转换将不会使用 CLR 运行时类型检查,但是它们会导致调用 QueryInterface,这会成功地进行,因为两个接口具有相同的 GUID。

使用 TLBIMP.exe 导入接口定义

要自动提取接口定义,您应该使用 TLBIMP 工具。TLBIMP 包含在 .NET Framework SDK 中。当 COM 库(.dll、.ocx、.tlb)的引用添加到 .NET Compact Framework 项目时,也会自动调用它。当您处理设备项目时,ActiveX 控件通常打包在一个 .cab 文件中。当然,这种控件将针对 ARM CPU 进行编译,不能在桌面计算机上运行;幸运的是,也不必在桌面计算机上运行。类型库是独立于平台的,即使包含在 .dll 文件中时也如此 - 几乎是(在 64 位平台上不是这样)。因此,对 ARM ActiveX DLL 的引用可以像对待桌面计算机控件一样,以相同的方式添加到项目中。当然,不可能对其进行实例化或做任何其他事情,但这是另一码事了。

示例请查看 Macromedia (Adobe) 提供的 Shockwave Flash Player 控件。您可以免费下载该播放器。安装程序包括一个名为 ActiveX.cab 的 .cab 文件,默认情况下,它复制到 C:/Program Files/Microsoft ActiveSync/Macromedia/Flash Player 6 目录中。如果双击该 .cab 文件,您将看到两个文件。名为 000flash.001 的文件是 ActiveX 本身。解压缩该文件,将其重命名为 Flash.dll,然后将其添加为对您项目的引用。它将在列表中显示为 ShockwaveFlashObjects,如图 2 所示。


2. Visual Studio 项目引用中的 Shockwave Flash 导入库

如果查看类浏览器,您会看到现在拥有一个新的命名空间、类和接口定义,如图 3 所示。


3. Visual Studio 类浏览器中的 Shockwave Flash 导入库

解决对 CAB 或类型库的访问

有时,您不能享受到访问控件 CAB 的乐趣。例如,当开发人员意识到 Windows Media Player 10 Mobile 没有可重新分发的包时,宿主 Windows Media Player 控件这一最常见的请求将会遇到第一个麻烦。它仅可用作设备 ROM 的一部分。幸运的是,在 Windows Media Player 中,桌面计算机的类型库与设备的类型库是相同的,区别仅为 Windows Media Player Mobile 上的许多方法返回 E_NOTIMPL。幸运的是,设备版本不提供某些方法的完整实现,这对类型库没有影响;因此,您可以从桌面播放器导入类型库(位于 C:/Windows/System32/wmpcore.dll 中),并将其与设备 Media Player 一起使用。您仍需要仔细阅读 Windows Media Player SDK 文档,了解实际上可以使用的方法和属性。

如果您拥有一个类型库不可用的控件,则检查是否存在一个具有相同或相似接口的相同控件的桌面计算机版本。

遗憾的是,有时上述方法并不起作用。例如,如果您试图使用桌面计算机的 Web 浏览器控件 (SHDOCVW.DLL) 的类型库来宿主 Pocket PC Web 浏览器控件,您可能会失望;因为它们并不相同。所幸,有一种避开这种情况的方法。如果查看 Windows Mobile SDK,您将看到一个名为 webvw.idl 的文件,它包含 Web 浏览器控件的所有接口定义 - 这是个好消息。但坏消息是,它的大小为 50 KB,包含大约 1500 行代码。该控件对于手动转换为 C# 是不可行的。不过还是好消息更多一些:有一个从该文件创建类型库的省事的方法。

webvw.idl 文件创建一个类型库

打开一个 Visual Studio 命令提示符。(如果运行的是 64 位平台,则确保它是 32 位命令提示符。)

浏览到 SDK Include 目录(默认情况下,它位于 C:/Program Files/Microsoft Visual Studio 8/SmartDevices/SDK/PocketPC2003/Include 中)。

运行以下命令:


midl /D UNDER_CE webwv.idl

MIDL 编译器编译 .idl 文件并生成另一个文件 webvw.tlb。您可能已经猜到,该文件可以导入到 Visual Studio 项目中。

这个巧妙的方法有一个局限。.idl 文件必须包含一个 library 部分,其中定义了一个 coclass。有时您可能没有该部分。在这种情况下,需要生成一个 library,如下示例代码所示。

import "OAIdl.Idl";
import "objidl.idl";
// Import any other .idl files you need
[
    uuid(77777777-7777-7777-7777-777777777777),
    helpstring("Test library"),
    lcid(0x0000),
    version(1.0)
]
library TestLib
{
    [
        uuid(33333333-3333-3333-3333-333333333333),
        helpstring("Test class")
    ]
    coclass Test
    {
        [default] interface IEnumConnectionPoints;
          interface IConnectionPoint;
          // List any other interfaces that you are interested in
    };
}

生成一个如代码示例所示的文件。根据需要添加 import 指令和接口参数。如果包括了一个接口,并且其方法引用另一个接口,则该接口将自动包含在其中。在前面的示例中,添加 IEnumConnectionPoints 会导致包括 IConnectionPointIConnectionPointContainer 以及其他一些接口。

使用 AxImp.exe 生成一个完整的控件类定义

能够最终获益当然好 - 按照 Visual Studio 在桌面计算机窗体项目中处理的方式处理 ActiveX 控件。您只需向工具栏添加一个控件,方法是浏览到该控件或从注册组件列表中选择它。虽然 Visual Studio 设计器主动阻止任何将 ActiveX 控件添加到设备项目的企图,但您可以避免该限制。与前面描述的方法(将 TLBIMP 工具用作添加对类型库引用的命令行功能)类似,当将一个 ActiveX 控件放在窗体上时,可以使用 AXIMP 工具(也是 .NET Framework SDK 的一部分),该工具创建与 Visual Studio IDE 所创建的库相同的一组库。正如 TLBIMP 工具具有比集成开发环境 (IDE)(在导入类型库方面)更丰富的功能集一样,AXIMP 工具能够完成更多的任务。对于您的项目而言,最重要的是能够以其源格式产生 Interop 代码。如果运行以下命令行 AxImp.exe C:/WINDOWS/system32/wmp.dll /source,将产生三个文件:

AxWMPLib.cs

AxWMPLib.dll

WMPLib.dll

还有一个 .pdb 文件,但它现在已经不重要了。AxWmpLib.cs 是 Interop 库 AxWMPLib.dll 的源代码。仔细看一下,您会发现它定义了一个从 System.Windows.Forms.AxHost 派生的类 AxWindowsMediaPlayer。这 1663 行代码告诉明示了手动编写该代码是多么地令人不快。

您为什么想要源代码呢?如果您在 ILDAsm 工具中查看 AxWMPLib.dll,就会发现,它是针对 MSCORLIB 和 System.Windows.Forms 的桌面计算机版本编译的。显然,您不能使用 AXIMP 生成的 AxWMPLib.dll 版本。但是,您可以针对 .NET Compact Framework 库编译源代码文件,获得可以在设备上使用的程序集。

试图编译 Interop 库的源代码时,您会发现由于 MSCORLIB 和 System.Windows.Forms 的设备版本而导致产生了相当多的错误。幸运的是,您可以忽略所有这些缺失的引用,并用生成方法存根 (stub) 替换它们。您可以在本文的下载代码示例的 AxImpSupport.cs 模块中找到这些 stub。

使用 AXIMP 工具生成包装的好处是:快速、简单,并且可以创建一个具有完全事件和设计器支持的控件类。当然,其他两个方法也可以产生带有完全设计器支持的代码,但它们会需要大量的工作。

一些开发人员更喜欢将这样的类编译到一个单独的库中。因为不需要编写任何额外的代码,即使不熟悉 C# 的开发人员也可以构建此控件库。控件库构建完成后,您可以轻松地将其用于 Visual Basic .NET 项目中,如本文的下载代码示例所示。

设计器支持

因为 AxHost 类是从 Control 类派生的,所以 Visual Studio 设计器直接获得 AxHost 类,并将其显示为可以插入到您的项目窗体中。对于从 AxHost 类派生的类也如此。所有宿主的控件在设计器中均可自动使用,如图 4 所示。


4. Visual Studio 工具箱中出现的包装 ActiveX 控件

此外,由于生成的 ActiveX 控件类通过 .NET Compact Framework 属性和事件公开了调度属性和事件接口,因此它们在设计器中也可用。您可以通过控件清单进一步自定义设计器中的控件外观,但这不属于本文的讨论范围。

图 5 显示一个宿主 ShockWave Flash 控件的设计器 Properties 窗格。


5. 宿主 ActiveX 控件事件


6. 宿主 ActiveX 控件属性

Control 类派生 AxHost 类的一个意外结果是 AxHost 类也在设计器工具箱中显示。因为对开发人员而言,它作为一个独立的控件是没用的,所以您需要用以下内容向项目添加一个控件清单文件来阻止它。

<?xml version="1.0" encoding="utf-16"?>
<Classes xmlns="http://schemas.microsoft.com/VisualStudio/2004/03/SmartDevices/XMTA.xsd">
    <Class Name="System.Windows.Forms.AxHost">
    <DesignTimeVisible>false</DesignTimeVisible>
    </Class>
</Classes>

需要改进的内容

总有一些内容可以改进。我们这些 OpenNETCF.org 的员工正计划将来添加或增强一些 AxHost 类功能。

下面列出一些不具备的功能:

键盘支持
尚未通过使用键盘的设备对该代码进行测试。我很肯定快捷键、焦点和按键处理存在一些问题。

无窗口激活
目前不支持该类型的激活。幸运的是,大部分支持无窗口激活的控件也支持窗口激活。

类似按钮和类似标签的控件
AxHost 类的当前版本很有可能不能正确地处理 OLEMISC 标志。利用诸如 Shockwave player 和 WMP 之类的控件,它实际上不是必需的,但我希望某些控件真正需要它。

激活和停用
大部分控件开发人员都对具有一个简单的生命周期感兴趣:创建,激活,显示,停用,销毁。一些控件可能还有更复杂的要求,如多个激活或停用。

 

小结

您已经了解了通过使用 .NET Compact Framework 2.0 创建 ActiveX 控件的托管宿主这一过程。本文介绍了有关控件宿主的一些内容,并针对特别有用的 ActiveX 宿主对象,说明了一些问题和解决方案,并向您介绍了如何使用它以在您自己的应用程序中宿主控件。

此处说明的托管控件宿主框架由 OpenNETCF.org 提供。

本文提供代码示例下载。

从 Microsoft Download Center 下载 Host_ActiveX_Controls.msi

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值