Unity 与 .so\.a\.dll库文件、 C\C++\OC\Java 交互

来源:互联网 发布:淘宝真丝针织背心 编辑:程序博客网 时间:2024/05/19 23:09

简介

通用语言基础架构(CLI) 的设计思想是 代码间更容易的复用或者相互调用。
我们要使用 其他语言打包成的 库文件时,只需 创建 DllImport 的来声明。
需要引入 System.Runtime.InteropServices 命名空间。
形如:

 [DllImport ("libc.so")] private static extern int getpid ();
  1. 如果运行平台上有libc.so 将会调用 POSIX getpid(2)
  2. 如果不存在 getpid () 方法,将会 抛出 EntryPointNotFoundException 异常。
  3. 如果libc.so无法被加载,则抛出 DllNotFoundException 异常。

总结调用有三要点

  1. 指明要抵用的库文件名称 (如libc.so)
  2. 确定要调用的函数名 (如getpid () )
  3. 传递参数

然而参数的传递有很多复杂的情况,如String\Sturct\IntPtr ...

库文件的加载

库文件的加载是分平台处理的。

Windows DLL Search Path

按照以下顺序搜索

  1. 被加载的应用程序的目录
  2. 当前目录
  3. 系统目录,GetSystemDirectory() 来获取系统目录
  4. 16-bit 系统目录
  5. Windows 目录,GetWindowsDirectory() 来获取
  6. PATH环境变量中列出的目录。

当然,事实上并非这么简单。在实践中,System目录实际上是%WINDIR%\system32,除了在Windows 9 x平台%WINDIR%\system。16位系统目录通常是%WINDIR%\system,但不认为是一个单独的搜索目录在Windows 9 x平台。
此外,在Windows Server 2003和Windows XP SP1,注册表键入HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode改变上述排序。如果这个1(默认),然后搜索当前目录是在SystemWindows目录。这是一个安全特性(它可以防止木马库被载入代替,例如,OLE32.DLL),但它上面的列表变成:1,3,4,5,2,6

See also

Linux Shared Library Search Path

共享库搜索按照以下顺序:

  1. 以冒号分隔的目录列表在用户的LD_LIBRARY_PATH环境变量。这是一个经常使用的方法,允许本地共享库来发现一个CLI程序。
  2. /etc/ld.so.cache库缓存列表。/etc/ld.so.cache通过编辑/etc/ld.so.conf创建缓存配置和runningldconfig(8)。编辑/etc/ld.so.conf额外配置的首选方法是搜索目录,而不是使用LD_LIBRARY_PATH,因为这是更安全(这是木马库进入/etc/ld.so.cache比将它插入到LD_LIBRARY_PATH更加困难)。
  3. /lib/usr/lib

As a Mono extension, if the library being loaded is __Internal, then the main program is searched for method symbols. This is equivalent to calling dlopen(3) with a filename of NULL. This allows you to P/Invoke methods that are within an application that is embedding Mono.

See also

Mac OS X Framework and .dylib Search Path

顺序如下

  1. 以冒号分隔的目录列表在用户的DYLD_FRAMEWORK_PATH环境变量。
  2. 以冒号分隔的目录列表在用户的DYLD_LIBRARY_PATH环境变量。
  3. 以冒号分隔的目录列表在用户的DYLD_FALLBACK_FRAMEWORK_PATH环境变量,默认目录:

    • ~/Library/Frameworks
    • /Library/Frameworks
    • /Network/Library/Frameworks
    • /System/Library/Frameworks
  4. 以冒号分隔的目录列表在用户的DYLD_FALLBACK_LIBRARY_PATH环境变量,默认目录:

    • ~/lib
    • /usr/local/lib
    • /lib
    • /usr/lib

Note: Mono uses GLib to load libraries, and GLib has a bug on Mac OS X where it doesn’t use a .dylib extension, but instead uses the Unix.soextension. While this should eventually be fixed, the current workaround is to write a.config file which maps to the.dylibfile, e.g.

 <configuration>   <dllmap dll="mylib" target="mylib.dylib" /> </configuration>

See also

Library Names

知道从哪去寻找 Library 只是进行到了一半,而另一半是 怎么加载

不同的平台库文件的命名协议不一样。

  1. Window – 以 .dll 结尾。 (eg. OLE32.dll)
  2. Linux – 以lib 为前缀 .so 结尾。
  3. Mac OS – 以lib 为前缀 .dylib 结尾。(PS:如果是 Framework 的话则是一个目录,这种情况更为复杂。)

严格地说,Unix 的命名通常是 .so 后加上版本号。(eg. libfreetype.so.6.3.3)

在声明 DllImport 的时候,使用的名称,需要去掉前缀和后缀
例如:

 [DllImport ("MyLibrary")] private static extern void Frobnicate ();

那么,你只要为 Window 平台 提供 MyLibrary.dll 、 为 Unix 平台 提供 libMyLibrary.so 、为 Mac OS X 平台 提供 libMyLibrary.dylib

调用 非托管代码

首先,加载库DllImport属性中指定,如上所述。

如果你需要调用c++代码,你有两个选择

  1. 对 C++ 方法 使用 extern "C" 把它视为 C 方法。并确保它使用一个已知的调用协定(PS.Window 平台可调用 winapiUnix 平台可调用 Cdecl,以此类推)
  2. 在确保它使用一个已知的调用协定的前提下,不使用 extern "C" 。但需要 在方法前使用DllImport.EntryPoint

我们来分析一段代码。

 typedef void (*Handler) (const char *message); void InvokeHandler (Handler handler)  {    char *message = (char *) malloc (10);    strcpy (message, "A Message");    (*handler)(message);    free (message);  }

如果 handler 是一个托管指针这里可能会抛出一个异常,那么free 就不会被执行,那么结果就是导致内存泄漏

然而在这时,由于 C++的垃圾回收并不会帮助你回收这块内存,而你又没有处理这种异常情况的代码。另一方面,托管代码那边也不知道 C++的异常处理。

这在封装 C++方法中很常见。所以 C++的异常 应该映射在一个 Out 的参数,或者 返回值中。这样,托管代码侧就知道,C++这边是否抛出了异常。当然你也可以抛出一个 托管的异常来“传播”C++的这个异常。

  • SeeAlso
  • SeeAlso
  • SeeAlso

Marshaling

在给定一个托管调用的位置、非托管的调用位置后,他们之间的通信的调用位置被称为 marshaled 的地方。可以理解为一个类型转换。

而实际上 marshaled 将数据放在栈上,然后去调用非托管代码。

对于简单类型(eg. int \ float)的 Marshaling 是一个 bitwise-copy (“blitting”) 的过程。
有些情况下可以不进行Marshaling操作。比如 在传递 structures 的引用,这时是一个指向结构的指针传递。

对于 String 在运行时将以 UTF-16-encoded 编码,这些需要我们去marshaled 为更为合适的编码方式。

Memory Boundaries

托管和非托管内存应该认为是完全独立的。

  1. 托管内存通常是一个垃圾收集堆上分配的内存
  2. 非托管内存是:ANSI C内存池(通过调用malloc)、自定义内存池和垃圾收集堆外CLI的控制实现(如LISP或Scheme内存堆)。

使用C#固定语句可以锁定一个托管堆的一块内存。这一段托管堆可以传递给非托管代码操作而不用担心未来的GC回收。
然而,这完全是受控于编程人员,而不是平台调用是如何工作的。

在P/Invoke调用运行时不模仿c#固定的语句。相反,类和结构的内存排列通过以下伪流程:

  1. 非托管内存的运行时分配一块内存。
  2. 管理类数据复制到非托管内存
  3. 非托管函数被调用时,通过它管理的非托管内存信息而不是托管内存。这必须这样做,如果发生GC,非托管函数不需要担心。(是的,我们需要担心GC,作为非托管函数可以调用回运行时,最终导致GC。多线程代码也会导致一个GC而非托管代码执行)。
  4. 非托管内存复制回托管内存

要记住有一个关键问题:

在上面指定的内存管理过程是隐式的,并没有办法控制运行时如何管理、分配、排列内存或持续多长时间。如果运行时marshals一个字符串Ansi转化(如utf - 16),marshals字符串只要调用就会存在。 非托管代码不能保持这个内存引用,因为它将调用结束后释放。 这适用于任何marshal过程运行时分配内存的marshal的过程。

委托管理的非托管函数指针表示持续只要委托管理
委托收集的GC时,非托管函数指针也会收集。这也是很重要的:如果委托收集和非托管内存调用函数指针,就发生了“踩内存空间”了(不再属于期望的那个内存内容了,已是物是人非了)。任何事情都有可能发生,其中包括程序报错。因此,必须确保非托管函数指针的生命周期是一个适当的子集的生命周期管理委托实例

Blittable Types

0 0
原创粉丝点击