|
接前上一篇:平台调用 (P/Invoke):DllImport特性说明
首先,我们知道C#和C/C++都是跨平台的,但是原理上他们是不一样的: - C#首先编译成一种中间语言(IL)的程序集,然后将这种程序集放到不同平台下的解释器里面去执行,这就是说一次编译到处运行
- C/C++是针对不同的平台直接编译,编译之后就不具备跨平台能力了
复制代码 所以,当我们开发的应用需要跨平台时,我们就需要将C/C++程序分别对不同平台编译了,那么剩下的就是我们怎么调用的问题了。
调用时判断
一个简单的思路就是,在需要调用的时候做判断,这个大家应该都会,比如我们有window和linux的两个动态库,那么我们在调用的时候可以通过环境来判断: - [DllImport("lib/Project-win.dll", EntryPoint = "Add")]
- static extern int WinAdd(int n1, int n2);
- [DllImport("lib/libProject-linux.so", EntryPoint = "Add")]
- static extern int LinuxAdd(int n1, int n2);
- static void Main(string[] args)
- {
- //判断系统环境
- if (OperatingSystem.IsLinux())
- {
- var sum = LinuxAdd(1, 2);
- Console.WriteLine(sum);
- }
- else
- {
- var sum = WinAdd(1, 2);
- Console.WriteLine(sum);
- }
- }
复制代码 显然,这个办法很笨拙,难道我们每个要调用的时候都加这个判断么?当然,有些想法的同学可能会考虑使用继承来做一层封装,这样就不用每个地方都加这种判断了,但是这也需要增加一些无用的代码量,想想就因为不同平台的编译,就要拐个弯来处理,想想就不划算。
库名称变体
很庆幸,.net为了解决跨平台导致的问题,允许我们将动态库按照一些规则来命名,这样就可以自动根据环境来选择对应的动态库了,比如,在对C/C++程序进行编译的时候,我们可以把它们编译成相同名称不同后缀的动态库,比如windows下就是project.dll,linux下就是project.so,然后就可以简单的实现: - [DllImport("lib/project", EntryPoint = "Add")]
- static extern int Add(int n1, int n2);
- static void Main(string[] args)
- {
- //自动根据当前系统环境判断
- var sum = Add(1, 2);
- Console.WriteLine(sum);
- }
复制代码 上述代码可以在linux和windows下运行,只要对应的动态库存在就可以了。
这种方式得益于.net的库名变体搜索规则:
, Windows下按以下顺序搜索dll:- [DllImport("lib/nativedep")]将按下面的顺序搜索动态库:
- 1、先搜索全名不带后缀的库,即nativedep
- 2、没有则继续搜索带.dll后缀的库,即nativedep.dll
复制代码 macOS下按以下顺序搜索dylib:- [DllImport("lib/nativedep")]将按下面的顺序搜索动态库:
- 1、先搜索带.dylib后缀的库,即nativedep.dylib
- 2、没有则继续搜索以lib开头,带.dylib后缀的库,即libnativedep.dylib
- 3、没有则继续搜索全名不带后缀的库,即nativedep
- 4、最后搜索以lib开头的库,即libnativedep
复制代码 Linux下要分情况而定
- 如果引入的库名以.so为后缀,或者以.so.*的格式结尾,则按以下顺序搜索so:
- [DllImport("lib/nativedep.so")]和[DllImport("lib/nativedep.so.1")]将按下面的顺序搜索动态库:
- 1、先搜索全名称没有处理的库,即nativedep.so、nativedep.so.1
- 2、没有则继续搜索带lib前缀的库,即libnativedep.so、libnativedep.so.1
- 3、没有则继续搜索带.so后缀的库,即nativedep.so.so、nativedep.so.1.so
- 4、没有则继续搜索带lib前缀、带.so后缀的库,即libnativedep.so.so、libnativedep.so.1.so
复制代码 - 否则按以下顺序搜索so:
- [DllImport("lib/nativedep")]将按下面的顺序搜索动态库:
- 1、先搜索带.so后缀的库,即nativedep.so
- 2、没有则继续搜索以lib开头,带.so后缀的库,即libnativedep.so
- 3、没有则继续搜索全名不带后缀的库,即nativedep
- 4、最后搜索以lib开头的库,即libnativedep
复制代码 注:如果DllImport的时候使用的是觉得路径,那么在使用绝对路径名称搜索,以上命名规则将不生效
自定义导入解析
有时候,我们开发都要求速度,也需开始的时候我们没有考虑这么多,可能是直接按照上面第一种做的,导致我们有多个名称的库,而又不方便按照第二种方式来处理,这个时候我们可以考虑下自定义导入解析的方式。
假如现在我们已经有window下的动态库Project-win.dll,以及Linux下的动态库libProject-linux.so,这是两个文件,名称不一样,我们不能使用上面第二种方式(库名称变体)来处理,那么可以自定义导入解析,首先,假如我们导入的程序是: - [DllImport("lib/plus", EntryPoint = "Add")]
- static extern int Plus(int n1, int n2);
复制代码 注意,这里的动态库名称是plus,接着提供一个自定义解析的委托函数: - static IntPtr DllImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
- {
- //如果库名是plus,则根据系统环境来更换
- if (libraryName == "lib/plus")
- {
- if (OperatingSystem.IsLinux())
- {
- libraryName = "lib/libProject-linux.so";
- }
- else
- {
- libraryName = "lib/Project-win.dll";
- }
- return NativeLibrary.Load(libraryName, assembly, searchPath);
- }
- return IntPtr.Zero;
- }
复制代码 接着注册一下,我们就可以用了: - static void Main(string[] args)
- {
- //将当前运行的程序集注册自定义的处理方式
- NativeLibrary.SetDllImportResolver(Assembly.GetExecutingAssembly(), DllImportResolver);
- //直接使用
- var sum = Plus(1, 2);
- Console.WriteLine(sum);
- }
复制代码
参考文档: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】 我们会及时删除侵权内容,谢谢合作! |
|