DllImport 特性

DllImport(“__Internal”)

`DllImport(“__Internal”)] 是 C# 中用于调用本地代码(通常是 C/C++ 编写的代码)的特性(Attribute)。它主要用于 Unity 和其他 .NET 环境中,允许开发者从托管代码(C#)调用非托管代码(C/C++)。以下是对其作用的详细解释:

1. 基本概念

  • DllImport:这是一个特性,用于指示 C# 编译器在运行时从指定的动态链接库(DLL)中导入函数。它通常用于调用 Windows API 或其他本地库中的函数。

  • __Internal:在 Unity 中,__Internal 是一个特殊的标识符,表示要调用的函数是在 Unity 的内部实现中,而不是在外部 DLL 中。这通常用于 Unity 的插件或原生代码。

2. 使用场景

[DllImport("__Internal")] 主要用于以下场景:

  • 调用 Unity 的原生插件:当你在 Unity 中编写原生插件(例如,使用 C/C++ 编写的代码)时,可以使用这个特性来调用这些插件中的函数。

  • 与 JavaScript 交互:在 WebGL 平台上,__Internal 可以用于调用 JavaScript 函数。Unity 会将 C# 代码编译为 JavaScript,并允许通过这个特性与 JavaScript 进行交互。

3. 示例代码

以下是一个简单的示例,展示如何使用 [DllImport("__Internal")] 来调用 Unity 中的原生函数:

using System.Runtime.InteropServices;
using UnityEngine;

public class MyPlugin : MonoBehaviour
{
    // 声明一个外部函数
    [DllImport("__Internal")]
    private static extern void MyNativeFunction();

    void Start()
    {
        // 调用原生函数
        MyNativeFunction();
    }
}

在这个示例中,MyNativeFunction 是一个在 Unity 的原生代码中实现的函数。通过 [DllImport("__Internal")],我们可以在 C# 中调用它。

4. 注意事项

  • 平台限制[DllImport("__Internal")] 主要用于 WebGL 和 Unity 的原生插件。在其他平台(如 Windows、macOS、Android 等)上,通常会使用具体的 DLL 名称(如 DllImport("MyLibrary"))来导入外部库。

  • 函数签名:确保在 C# 中声明的函数签名与原生代码中的函数签名匹配,包括参数类型和返回类型。

  • 性能考虑:调用非托管代码可能会引入性能开销,因此在性能敏感的场景中要谨慎使用。

总结

[DllImport("__Internal")] 是一个强大的特性,允许开发者在 Unity 中调用原生代码或 JavaScript 函数。它在实现与底层系统交互、优化性能或扩展功能时非常有用。理解其用法和限制可以帮助开发者更有效地利用 Unity 的功能。

转换

在 Unity 中,使用 [DllImport("__Internal")] 特性时,编译器会将其转换为特定的调用约定,以便在运行时能够正确地调用相应的原生代码或 JavaScript 函数。以下是一些关于这个特性的详细信息,以及它在编译和运行时的转换过程。

1. 转换过程

当你在 C# 代码中使用 [DllImport("__Internal")] 时,编译器会执行以下操作:

  • 函数声明:编译器会将该函数声明为外部函数,表示它的实现位于非托管代码中。这个声明会包含函数的名称、参数类型和返回类型。

  • 调用约定:编译器会根据平台和目标环境(如 WebGL)生成适当的调用约定。这意味着它会处理参数的传递方式、返回值的处理等。

2. 在 WebGL 中的特定行为

在 Unity 的 WebGL 构建中,__Internal 特别用于调用 JavaScript 函数。以下是一些关键点:

  • JavaScript 函数:当你在 C# 中使用 [DllImport("__Internal")] 来声明一个函数时,Unity 会期望在 JavaScript 代码中有一个同名的函数。这个函数可以在 Unity 的 WebGL 模块中被调用。

  • 编译为 JavaScript:Unity 会将 C# 代码编译为 JavaScript,并在运行时通过 Emscripten 生成的 JavaScript 代码与原生 JavaScript 函数进行交互。

3. 示例

假设你在 C# 中有以下代码:

using System.Runtime.InteropServices;
using UnityEngine;

public class MyPlugin : MonoBehaviour
{
    [DllImport("__Internal")]
    private static extern void MyJavaScriptFunction();

    void Start()
    {
        MyJavaScriptFunction();
    }
}

在这个例子中,MyJavaScriptFunction 是一个在 JavaScript 中实现的函数。Unity 会在构建 WebGL 项目时,确保这个函数在生成的 JavaScript 代码中可用。

4. 运行时行为

  • 调用:当 MyJavaScriptFunction 被调用时,Unity 会通过 JavaScript 的调用机制执行该函数。这意味着你可以在 JavaScript 中实现复杂的逻辑,并通过 C# 代码与之交互。

  • 参数传递:如果函数需要参数,Unity 会负责将 C# 中的参数转换为 JavaScript 中的相应类型。这通常涉及到基本数据类型(如整数、浮点数、字符串等)的转换。

5. 注意事项

  • 函数名匹配:确保在 JavaScript 中实现的函数名称与 C# 中声明的函数名称完全匹配,包括大小写。

  • 错误处理:在调用 JavaScript 函数时,确保处理可能的错误情况,因为 JavaScript 代码可能会抛出异常或返回错误。

  • 性能考虑:频繁地在 C# 和 JavaScript 之间进行调用可能会影响性能,因此在设计时要考虑调用的频率和复杂性。

总结

[DllImport("__Internal")] 在 Unity 中用于调用原生代码或 JavaScript 函数。编译器会将其转换为适当的调用约定,以便在运行时能够正确地执行这些函数。在 WebGL 环境中,它特别用于与 JavaScript 进行交互,允许开发者利用 JavaScript 的功能和库。理解这一点可以帮助开发者更有效地在 Unity 中实现跨语言的功能。

DllImport

在 C# 中,DllImport 是一个特性(Attribute),用于声明外部函数的调用,通常是从非托管代码(如 C/C++ 编写的动态链接库 DLL)中导入函数。通过使用 DllImport,你可以在 C# 中调用这些外部函数,从而实现与底层系统或其他库的交互。

使用 DllImport 的基本步骤

  1. 引入命名空间
    你需要引入 System.Runtime.InteropServices 命名空间,以便使用 DllImport 特性。

  2. 声明外部函数
    使用 DllImport 特性来声明你想要调用的外部函数。

  3. 调用函数
    在你的 C# 代码中调用这个函数,就像调用普通的 C# 方法一样。

示例代码

以下是一个简单的示例,展示如何使用 DllImport 来调用 Windows API 中的 MessageBox 函数:

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(IntPtr.Zero, "Hello, World!", "My Message Box", 0);
    }
}

代码解析

  • [DllImport("user32.dll", CharSet = CharSet.Auto)]

    • user32.dll 是 Windows 系统中的一个动态链接库,包含了许多与用户界面相关的函数。
    • CharSet.Auto 指定字符集,通常用于处理字符串参数。
  • public static extern int MessageBox(...)

    • extern 关键字表示这个方法是在外部定义的。
    • 方法的参数和返回值类型需要与 DLL 中的函数签名匹配。
  • MessageBox(IntPtr.Zero, "Hello, World!", "My Message Box", 0);

    • 调用 MessageBox 函数,显示一个消息框。

注意事项

  1. 数据类型匹配
    确保 C# 中的数据类型与 DLL 中的函数参数和返回值类型匹配。例如,C# 中的 int 对应 C/C++ 中的 int,C# 中的 string 通常对应 C/C++ 中的 char*

  2. 字符集
    使用 CharSet 属性来指定字符集,确保字符串在托管和非托管代码之间正确传递。

  3. 调用约定
    如果外部函数使用了特定的调用约定(如 stdcallcdecl),你需要在 DllImport 特性中指定 CallingConvention 属性。

    [DllImport("user32.dll", CallingConvention = CallingConvention.StdCall)]
    
  4. 错误处理
    调用外部函数时,可能会遇到错误或异常,确保在调用时进行适当的错误处理。

  5. 平台依赖性
    使用 DllImport 使得代码与特定平台(如 Windows)紧密耦合,因此在跨平台开发时需要谨慎。

总结

DllImport 是 C# 中与非托管代码交互的重要工具,允许开发者调用外部 DLL 中的函数。通过正确使用 DllImport,你可以扩展 C# 应用程序的功能,利用底层系统资源或其他库的能力。

DllImport 参数

在 C# 中,使用 DllImport 特性时,可以为其指定多个参数,以便正确地调用外部 DLL 中的函数。以下是 DllImport 特性的一些常用参数及其说明:

常用参数

  1. Value:

    • 描述:指定要导入的 DLL 的名称。
    • 示例[DllImport("user32.dll")]
  2. CharSet:

    • 描述:指定字符串参数的字符集。可以是以下值之一:
      • CharSet.Auto:根据平台自动选择字符集(Windows 上通常是 Unicode)。
      • CharSet.Ansi:使用 ANSI 字符集。
      • CharSet.Unicode:使用 Unicode 字符集。
    • 示例[DllImport("user32.dll", CharSet = CharSet.Auto)]
  3. CallingConvention:

    • 描述:指定调用约定。可以是以下值之一:
      • CallingConvention.Winapi:使用平台默认的调用约定。
      • CallingConvention.StdCall:使用 stdcall 调用约定(常用于 Windows API)。
      • CallingConvention.Cdecl:使用 cdecl 调用约定。
      • CallingConvention.ThisCall:用于 C++ 类成员函数。
    • 示例[DllImport("user32.dll", CallingConvention = CallingConvention.StdCall)]
  4. EntryPoint:

    • 描述:指定 DLL 中要调用的函数名称。如果函数名称与 C# 方法名称不同,可以使用此参数。
    • 示例[DllImport("user32.dll", EntryPoint = "MessageBoxA")]
  5. SetLastError:

    • 描述:指示是否在调用后设置最后一个错误代码。通常用于 Windows API 函数。
    • 示例[DllImport("user32.dll", SetLastError = true)]
  6. PreserveSig:

    • 描述:指示是否保留原始函数的返回值类型。如果设置为 false,则会将返回值转换为 void,并在发生错误时抛出异常。
    • 示例[DllImport("user32.dll", PreserveSig = false)]

示例代码

以下是一个完整的示例,展示如何使用 DllImport 特性及其参数:

using System;
using System.Runtime.InteropServices;

class Program
{
    // 声明外部函数
    [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
    public static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type);

    static void Main()
    {
        // 调用外部函数
        int result = MessageBox(IntPtr.Zero, "Hello, World!", "My Message Box", 0);
        
        // 检查错误
        if (result == 0)
        {
            int errorCode = Marshal.GetLastWin32Error();
            Console.WriteLine($"Error: {errorCode}");
        }
    }
}

代码解析

  • [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]

    • 指定要调用的 DLL 是 user32.dll
    • 使用自动字符集。
    • 使用 stdcall 调用约定。
    • 设置最后一个错误代码。
  • MessageBox 方法

    • 该方法的参数与 Windows API 中的 MessageBox 函数相匹配。

总结

DllImport 特性允许你从外部 DLL 中导入函数,并通过参数配置调用的细节。正确使用这些参数可以确保与非托管代码的交互顺利进行,避免潜在的错误和性能问题。

DllImport 参数具体案例说明

下面是一些具体的案例,展示如何使用 DllImport 特性及其参数,以便从外部 DLL 中调用函数。我们将通过几个示例来说明每个参数的使用。

示例 1:调用 Windows API 的 MessageBox 函数

代码示例
using System;
using System.Runtime.InteropServices;

class Program
{
    // 声明外部函数
    [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
    public static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type);

    static void Main()
    {
        // 调用外部函数
        MessageBox(IntPtr.Zero, "Hello, World!", "My Message Box", 0);
    }
}
参数说明
  • "user32.dll":指定要调用的 DLL 名称。
  • CharSet.Auto:根据平台自动选择字符集(在 Windows 上通常是 Unicode)。
  • CallingConvention.StdCall:指定调用约定为 stdcall,这是 Windows API 的标准调用约定。

示例 2:使用 SetLastError 获取错误代码

代码示例
using System;
using System.Runtime.InteropServices;

class Program
{
    // 声明外部函数
    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr LoadLibrary(string lpFileName);

    static void Main()
    {
        // 尝试加载一个不存在的 DLL
        IntPtr handle = LoadLibrary("nonexistent.dll");

        // 检查是否加载成功
        if (handle == IntPtr.Zero)
        {
            // 获取最后一个错误代码
            int errorCode = Marshal.GetLastWin32Error();
            Console.WriteLine($"Error loading library: {errorCode}");
        }
        else
        {
            Console.WriteLine("Library loaded successfully.");
        }
    }
}
参数说明
  • "kernel32.dll":指定要调用的 DLL 名称。
  • SetLastError = true:指示在调用后设置最后一个错误代码,以便后续调用 Marshal.GetLastWin32Error() 获取错误信息。

示例 3:调用 C 函数并指定 EntryPoint

假设你有一个 C 函数,名称为 Add,但在 DLL 中它被编译为 AddNumbers。你可以使用 EntryPoint 指定要调用的函数名称。

C 代码(假设已编译为 MyMath.dll)
// MyMath.c
extern "C" __declspec(dllexport) int Add(int a, int b) {
    return a + b;
}
C# 代码示例
using System;
using System.Runtime.InteropServices;

class Program
{
    // 声明外部函数
    [DllImport("MyMath.dll", EntryPoint = "Add", CallingConvention = CallingConvention.Cdecl)]
    public static extern int AddNumbers(int a, int b);

    static void Main()
    {
        int result = AddNumbers(5, 3);
        Console.WriteLine($"Result: {result}");
    }
}
参数说明
  • "MyMath.dll":指定要调用的 DLL 名称。
  • EntryPoint = "Add":指定 DLL 中的函数名称为 Add,尽管在 C# 中我们使用 AddNumbers 作为方法名。
  • CallingConvention.Cdecl:指定调用约定为 cdecl,这是 C/C++ 的标准调用约定。

示例 4:使用 PreserveSig

在某些情况下,你可能希望保留原始函数的返回值类型,而不是将其转换为 void。这可以通过 PreserveSig 参数实现。

C 代码(假设已编译为 MyLibrary.dll)
// MyLibrary.c
extern "C" __declspec(dllexport) int MyFunction() {
    return -1; // 返回错误代码
}
C# 代码示例
using System;
using System.Runtime.InteropServices;

class Program
{
    // 声明外部函数
    [DllImport("MyLibrary.dll", PreserveSig = true)]
    public static extern int MyFunction();

    static void Main()
    {
        int result = MyFunction();
        Console.WriteLine($"Result: {result}");
    }
}
参数说明
  • "MyLibrary.dll":指定要调用的 DLL 名称。
  • PreserveSig = true:保留原始函数的返回值类型,允许直接获取返回值。

总结

通过这些示例,你可以看到如何使用 DllImport 特性及其参数来调用外部 DLL 中的函数。每个参数都有其特定的用途,确保你能够正确地与非托管代码进行交互。根据具体的需求,选择合适的参数配置可以帮助你避免潜在的错误和性能问题。

你可能感兴趣的:(编译原理,DllImport)