使用 COM 访问 Silverlight 中的硬件






4.91/5 (17投票s)
一个用 C# 编写的 COM 对象示例,
引言
Silverlight 4 提供了对用户麦克风和摄像头的访问,并增加了打印功能,但就硬件而言,仅此而已。幸运的是,Silverlight 4 还允许在具有提升权限的浏览器外应用程序中访问 COM。如果您需要访问其他本地硬件,提供一个 COM 组件是解决方案。本示例提供了一个用 C# 编写并由 Silverlight 应用程序寻址的 COM 对象。
背景
我的公司提供垂直市场软件,并且通常也会提供外围设备。典型设备包括现金抽屉和刷卡机等。这些通常是串行设备。客户还要求基于 Web 的解决方案,以避免本地安装软件通常的分发和更新问题。Silverlight 现在提供了出色的用户体验,但硬件访问至关重要。
我没有任何编写 COM 对象(或使用它们)的经验,所以这对我是个探索过程。我不得不拼凑许多不同的文章和示例才能让某些东西奏效。我希望您能从一个完整的示例中受益。
示例问题
在我的示例中,我模拟了一个三抽屉的现金抽屉堆叠。应用程序需要三个基本功能:
- 按抽屉编号打开抽屉。
- 检查特定抽屉的状态。
- 如果抽屉打开或关闭,则接收事件。
示例解决方案包含一个 Windows 类库,该库模拟设备类并将必要的方法和事件公开给 COM。解决方案中还有一个非常简单的 Silverlight 测试应用程序。
COM 对象的目标是 2.0 Framework。使用它的 Silverlight 应用程序是一个 Silverlight 4 项目。

创建 COM 组件
要创建公开给 COM 的类,请为 public
类的成员(事件除外)创建一个接口,并为事件创建另一个接口。这些必须分开到单独的接口中。
这是方法接口
using System;
using System.Runtime.InteropServices;
namespace ComExampleLib
{
[Guid("65152CDB-8530-4363-8036-E7F732E9E6F6")]
public interface IComExample
{
void OpenDrawer(int drawerNumber);
bool IsDrawerOpen(int drawerNumber);
void CloseAllOpenDrawers();
}
}
有几点需要注意:
- 用 guid 装饰接口。guid 属性位于
System.Runtime.InteropServices
。 - 接口必须标记为
public
。 - 参数是简单类型。
这是事件接口
using System;
using System.Runtime.InteropServices;
namespace ComExampleLib
{
[Guid("AF8B17FB-9A65-464E-AD19-E3849B97C2AA"),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IComExampleEvents
{
void DrawerStateChange(int drawerNumber, bool isOpen);
}
}
注意事项
- 接口必须是
public
。 - 接口用 guid 属性装饰,并且它的 guid 与用于其他接口的 guid不同。
- 接口还用互操作接口类型
InterfaceType(ComInterfaceType.InterfaceIsIDispatch)
装饰。这很重要,并且IDispatch
是您应该使用的类型。 - 最需要注意的一点是,尽管这是“事件”接口,但我们声明了一个返回
void
的简单方法,而不是 C# 的event
或delegate
。同样重要的是参数为简单类型。使用派生自EventArgs
的复杂类型的常规模式不起作用。
我在其自己的代码文件中为事件声明了一个委托。事件接口中的方法和此委托都将在实际类中使用。请注意,签名匹配但名称不匹配。在实际公开的类中,我将使用委托声明一个事件。该事件的名称必须与上面显示的事件接口中声明的名称匹配。
using System;
namespace ComExampleLib
{
public delegate void DrawerChange(int drawerNumber, bool isOpen);
}
最后,这是类
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace ComExampleLib
{
[Guid("03EFC7CF-A57B-4F66-ABC9-FFBE13F1E927"),
ProgId("ComExample.Application"),
ClassInterface(ClassInterfaceType.None),
ComSourceInterfaces(typeof(IComExampleEvents))
]
public class ComExample : IComExample
{
private Dictionary<int, bool> m_DrawerList = new Dictionary<int, bool>();
public ComExample() { }
#region IComExample Members
public void OpenDrawer(int drawerNumber)
{
if (m_DrawerList.ContainsKey(drawerNumber))
{
m_DrawerList[drawerNumber] = true;
}
else
{
m_DrawerList.Add(drawerNumber, true);
}
OnDrawerStateChange(drawerNumber, true);
}
public bool IsDrawerOpen(int drawerNumber)
{
return m_DrawerList.ContainsKey(drawerNumber) && m_DrawerList[drawerNumber];
}
public void CloseAllOpenDrawers()
{
List<int> toClose = new List<int>();
foreach (KeyValuePair<int, bool> drawer in m_DrawerList)
{
if (drawer.Value)
{
toClose.Add(drawer.Key);
}
}
foreach (int n in toClose)
{
m_DrawerList[n] = false;
OnDrawerStateChange(n, false);
}
}
#endregion
#region Events
public event DrawerChange DrawerStateChange;
public void OnDrawerStateChange(int drawerNumber, bool isOpen)
{
if (DrawerStateChange != null)
{
DrawerStateChange(drawerNumber, isOpen);
}
}
#endregion
}
}
关于类的重要说明:
- 类是
public
。 - 类用 guid 属性装饰,并且 guid 与两个接口的 guid 不同。
ProgId("ComExample.Application")
属性定义了将在 Silverlight 应用程序中如何引用该类。您可以提供任何唯一的string
。您也可以省略此属性,在这种情况下,您将以Assembly.Class
的形式引用类。ClassInterface(ClassInterfaceType.None)
属性会关闭自动生成的接口。我们将提供一个显式接口。ComSourceInterfaces(typeof(IComExampleEvents)
属性声明了类引发的事件。请注意,事件接口未在类定义中列为已实现。这可能显得奇怪,但却是正确的。- 请注意,类确实显式实现了方法接口。
- 最后,请注意,类使用我们之前定义的委托声明了一个
public
事件,其名称与事件接口中的事件名称完全相同。类中的事件和事件接口中的方法名必须匹配。
Visual Studio 设置
要使此生效,还需要做几件事。打开项目的属性并选择“应用程序”选项卡。单击“程序集信息”按钮,然后选中“使程序集 COM 可见”复选框。

作为使整个程序集 COM 可见的一种替代方法,您可以使用 ComVisible(true)
属性标记您想要公开的程序集的部分。
最后,为了在不手动注册 COM 对象的情况下运行解决方案,请转到“生成”选项卡并选中“注册 COM 互操作”复选框。当 Visual Studio 运行项目时,它将为您注册该对象。

要在另一台计算机上安装 COM 对象,请使用 .NET Framework 中的 RegAsm 程序。
C:\Windows\Microsoft.NET\Framework\v2.0.50727\regasm ComExampleLib.dll /tlb
Silverlight 项目
Silverlight 应用程序相对简单,这得益于新的 Silverlight 4 功能。
请注意,我已向 Silverlight 应用程序项目添加了两个引用:Microsoft.CSharp
和 System.Core
。
我在主页的代码隐藏中声明了两个模块级变量:
dynamic m_ComDrawerContoller;
AutomationEvent m_DrawerChangeEventHandler;
这些不是在启动时填充的,因为我们需要检查以确保我们在正确条件下运行。
if (Application.Current.IsRunningOutOfBrowser &&
Application.Current.HasElevatedPermissions)
{
RegisterComObject();
}
如果条件正确,我们就可以初始化 COM 对象:
private void RegisterComObject()
{
try
{
m_ComDrawerContoller = AutomationFactory.CreateObject("ComExample.Application");
m_DrawerChangeEventHandler = AutomationFactory.GetEvent
(m_ComDrawerContoller, "DrawerStateChange");
m_DrawerChangeEventHandler.EventRaised +=
new EventHandler(HandleDrawerStateChange);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
也可以为 EventRaised
处理程序使用 lambda 表达式。
当事件引发时,我们可以对其进行处理。AutomationEventArgs
类定义了一个 Arguments
对象数组。这些是我们传递给事件方法的参数,并且按照声明的顺序出现。
private void HandleDrawerStateChange(object sender, AutomationEventArgs e)
{
switch ((int)e.Arguments[0])
{
case 1:
Drawer1Status.Text = (bool)e.Arguments[1] ? "Open" : "Closed";
break;
case 2:
Drawer2Status.Text = (bool)e.Arguments[1] ? "Open" : "Closed";
break;
case 3:
Drawer3Status.Text = (bool)e.Arguments[1] ? "Open" : "Closed";
break;
}
}
最后,常规方法可以以常规方式调用。请注意,它们不是异步的。
private void btnReset_Click(object sender, RoutedEventArgs e)
{
m_ComDrawerContoller.CloseAllOpenDrawers();
}
private void check_click(object sender, System.Windows.RoutedEventArgs e)
{
int drawerNumberToCheck;
int.TryParse(txtDrawerToCheck.Text, out drawerNumberToCheck);
if (drawerNumberToCheck > 0)
{
bool isOpen = m_ComDrawerContoller.IsDrawerOpen(drawerNumberToCheck);
if (isOpen) lblCheckStatus.Text = "Open"; else lblCheckStatus.Text = "Closed";
}
}
Intellisense 不会帮助您处理方法名称或参数,因此您需要对 COM 对象有良好的文档。
运行 Silverlight 项目
请注意,Silverlight 项目设置为在浏览器外运行并具有提升的信任。这些设置位于 Silverlight 项目的属性页的“Silverlight”选项卡上。单击“浏览器外设置”按钮设置提升的信任设置。

要运行项目,请先将 Web 应用程序设置为启动项目。当 Silverlight 应用程序加载后,右键单击并将其本地安装。关闭项目,然后返回到 Silverlight 应用程序的 Web 主机项目,并更改调试属性以调试 Silverlight 应用程序(而不是 Web 应用程序)。现在,当您将启动项目设置为 Web 应用程序时,它将调试正确的应用程序。

当程序运行时,您应该能够调用 COM 对象的方法并接收其事件。
结论
要求用户安装 COM 对象才能使您的 Silverlight 项目正常工作,这显然对于面向公众的通用应用程序来说是不可行的。然而,在更受控制的业务线或企业环境中,它可以提供连接您需要做的事情和 Silverlight 可以做的事情的最终桥梁。
历史
- 2010年5月25日:初始版本