Microsoft .NET 框架常见问题(二)

名称空间与程序集名称之间有什么区别?
名称空间是类型的一种逻辑命名方案,其中简单类型名称(如 MyType)前面带有用点分隔的层次结构名称。这样的命名方案完全在开发人员的控制之下。例如,键入 MyCompany.FileAccess.A 和 MyCompany.FileAccess.B 在逻辑上将会具有与文件访问相关的功能。.NET 框架使用一种层次结构命名方案,用于将类型按相关功能的逻辑类别进行分组,例如,ASP.NET 应用程序框架或远程处理功能。设计工具可以利用名称空间使开发人员更容易在代码中浏览和引用类型。名称空间的概念与程序集的概念之间没有任何联系。一个程序集可以包含其层次结构名称具有不同名称空间根的类型,而一个逻辑名称空间根可以跨越多个程序集。在 .NET 框架中,名称空间是在设计时进行逻辑命名的便捷方式,而程序集在运行时为类型建立名称作用域。

应用程序部署和隔离
部署 .NET 应用程序时可以使用哪些选项?
通过使应用程序的无影响安装和 XCOPY 部署成为可能,.NET 框架简化了部署。因为所有的请求首先在专用应用程序目录中进行解析,所以只需简单地将一个应用程序的目录文件复制到磁盘中,即可运行该应用程序,而不需要注册。

此方案对于 Web 应用程序、Web 服务和独立的桌面应用程序特别有吸引力。不过,在有些方案中 XCOPY 还不足以担当分发机制。例如,当应用程序具有很少的专用代码,而依赖于可用的共享程序集;或者应用程序不是安装在本地(而是按需下载)。对于这些情况,.NET 框架提供了扩展的代码下载服务以及与 Windows Installer 的集成。.NET 框架提供的代码下载支持通过当前平台提供了许多优势,包括增量下载、代码访问安全性(不再有“Authenticode”对话框)和应用程序隔离(为一个应用程序下载的代码不会影响其他应用程序)。Windows Installer 是 .NET 应用程序可以使用的另外一个强大的部署机制。在 Windows Installer 1.5 中,Windows Installer 的所有特性(包括发行、公布和应用程序修补)都可以在 .NET 应用程序中使用。

如果我已经编写了一个程序集,并希望在多个应用程序中使用它,我应该在何处部署它?
要由多个应用程序使用的程序集(如共享程序集)需要部署到全局程序集缓存中。在预发布版和 Beta 版中,使用 Alink SDK 工具的 /i 选项可将程序集安装到缓存中:

al /i:myDll.dll
Windows Installer 的后续版本能够将程序集安装到全局程序集缓存中。

如何才能看到在全局程序集缓存中安装了哪些程序集?
.NET 框架附带了一个 Windows 外壳扩展,用于查看程序集缓存。在 Windows 资源管理器中,转至 % windir%\assembly 以激活查看器。

什么是应用程序域?
应用程序域(通常是 AppDomain)是用于隔离应用程序的虚拟进程。在同一个应用程序作用域中创建的所有对象(换句话说,从该应用程序的入口点开始沿着对象激活序列的任何地方)都在同一个应用程序域中创建。多个应用程序域可以存在于一个操作系统进程中,使它们成为隔离应用程序的简便方式。

操作系统进程通过使用各不相同的内存地址空间来提供隔离。尽管它是有效的,但也是代价昂贵的,并且不能达到大型 Web 服务器所需要的数量。与其相比,公共语言运行时通过管理在应用程序域中运行的代码的内存使用来强制进行应用程序隔离。这样就确保它不会访问应用程序域以外的内存。需要注意的是,只有类型安全的代码才能以这种方式管理(当在应用程序域中加载不安全代码时,运行时不能保证隔离)。

垃圾回收
什么是垃圾回收?
垃圾回收是使计算机能检测何时不再能够访问某个对象的一种机制。它将自动释放由该对象使用的内存(也调用用户编写的称为“结束者”的清理例程)。一些垃圾回收器(如由 .NET 使用的)会压缩内存,并因此减少程序的工作集。

非确定性垃圾回收是如何影响代码的?
对于大多数编程人员而言,拥有一个垃圾回收器(并且使用可作为垃圾回收的对象)意味着永远不需要操心释放内存或引用计数对象,即使您使用了复杂的数据结构。但如果您通常在同一个用于释放对象内存的代码块中释放系统资源(文件句柄、锁定等等),那么在编码样式方面需要做一些修改。使用可作为垃圾回收的对象时,您应该提供一种方法,来明确释放系统资源(也就是说,由您的程序控制),同时允许垃圾回收器在压缩工作集时释放内存。

是否能够避免使用可作为垃圾回收的堆?
所有支持运行时的语言都允许您从可作为垃圾回收的堆中分配类对象。这在快速分配方面带来了好处,并且使编程人员无需自己来计算何时应该显式“free”每个对象。

CLR 还提供了 ValueTypes 对象——它们与类相似,但 ValueType 对象是在运行时堆栈(不是堆)中分配的,因此当您的代码退出定义这些对象的过程时,将自动回收它们。这就是 C# 中“struct”的操作方式。

C++ 的托管扩展使您可以选择类对象分配的位置。如果使用 __gc 关键字声明为托管类,它们将从可作为垃圾回收的堆中分配;如果它们不包含 __gc 关键字,它们将与普通的 C++ 对象一样从 C++ 堆中分配,并且使用“free”方法显式释放。

有关垃圾回收的的详细信息,请参阅:

垃圾回收:Microsoft .NET 框架中的自动内存管理(英文)

垃圾回收 – 第 2 部分:Microsoft .NET 框架中的自动内存管理(英文)

远程处理
如何在公共语言运行时中进行进程内和进程间通讯?
进程内通讯有两种:在单一应用程序域的上下文中,或者跨应用程序域。在同一个应用程序域的上下文中,使用代理作为监听机制,而不涉及封送处理/序列化。当跨应用程序域时,使用运行时二进制协议来作封送处理/序列化。

进程间通讯为每个特定目的使用一个可插入通道和格式化程序协议。

如果开发人员使用 soapsuds.exe 工具指定终结点来生成元数据代理,那么默认值是带有 SOAP 格式化程序的 HTTP 通道。

如果开发人员在托管世界中执行显式远程处理,需要明确指定使用的通道和格式化程序。这可以通过配置文件用可管理的方式来表示,或者用 API 调用来加载特定通道。选项如下:
带有 SOAP 格式化程序的 HTTP 通道(HTTP 在 Internet 上或任何必须通过防火墙进行通信的时候运行良好)

带有二进制格式化程序的 TCP 通道(对于局域网,TCP 是性能较高的选项)

带有 SOAP 格式化程序的 SMTP 通道(仅对跨计算机有意义)

在托管代码和非托管代码之间进行转换时,COM 基础结构(尤其是 DCOM)用于远程处理。在 CLR 的中间版本中,这也适用于服务组件(使用 COM+ 服务的组件)。在最终版本中,配置任何远程组件都是可能的。

对象的分布式垃圾回收由名为“租用生存期”的系统来管理。每个对象都有一个租用时间,当到期时,该对象与 CLR 的远程处理基础结构断开连接。对象具有一个默认的更新时间——当客户端成功地调用了对象时,租用将被更新。客户端可以显式更新租用。

互操作性
是否可以在 .NET 框架程序中使用 COM 对象?
是。您现在部署的任何 COM 组件都可以在托管代码中使用。通常情况下,所需的调整是完全自动进行的。

特别是,可以使用运行时可调用包装 (RCW) 从 .NET 框架访问 COM 组件。此包装将 COM 组件提供的 COM 接口转换为与 .NET 框架兼容的接口。对于 OLE 自动化接口,RCW 可以从类型库中自动生成;对于非 OLE 自动化接口,开发人员可以编写自定义 RCW,手动将 COM 接口提供的类型映射为与 .NET 框架兼容的类型。

是否可以在 COM 程序中使用 .NET 框架组件?
是。您现在创建的托管类型都可以通过 COM 访问。通常情况下,所需的配置是完全自动进行的。托管开发环境的某些新特性不能在 COM 中访问。例如,不能在 COM 中使用静态方法和参数化构造函数。一般,提前确定给定类型所针对的用户是一种较好的办法。如果类型需要在 COM 中使用,您将被限制在使用 COM 可访问的特性。

默认情况下,托管类型可能是可见的,也可能是不可见的,这由用于编写托管类型的语言决定。

特别是,可以使用 COM 可调用包装 (CCW) 从 COM 访问 .NET 框架组件。这与 RCW(请参阅上一个问题)相似,但它们的方向相反。同样,如果 .NET 框架开发工具不能自动生成包装,或者如果自动方式不是您所需要的,则可以开发自定义的 CCW。

是否可以在 .NET 框架程序中使用 Win32 API?
是。使用 P/Invoke,.NET 框架程序可以通过静态 DLL 入口点的方式来访问本机代码库。

下面是 C# 调用 Win32 MessageBox 函数的示例:

using System;
using System.Runtime.InteropServices;

class MainApp
{
    [DllImport("user32.dll", EntryPoint="MessageBox")]
    public static extern int MessageBox(int hWnd, String strMessage, String strCaption, uint uiType);

    public static void Main()
    {
        MessageBox( 0, "您好,这是 PInvoke!", ".NET", 0 );
    }
}

安全性
如何使代码与安全系统协调工作?
通常,这不成问题——大多数应用程序能安全地运行,不会受恶意攻击的干扰。通过简单地使用标准类库来访问资源(如文件)或执行受保护的操作(例如反转类型的私有成员),安全性由这些库来实施。应用程序开发者需要完成的一项简单工作是包括权限请求(一种公开的安全性),将代码可能接收的权限限制在它所需要的权限范围内。这也确保了如果代码被允许运行,它在运行时将具有所需的所有权限。

仅当开发人员需要编写提供新型资源的新基类库时,他们才需要直接处理安全系统。在这种情况下,并非所有的代码都有潜在的安全性问题,代码访问安全机制将其限制在替代了安全系统的那部分代码上。

为什么在网络共享驱动器中运行代码时会发生安全异常?
默认安全策略仅给来自本地 Intranet 区域的代码授予有限的权限。这个区域是由 Internet Explorer 安全设置定义的,它们应该配置为与企业内部的本地网络相匹配。由于由 UNC 或映射驱动器(例如使用 NET USE 命令)命名的文件都需要在本地网络上发送,因此它们也在本地 Intranet 区域中。

默认值是为不安全的 Intranet 这种最坏情况而设置的。如果您的 Intranet 比较安全,您可以修改安全策略(用 CASPol 工具),给本地 Intranet 或其一部分(例如特定的计算机共享名)授予更多的权限。

如何编写代码,使它在安全系统停止该代码时运行?
当代码试图执行未经授权的操作时,将发生安全异常。权限是基于代码(尤其是其位置)来授予的。例如,从 Internet 中运行的代码所得到的权限比在本地计算机上运行的代码所得到的权限要少,这是因为经验证明,它的可靠性要低一些。因此,要运行由于安全异常而失败的代码,您必须增加授予它的权限。一个简单的方法是将代码移到更受信任的位置(例如本地文件系统)。但这种方法并不是在任何情况下都有效(Web 应用程序是一个很好的例子,企业网络上的 Intranet 应用程序是另一个例子)。因此,不要改变代码位置,而是通过更改安全策略给该位置授予更多的权限。请使用代码访问安全策略工具 (caspol.exe) 或图形化管理工具(在 Beta 2 和更高版本中可以得到)来执行此操作。如果您是代码的开发人员或发行者,您也可以对它进行数字签名,然后修改安全策略,给带有该数字签名的代码授予更多权限。但是,在执行上述任何操作时,请记住此代码被授予较少的权限,是因为它不是来自受信任的来源——在将代码移至本地计算机或更改安全策略以前,您应该确保这些代码不会执行恶意或损坏性的操作。

如何管理个人或企业计算机的安全性?
目前,CASPol 命令行工具是管理安全性的唯一方法。安全策略由两个级别组成:按计算机和按用户。我们计划在 .NET 框架第一版中提供全面的管理工具以及企业策略管理支持。

基于证据的安全性是如何与 Windows 2000 安全性配合工作的?
基于证据的安全性(基于授权码)能与 Windows 2000 安全性(基于登录身份标识)配合工作。例如,要访问一个文件,托管代码必须具有代码访问安全文件权限,也必须在具有 NTFS 文件访问权限的登录身份标识下运行。.NET 框架中包括的托管库也为基于角色的安全性提供了类。这些都使应用程序能够与 Windows 登录身份标识及用户组配合工作。