翼度科技»论坛 编程开发 .net 查看内容

平台调用 (P/Invoke):跨平台方案

5

主题

5

帖子

15

积分

新手上路

Rank: 1

积分
15
  接前上一篇:平台调用 (P/Invoke):DllImport特性说明
  首先,我们知道C#和C/C++都是跨平台的,但是原理上他们是不一样的:  
  1.     C#首先编译成一种中间语言(IL)的程序集,然后将这种程序集放到不同平台下的解释器里面去执行,这就是说一次编译到处运行
  2.     C/C++是针对不同的平台直接编译,编译之后就不具备跨平台能力了
复制代码
  所以,当我们开发的应用需要跨平台时,我们就需要将C/C++程序分别对不同平台编译了,那么剩下的就是我们怎么调用的问题了。
  调用时判断
  一个简单的思路就是,在需要调用的时候做判断,这个大家应该都会,比如我们有window和linux的两个动态库,那么我们在调用的时候可以通过环境来判断:  
  1.     [DllImport("lib/Project-win.dll", EntryPoint = "Add")]
  2.     static extern int WinAdd(int n1, int n2);
  3.     [DllImport("lib/libProject-linux.so", EntryPoint = "Add")]
  4.     static extern int LinuxAdd(int n1, int n2);
  5.     static void Main(string[] args)
  6.     {
  7.         //判断系统环境
  8.         if (OperatingSystem.IsLinux())
  9.         {
  10.             var sum = LinuxAdd(1, 2);
  11.             Console.WriteLine(sum);
  12.         }
  13.         else
  14.         {
  15.             var sum = WinAdd(1, 2);
  16.             Console.WriteLine(sum);
  17.         }
  18.     }
复制代码
  显然,这个办法很笨拙,难道我们每个要调用的时候都加这个判断么?当然,有些想法的同学可能会考虑使用继承来做一层封装,这样就不用每个地方都加这种判断了,但是这也需要增加一些无用的代码量,想想就因为不同平台的编译,就要拐个弯来处理,想想就不划算。
  库名称变体
  很庆幸,.net为了解决跨平台导致的问题,允许我们将动态库按照一些规则来命名,这样就可以自动根据环境来选择对应的动态库了,比如,在对C/C++程序进行编译的时候,我们可以把它们编译成相同名称不同后缀的动态库,比如windows下就是project.dll,linux下就是project.so,然后就可以简单的实现:  
  1.     [DllImport("lib/project", EntryPoint = "Add")]
  2.     static extern int Add(int n1, int n2);
  3.     static void Main(string[] args)
  4.     {
  5.         //自动根据当前系统环境判断
  6.         var sum = Add(1, 2);
  7.         Console.WriteLine(sum);
  8.     }
复制代码
  上述代码可以在linux和windows下运行,只要对应的动态库存在就可以了。
  这种方式得益于.net的库名变体搜索规则:
,  Windows下按以下顺序搜索dll:
  1.     [DllImport("lib/nativedep")]将按下面的顺序搜索动态库:
  2.     1、先搜索全名不带后缀的库,即nativedep
  3.     2、没有则继续搜索带.dll后缀的库,即nativedep.dll
复制代码
  macOS下按以下顺序搜索dylib:
  1.     [DllImport("lib/nativedep")]将按下面的顺序搜索动态库:
  2.     1、先搜索带.dylib后缀的库,即nativedep.dylib
  3.     2、没有则继续搜索以lib开头,带.dylib后缀的库,即libnativedep.dylib
  4.     3、没有则继续搜索全名不带后缀的库,即nativedep
  5.     4、最后搜索以lib开头的库,即libnativedep
复制代码
  Linux下要分情况而定

  • 如果引入的库名以.so为后缀,或者以.so.*的格式结尾,则按以下顺序搜索so:
    1.     [DllImport("lib/nativedep.so")]和[DllImport("lib/nativedep.so.1")]将按下面的顺序搜索动态库:
    2.     1、先搜索全名称没有处理的库,即nativedep.so、nativedep.so.1
    3.     2、没有则继续搜索带lib前缀的库,即libnativedep.so、libnativedep.so.1
    4.     3、没有则继续搜索带.so后缀的库,即nativedep.so.so、nativedep.so.1.so
    5.     4、没有则继续搜索带lib前缀、带.so后缀的库,即libnativedep.so.so、libnativedep.so.1.so
    复制代码
  • 否则按以下顺序搜索so:
    1.     [DllImport("lib/nativedep")]将按下面的顺序搜索动态库:
    2.     1、先搜索带.so后缀的库,即nativedep.so
    3.     2、没有则继续搜索以lib开头,带.so后缀的库,即libnativedep.so
    4.     3、没有则继续搜索全名不带后缀的库,即nativedep
    5.     4、最后搜索以lib开头的库,即libnativedep
    复制代码
  注:如果DllImport的时候使用的是觉得路径,那么在使用绝对路径名称搜索,以上命名规则将不生效 
  自定义导入解析
  有时候,我们开发都要求速度,也需开始的时候我们没有考虑这么多,可能是直接按照上面第一种做的,导致我们有多个名称的库,而又不方便按照第二种方式来处理,这个时候我们可以考虑下自定义导入解析的方式。
  假如现在我们已经有window下的动态库Project-win.dll,以及Linux下的动态库libProject-linux.so,这是两个文件,名称不一样,我们不能使用上面第二种方式(库名称变体)来处理,那么可以自定义导入解析,首先,假如我们导入的程序是:  
  1.     [DllImport("lib/plus", EntryPoint = "Add")]
  2.     static extern int Plus(int n1, int n2);
复制代码
  注意,这里的动态库名称是plus,接着提供一个自定义解析的委托函数:  
  1.     static IntPtr DllImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
  2.     {
  3.         //如果库名是plus,则根据系统环境来更换
  4.         if (libraryName == "lib/plus")
  5.         {
  6.             if (OperatingSystem.IsLinux())
  7.             {
  8.                 libraryName = "lib/libProject-linux.so";
  9.             }
  10.             else
  11.             {
  12.                 libraryName = "lib/Project-win.dll";
  13.             }
  14.             return NativeLibrary.Load(libraryName, assembly, searchPath);
  15.         }
  16.         return IntPtr.Zero;
  17.     }
复制代码
  接着注册一下,我们就可以用了:  
  1.     static void Main(string[] args)
  2.     {
  3.         //将当前运行的程序集注册自定义的处理方式
  4.         NativeLibrary.SetDllImportResolver(Assembly.GetExecutingAssembly(), DllImportResolver);
  5.         //直接使用
  6.         var sum = Plus(1, 2);
  7.         Console.WriteLine(sum);
  8.     }
复制代码
  
  参考文档:https://learn.microsoft.com/en-us/dotnet/standard/native-interop/cross-platform
 

来源:https://www.cnblogs.com/shanfeng1000/archive/2023/02/20/17137614.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

举报 回复 使用道具