在 .NET 中使用非托管 (VB6) 代码






4.86/5 (7投票s)
在 .NET 中使用非托管代码
引言
在本文中,我将尝试解释
- 托管代码
- 非托管代码
- 运行时可调用包装器 (RCW)
本文将解释我们如何使用运行时可调用包装器在托管环境中调用非托管代码。
托管代码
托管代码是为托管运行时执行环境(如 .NET Framework 中的 通用语言运行时 (CLR))编写的代码。托管代码始终由托管运行时执行环境执行,而不是直接由操作系统执行。托管是指程序与运行时环境之间交换信息的一种方法。由于代码的执行由运行时环境管理,因此该环境可以保证代码将要做什么,并在执行任何代码之前提供必要的安全检查。出于相同的原因,托管代码还可以从运行时环境获得不同的服务,例如 垃圾回收、类型检查、异常处理、边界检查等。这样,托管代码就不必担心内存分配、类型安全等问题。以 Java、C#、VB.NET 等语言编写的应用程序都以一个管理执行的运行时环境为目标,而使用这些类型语言编写的代码则被称为托管代码。托管代码始终编译为中间语言(.NET Framework 中的 MSIL)。.NET Framework 用于编译托管代码的编译器将其编译为中间语言,并生成必要的 元数据,即描述中间语言中所有入口点和公开构造(例如,方法、属性)及其特性的符号信息。通用语言基础结构 (CLI) 标准描述了信息的编码方式,以运行时为目标编程语言会发出正确的编码。
在 .NET Framework 中,托管代码在 .NET Framework 的 CLR 中运行,并受益于 CLR 提供的服务。当我们编译托管代码时,代码会被编译为中间语言(MSIL),并创建一个可执行文件。当用户运行可执行文件时,CLR 的即时 (JIT) 编译器会将中间语言编译为特定于底层体系结构的本地代码。由于这种转换是由托管执行环境 (CLR) 完成的,因此托管执行环境可以保证代码的执行方式,因为它实际上可以对其进行推理。它可以插入陷阱和各种保护,如果它在沙盒环境中运行,它可以插入所有适当的垃圾回收钩子、异常处理、类型安全、数组边界、索引检查等等。
托管代码还提供平台独立性。由于托管代码首先被编译为中间语言,CLR 的 JIT 编译器会负责将此中间语言编译为特定于体系结构的指令。
非托管代码
直接由操作系统执行的代码称为非托管代码。通常,用 VB 6.0、C++、C 等编写的应用程序都是非托管代码的示例。非托管代码通常以处理器体系结构为目标,并且始终依赖于计算机体系结构。非托管代码始终编译为特定体系结构的目标,并且只能在预期的平台上运行。这意味着,如果您想在不同体系结构上运行相同的代码,则必须使用该特定体系结构重新编译代码。非托管代码始终编译为特定于体系结构的本地代码。当我们编译非托管代码时,它会被编译为二进制 X86 映像。此映像始终取决于编译代码的平台,并且无法在与编译代码的平台不同的其他平台上执行。非托管代码不会从托管执行环境获得任何服务。
在非托管代码中,内存分配、类型安全、安全性等都需要由开发人员负责。这使得非托管代码容易出现内存泄漏,如缓冲区溢出和指针覆盖等。
非托管可执行文件本质上是一个二进制映像,x86 代码,加载到内存中。程序计数器被放置在那里,操作系统就知道了这么多。在内存管理和端口 I/O 等方面有保护措施,但系统实际上并不知道应用程序在做什么。
运行时可调用包装器 (RCW)
通用语言运行时通过一个名为运行时可调用包装器 (RCW) 的代理公开 COM 对象。尽管 RCW 对 .NET 客户端而言似乎是一个普通对象,但它的主要功能是在 .NET 客户端和 COM 对象之间进行封送调用。
运行时为每个 COM 对象创建一个 RCW,无论该对象有多少引用。运行时为每个对象在每个进程中维护一个 RCW。如果您在一个应用程序域或单元中创建了 RCW,然后将引用传递到另一个应用程序域或单元,则将使用第一个对象的代理。如下图所示,任意数量的托管客户端都可以引用公开 INew
和 INewer
接口的 COM 对象。
利用从类型库派生的元数据,运行时会创建被调用的 COM 对象和该对象的包装器。每个 RCW 都维护一个指向其包装的 COM 对象上的接口指针的缓存,并在不再需要 RCW 时释放其对 COM 对象上的引用。运行时会对 RCW 执行垃圾回收。
除其他活动外,RCW 还代表被包装对象在托管代码和非托管代码之间进行数据封送。具体来说,当客户端和服务器对它们之间传递的数据表示不同时,RCW 会为方法参数和方法返回值提供封送。
标准包装器强制执行内置封送规则。例如,当 .NET 客户端将 String
类型作为参数传递给非托管对象时,包装器会将 string
转换为 BSTR
类型。如果 COM 对象将 BSTR
返回给其托管调用者,则调用者会收到一个 String
。客户端和服务器都发送和接收对它们来说熟悉的数据。其他类型则不需要转换。例如,标准包装器始终会在托管代码和非托管代码之间传递 4 字节整数,而无需转换类型。
步骤 1:使用 Visual Basic 6.0 创建 ActiveX DLL
在此项目中,我使用 Visual Basic 6.0(非托管代码)创建了一个 ActiveX DLL,它将接收来自客户端(托管代码)的请求,并在处理后,非托管代码将响应托管代码。对于不知道如何使用 Visual Basic 6.0 创建 DLL 的初学者,我将尽力逐步解释。
- 使用 Visual Basic 6.0 创建一个 ActiveX 项目。
- 默认情况下,它会创建 Class1.cls 和 Project1.vbp。从属性窗口将 Class1.cls 重命名为 ClsName.cls,将 Project1.vbp 重命名为 ProjClsName.vbp。
- 如果您想在此项目中创建 COM+ 应用程序,请转到 Project-->References 并选择 "COM+ Services Template Library"。
- 现在编写一个函数,该函数从托管代码(VS.NET 应用程序)接收 Name,然后该函数将 Name 与一些附加的
string
连接后返回。Function Name(aName As String) MsgBox ("Your Name is " & aName) End Function
- 编译您的项目。
- 成功编译代码后,下一步是创建项目的 DLL。为此,请转到 File--> Make ProjClsName.dll,如下图所示。这将在您的应用程序文件夹中创建 DLL。您还可以在
Create
之前为 DLL 指定版本、注释等。
步骤 2:创建 DLL 的运行时可调用包装器
创建 DLL 后,我们的下一步是创建 DLL 的运行时可调用包装器,以便我们可以将此 DLL 与我们的 .NET 应用程序一起使用。生成主互操作程序集的 JIT 方式是使用 Tlbimp.exe(类型库导入器)。要使用 TLBIMP.EXE 生成主互操作程序集。
-
在 Visual Studio 2008 命令提示符下,键入
tlbimp tlbfile /primary /keyfile:filename /out:assemblyname
在此命令中,tlbfile 是包含 COM 类型库的文件,filename 是包含密钥对的容器或文件的名称,assemblyname 是要使用强名称签名的程序集名称。
主互操作程序集只能引用其他主互操作程序集。如果您的程序集引用了第三方 COM 类型库中的类型,则必须先从发布者获取主互操作程序集,然后才能生成自己的主互操作程序集。如果您是发布者,则必须在生成引用的主互操作程序集之前为此依赖的类型库生成主互操作程序集。
创建运行时可调用包装器的简单语法是
tlbimp C:\Source.dll /out:C:\Destination.dll
步骤 3:托管与非托管代码之间的通信
使用 Visual Basic 6.0 创建 DLL 并创建运行时可调用包装器后,我们的下一步是使用 Visual Studio .NET(客户端应用程序)创建一个应用程序,该应用程序将加载此运行时可调用包装器(导入的 DLL,即 ComClsName.dll)并进行使用。为此:
- 打开 Visual Studio.NET。
- 选择您喜欢的语言。我选择了 C#.NET。这样,我只想解释 C#.NET 如何与 Visual Basic 6.0 代码交互。
- 我在这里创建一个简单的 Windows 应用程序,其中包含一个简单的文本框,用于接收用户的姓名,以及一个简单的按钮,用于发送用户输入的
姓名到我们的非托管代码(ComClsName.dll)。 - 创建表单后,现在我们需要将 comClsName.dll 添加到引用中,如下所示
- 转到 Visual Basic 6.0 项目的应用程序文件夹路径,您在通过 TLBIMP.EXE 创建运行时可调用包装器后放置 ComClsName.dll 的位置。
- 选择 comClsName.dll,然后单击确定将其添加到 Windows 窗体项目的引用文件夹中。
- 将 ComClsName.dll 添加到项目中后,请在
代码文件中使用“using
”关键字添加 comClsName.dll 的命名空间。using ComClsName;
- 现在我们需要在按钮的 Click 事件中添加一些代码。当用户在文本框中输入姓名并单击按钮时,我们的 .NET 应用程序将调用 comClsName.dll 的
Name
函数,并将返回与private void button1_Click(object sender, EventArgs e) { ClsName UnManagedCode = new ClsName(); string aName = this.textBox1.Text; UnManagedCode.Name(ref aName); }
- 在此,我初始化了类
ClsName
的实例。String aName
将接收用户在文本框中提供的姓名,并以ref
参数形式调用 comClsName.dll 的Name
函数。ref
关键字导致参数按引用传递。 - 现在生成并运行项目。
- 当您单击“Call unmanaged Function”按钮时,它将启动 comClsName.dll 中声明的非托管类
ClsName
,将文本框中提供的姓名作为参数传递给类ClsName
的Name
函数,并显示一个消息框,其中包含 Visual Basic 项目的Name
函数中定义的文本“YOUR NAME IS
”,如下所示。
摘要
在本文中,我尝试解释了托管代码、非托管代码、运行时可调用包装器,使用 TLBIMP.EXE 创建 DLL 的 RCW,以及托管代码(.NET 代码)如何与非托管代码(Visual Basic 6.0、Visual C++ 6.0)交互等。
历史
- 2011 年 2 月 5 日:初始帖子