非托管代码互操作性详解
发布时间: 2025-08-17 00:24:32 阅读量: 1 订阅数: 3 

# 非托管代码互操作性详解
## 1. 非托管代码互操作性概述
Microsoft .NET Framework 是一个功能强大的平台,它集成了托管运行时(公共语言运行时,即 CLR)、用于托管 Web 应用程序的平台(Microsoft ASP.NET)以及用于构建各类应用程序的广泛类库。然而,尽管 .NET Framework 功能丰富,但它并未涵盖非托管代码中的所有特性。目前,.NET Framework 并未包含 Win32 API 中的所有功能,而且许多企业仍在使用基于 COM 的语言(如 Microsoft Visual Basic 6 和 Visual C++ 6)构建的复杂专有解决方案。
不过,在迁移到 .NET 平台时,企业无需放弃现有的代码库。.NET Framework 具备互操作性特性,允许在 .NET 应用程序中使用遗留代码,甚至能像使用 COM 组件一样访问 .NET 程序集。以下是一些常见的操作及解决方法:
- 调用 DLL 中定义的函数、获取控件或窗口的句柄、调用使用结构的非托管函数、调用非托管回调函数以及检索非托管错误信息。
- 在 .NET 应用程序中使用 COM 组件、快速释放 COM 组件以及使用可选参数。
- 在 .NET 客户端中使用 ActiveX 控件。
- 通过 COM 公开 .NET 程序集的功能。
## 2. 调用非托管 DLL 中的函数
### 2.1 问题描述
需要调用 DLL 中的 C 函数,该函数可能是 Win32 API 的一部分,也可能是自己的遗留代码。
### 2.2 解决方案
在 C# 代码中声明一个用于访问非托管函数的方法。将此方法声明为 extern 和 static,并应用 System.Runtime.InteropServices.DllImportAttribute 属性来指定 DLL 文件和非托管函数的名称。
### 2.3 工作原理
要使用外部库中的 C 函数,只需正确声明它。公共语言运行时(CLR)会自动处理其余部分,包括在调用函数时将 DLL 加载到内存中,并将参数从 .NET 数据类型封送到 C 数据类型。支持这种跨平台执行的 .NET 服务称为 PInvoke(平台调用),该过程通常是无缝的。不过,在某些情况下,可能需要做更多工作,例如支持内存中的结构、回调或可变字符串。
PInvoke 常用于访问 Win32 API 中的功能,特别是 .NET Framework 中托管类集未包含的 Win32 特性。Win32 API 由三个核心库组成:
- Kernel32.dll:包含特定于操作系统的功能,如进程加载、上下文切换以及文件和内存 I/O。
- User32.dll:包含用于操作窗口、菜单、对话框、图标等的功能。
- GDI32.dll:包含用于在窗口、菜单和控件表面直接绘图以及打印的图形功能。
### 2.4 代码示例
```csharp
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace Apress.VisualCSharpRecipes.Chapter12
{
class Recipe12_01
{
// Declare the unmanaged functions.
[DllImport("kernel32.dll", EntryPoint = "GetPrivateProfileString")]
private static extern int GetPrivateProfileString(string lpAppName,
string lpKeyName, string lpDefault, StringBuilder lpReturnedString,
int nSize, string lpFileName);
[DllImport("kernel32.dll", EntryPoint = "WritePrivateProfileString")]
private static extern bool WritePrivateProfileString(string lpAppName,
string lpKeyName, string lpString, string lpFileName);
static void Main(string[] args)
{
string val;
// Obtain current value.
val = GetIniValue("SampleSection", "Key1", "\\initest.ini");
Console.WriteLine("Value of Key1 in [SampleSection] is: "
+ val);
// Write a new value.
WriteIniValue("SampleSection", "Key1", "New Value",
"\\initest.ini");
// Obtain the new value.
val = GetIniValue("SampleSection", "Key1", "\\initest.ini");
Console.WriteLine("Value of Key1 in [SampleSection] is now: "
+ val);
// Write original value.
WriteIniValue("SampleSection", "Key1", "Value1",
"\\initest.ini");
// Wait to continue.
Console.WriteLine(Environment.NewLine);
Console.WriteLine("Main method complete. Press Enter.");
Console.ReadLine();
}
public static string GetIniValue(string section, string key,
string filename)
{
int chars = 256;
StringBuilder buffer = new StringBuilder(chars);
string sDefault = "";
if (GetPrivateProfileString(section, key, sDefault,
buffer, chars, filename) != 0)
{
return buffer.ToString();
}
else
{
return null;
}
}
public static bool WriteIniValue(string section, string key,
string value, string filename)
{
return WritePrivateProfileString(section, key, value, filename);
}
}
}
```
### 2.5 使用方法
可以很容易地测试这个程序。首先,在应用程序文件夹中创建如下所示的 inittest.ini 文件:
```plaintext
[SampleSection]
Key1=Value1
```
然后,执行 Recipe12 - 01.exe,将得到如下输出:
```plaintext
Value of Key1 in [SampleSection] is: Value1
Value of Key1 in [SampleSection] is now: New Value
Main method complete. Press Enter.
```
## 3. 获取控件、窗口或文件的句柄
### 3.1 问题描述
需要调用一个需要控件、窗口或文件句柄的非托管函数。
### 3.2 解决方案
许多类(包括所有从 Control 派生的类和 FileStream 类)通过名为 Handle 的属性以 IntPtr 形式返回它们所包装的非托管 Windows 对象的句柄。其他类也提供类似信息,例如 System.Diagnostics.Process 类除了提供 Handle 属性外,还提供了 Process.MainWindowHandle 属性。
### 3.3 工作原理
.NET Framework 不会隐藏诸如控件和窗口使用的操作系统句柄等底层细节。虽然通常不会使用这些信息,但在需要调用需要这些信息的非托管函数时,可以检索它们。例如,许多 Microsoft Windows API 函数需要控件或窗口句柄。
### 3.4 代码示例
```csharp
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Text;
namespace Apress.VisualCSharpRecipes.Chapter12
{
public partial class ActiveWindowInfo : Form
{
public ActiveWindowInfo()
{
InitializeComponent();
}
// Declare external functions.
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
private static extern int GetWindowText(IntPtr hWnd,
StringBuilder text, int count);
private void tmrRefresh_Tick(object sender, EventArgs e)
{
int chars = 256;
StringBuilder buff = new StringBuilder(chars);
// Obtain the handle of the active window.
IntPtr handle = GetForegroundWindow();
// Update the controls.
if (GetWindowText(handle, buff, chars) > 0)
{
lblCaption.Text = buff.ToString();
lblHandle.Text = handle.ToString();
if (handle == this.Handle)
{
lblCurrent.Text = "True";
}
else
{
lblCurrent.Text = "False";
}
}
}
}
}
```
### 3.5 注意事项
Windows 窗体基础结构会透明地管理窗体和控件的窗口句柄。更改它们的某些属性可能会迫使 CLR 在后台创建一个新的本机窗口,从而导致新的句柄被包装。因此,在使用句柄之前,应始终重新检索它,而不是将其存储在成员变量中很长时间。
## 4. 调用使用结构的非托管函数
### 4.1 问题描述
需要调用一个接受结构作为参数的非托管函数。
### 4.2 解决方案
在 C# 代码中定义该结构。使用 System.Runtime.InteropServices.StructLayoutAttribute 属性来配置结构字段在内存中的布局。如果需要确定非托管结构的字节大小,可以使用 System.Runtime.Interop.Marshal 类的静态 SizeOf 方法。
### 4.3 工作原理
在纯 C# 代码中,无法直接控制类型字段在内存分配后的布局。相反,CLR 会自由排列字段以优化性能,特别是在垃圾回收期间移动内存时。这在与期望结构按顺序布局在内存中的遗留 C 函数交互时可能会导致问题。幸运的是,.NET Framework 允许使用 StructLayoutAttribute 属性来解决这个问题,该属性可以指定给定类或结构的成员在内存中的排列方式。
### 4.4 代码示例
```csharp
using System;
using System.Runtime.InteropServices;
namespace Apress.VisualCSharpRecipes.Chapter12
{
class Recipe12_03
{
// Declare the external function.
[DllImport("kernel32.dll")]
public static extern bool GetVersionEx([In, Out] OSVersionInfo osvi);
static void Main(string[] args)
{
OSVersionInfo osvi = new OSVersionInfo();
osvi.dwOSVersionInfoSize = Marshal.SizeOf(osvi);
// Obtain the OS version information.
GetVersionEx(osvi);
// Display the version information.
Console.WriteLine("Class size: " + osvi.dwOSVersionInfoSize);
Console.WriteLine("Major Version: " + osvi.dwMajorVersion);
Console.WriteLine("Minor Version: "
```
0
0
相关推荐










