obsidian/笔记文件/2.笔记/InteropServices.md
2025-03-26 00:02:56 +08:00

5.4 KiB
Raw Permalink Blame History

#unity/日常积累

InteropServices 是 .NET 框架中 System.Runtime.InteropServices 命名空间的一部分,用于处理 互操作性Interop也就是 .NET 程序与非托管代码通常是原生的C、C++代码或者Win32 API之间的交互。这允许你在托管环境中如 C#)调用非托管代码或使用非托管资源。

InteropServices 提供了许多工具和功能来支持这种交互,包括 COM 互操作、平台调用P/Invoke、处理非托管资源等。以下是一些常见的使用场景和类/功能:

1. Platform Invocation (P/Invoke)

P/Invoke 是 InteropServices 中用于调用非托管代码(例如 C、C++ 编写的 DLL的功能。通过 P/Invoke可以从托管代码中调用非托管的函数。

示例:

假设你想调用 Windows API 函数 MessageBox,可以使用 P/Invoke 来声明和调用这个函数。

using System;
using System.Runtime.InteropServices;

class Program
{
    // 声明一个外部的非托管函数
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern int MessageBox(IntPtr hWnd, String text, String caption, uint type);

    static void Main()
    {
        // 调用非托管的 MessageBox 函数
        MessageBox(IntPtr.Zero, "Hello, World!", "Message", 0);
    }
}

2. COM Interop

COM组件对象模型是一种 Microsoft 技术,允许应用程序通过接口进行交互。InteropServices 提供了将托管代码与 COM 组件集成的功能,帮助 .NET 应用调用 COM 对象,或者将 .NET 对象暴露为 COM 对象。

示例:

通过 Marshal.GetActiveObject 调用一个已运行的 COM 对象,例如 Microsoft Excel。

using System;
using System.Runtime.InteropServices;

class Program
{
    static void Main()
    {
        try
        {
            // 获取一个正在运行的 Excel 实例
            dynamic excel = Marshal.GetActiveObject("Excel.Application");
            excel.Visible = true;
        }
        catch (COMException ex)
        {
            Console.WriteLine("Error: " + ex.Message);
        }
    }
}

3. Marshalling

InteropServices 提供的 Marshal 类帮助管理托管和非托管内存之间的数据转换。因为托管代码和非托管代码的内存布局和管理方式不同某些情况下需要进行“封送处理”Marshalling以便正确地传递数据。

示例:

将托管的结构体转换为非托管内存块,或者从非托管内存读取数据。

using System;
using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Sequential)]
struct Point
{
    public int X;
    public int Y;
}

class Program
{
    static void Main()
    {
        Point point = new Point { X = 10, Y = 20 };
        
        // 将托管结构体封送到非托管内存
        IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(point));
        Marshal.StructureToPtr(point, ptr, false);
        
        // 从非托管内存读取回托管结构体
        Point newPoint = Marshal.PtrToStructure<Point>(ptr);
        Console.WriteLine($"X: {newPoint.X}, Y: {newPoint.Y}");
        
        // 释放非托管内存
        Marshal.FreeHGlobal(ptr);
    }
}

4. 非托管资源的处理

使用 GCHandle 结构体,可以将托管对象转换为不可移动的句柄,允许非托管代码使用它。这样在垃圾回收期间不会移动对象的内存地址。

using System;
using System.Runtime.InteropServices;

class Program
{
    static void Main()
    {
        string managedString = "Hello, Interop!";

        // 将托管对象固定在内存中
        GCHandle handle = GCHandle.Alloc(managedString, GCHandleType.Pinned);

        // 获取该对象的内存地址
        IntPtr pointer = handle.AddrOfPinnedObject();

        Console.WriteLine("Memory Address: " + pointer);

        // 释放句柄,允许对象移动或被回收
        handle.Free();
    }
}

5. 安全处理非托管代码异常

InteropServices 提供一些机制来处理非托管代码中的异常。例如,SEHException 可以捕获结构化异常处理中的错误。

try
{
    // 调用可能会抛出非托管异常的函数
}
catch (System.Runtime.InteropServices.SEHException sehEx)
{
    Console.WriteLine("Caught unmanaged exception: " + sehEx.Message);
}

6. 调用 WinRT 组件

InteropServices 还可以帮助托管代码调用 WinRTWindows Runtime组件使用类似于调用 COM 对象的方式。


常用类和功能:

  • Marshal:提供了一些用于分配、释放、封送内存的静态方法,帮助托管代码与非托管代码互操作。
  • DllImportAttribute:用于 P/Invoke声明非托管的函数。
  • GCHandle:用于在托管堆中固定对象的句柄,防止对象在调用非托管代码时被垃圾回收。
  • SEHException:处理非托管代码中的结构化异常。
  • COMException:处理与 COM 互操作相关的异常。
  • CoCreateInstance:用于从托管代码创建 COM 对象。

总结:

System.Runtime.InteropServices 是 .NET 的一个核心命名空间,它支持 .NET 应用与非托管代码(如 C++ DLL、Windows API、COM之间的互操作。通过 P/Invoke、COM 互操作、封送处理等功能,开发者可以在托管代码中灵活使用现有的非托管代码库,或在新的 .NET 应用中复用老的系统组件。