.Net Core 为 x86 和 x64 程序集编写 AnyCPU 包装
前言
这几天研究了一下 vJoy 这个虚拟游戏手柄驱动,感觉挺好玩的。但是使用时发现一个问题,C# SDK 中的程序集被分为 x86 和 x64 两个版本,如果直接在 AnyCPU 平台编译运行就有隐患,在32位系统中运行程序时会因为程序集版本不兼容而崩溃。这个 SDK 的两个版本文件名完全相同,根据 .Net 程序集的加载规则,我们是无法在不做任何工作的情况下实现共存的。对于平台特定程序集,目前的主流做法是把程序集放到以平台名称命名的文件夹中。通过一个包装程序集完成载入和调用。
正文
我这里的包装使用了 .Net Core 的 AssemblyLoadContext 为中介完成 SDK 的动态加载。不过这种包装方式也有个很麻烦的地方,不能像正常引用的程序集那样享受各种智能提示,只能使用反射的方式调用实际库中的各种功能。为方便使用,包装库需要在内部完成反射处理并对外公开一套 API 方便使用。
定义程序集载入上下文
因为资源由内部管理,不需要对外暴露,我这里的上下文是公开 API 的私有内部类,加载上下文的可重写方法中还有一个是用于非托管程序集的。这个上下文很智能,会自动查找被加载程序集的相同文件夹,所以无需重写,如果依赖程序集在其他位置,需要重写加载方法。
private class VJoyAssemblyLoadContext : AssemblyLoadContext
{
public VJoyAssemblyLoadContext() : base(isCollectible: true)
{
} protected override Assembly Load(AssemblyName name)
{
return null;
}
}
定义反射资源管理器
其中 _is64BitRuntime 是关键,用 IntPtr.Size == 8 可以判断当前进程运行在 x86 还是 x64 模式,并在之后用于生成程序集加载路径。因为管理器的定位相当于驱动信息管理,所以只需要一个实例。在这里使用了私有构造方法加公共静态的实例获取方法来管理对象资源。
程序集载入后使用 Activator.CreateInstance 创建实例,然后使用 GetMethod 获取方法成员后就可以反射调用实例方法了。不过一直反射调用可能会造成巨大的性能损失,粗略估计最高可以达到上百倍,如果条件允许,最好能把方法缓存起来,以后调用既方便又高效。具体就是下面代码中定义的 Delegate 和 Fun<> 型变量,用于缓存方法信息。反射获取方法信息后可以调用 CreateDelegate 方法生成委托,静态方法和实例方法都可以。对于 Func<> 和 Action<> 来说需要进行强制类型转换。实际使用时尽量使用 Func<> 和 Action<>,如果方法的参数或返回值类型定义在动态加载的程序集中就只能使用 Delegate 来缓存了,同时也只能使用 DynamicInvoke 方法来调用。
下面的代码中的 DriverMatch 方法的参数有 ref 修饰符,对于这种参数类型,内置的 Func<> 无法封装,需要自行定义委托类型,我这里因为只用一次,所以就没管了,Invoke 调用的时候 .Net 会负责处理 ref、out、in 修饰符,相应地调用方法修改后的对象也会反映到传入的参数对象中。如果要定义相应的委托,看起来应该长这样:
public delegate TResult Func<T1, T2, T3, out TResult>(in T1 arg1, out T2 arg2, ref T3 arg3);
定义示例:
public class VJoyControllerManager
{
private static readonly bool _is64BitRuntime = IntPtr.Size == ;
private static readonly object _locker = new object();
private static VJoyControllerManager _manager = null; private VJoyAssemblyLoadContext _vJoyAssemblyLoadContext;
private Assembly _vJoyInterfaceWrapAssembly;
private object _joystick;
private Type _vJoyType;
//省略相似代码…… private Delegate _getVJDStatusFunc;
private Delegate _getVJDAxisExist;
private Func<uint, int> _getVJDButtonNumber;
//省略相似代码…… public bool IsVJoyEnabled { get; }
public bool DriverMatch { get; }
public uint DllVer { get; }
public uint DrvVer { get; }
//省略相似代码…… private VJoyControllerManager()
{
var path = Process.GetCurrentProcess().MainModule.FileName;
var filePath = $@"{path.Substring(0, path.LastIndexOf('\\'))}\{(_is64BitRuntime ? "x64" : "x86")}\vJoyInterfaceWrap.dll"; _vJoyAssemblyLoadContext = new VJoyAssemblyLoadContext();
_vJoyInterfaceWrapAssembly = _vJoyAssemblyLoadContext.LoadFromAssemblyPath(filePath);
_joystick = Activator.CreateInstance(_vJoyInterfaceWrapAssembly.GetTypes().Single(t => t.Name == "vJoy"));
_vJoyType = _joystick.GetType(); IsVJoyEnabled = (bool)_vJoyType.GetMethod("vJoyEnabled").Invoke(_joystick, null); //省略相似代码…… _getVJDButtonNumber = (Func<uint, int>)_vJoyType.GetMethod("GetVJDButtonNumber").CreateDelegate(typeof(Func<uint, int>), _joystick); var funcType = typeof(Func<,>).MakeGenericType(new Type[] { typeof(uint), _VjdStatEnumType });
_getVJDStatusFunc = _vJoyType.GetMethod("GetVJDStatus").CreateDelegate(funcType, _joystick); funcType = typeof(Func<,,>).MakeGenericType(new Type[] { typeof(uint), _hidUsagesEnumType, typeof(bool) });
_getVJDAxisExist = _vJoyType.GetMethod("GetVJDAxisExist").CreateDelegate(funcType, _joystick); var args = new object[] { 0u, 0u };
DriverMatch = (bool)_vJoyType.GetMethod("DriverMatch").Invoke(_joystick, args);
DllVer = (uint)args[];
DrvVer = (uint)args[];
} public static VJoyControllerManager GetManager()
{
if (_manager == null)
lock (_locker)
if (_manager == null)
_manager = new VJoyControllerManager(); return _manager;
} public static void ReleaseManager()
{
if (_manager != null)
lock (_locker)
if (_manager != null)
{
_manager._axisEnumValues = null;
//省略相似代码…… _manager.UnLoadContext();
_manager = null;
} } private void UnLoadContext()
{
_vJoyAssemblyLoadContext.Unload();
_vJoyAssemblyLoadContext = null;
} public object GetVJDStatus(uint id) => IsVJoyEnabled ? _getVJDStatusFunc.DynamicInvoke(id) : null; public int GetVJDButtonNumber(uint id) => IsVJoyEnabled ? _getVJDButtonNumber(id) : ; public bool GetVJDAxisExist(uint id, USAGES usages) => IsVJoyEnabled ? (bool)_getVJDAxisExist.DynamicInvoke(new object[] { id, _axisEnumValues[(int)usages] }) : false; //省略相似代码……
}
剩下的部分基本就是用这个套路把剩下要用的东西封装缓存后供外界使用。感兴趣的话,完整代码可以在文章末尾到我的 GitHub 项目中查看。
结语
用这个方法可以把分开的平台绑定程序集封装到一个 AnyCPU 程序集里。对于要与原生 dll 交互的项目,在原生封装时就完成这一步最好。如果封装也是分开的,这个办法就可以再封装一次,用来适配 AnyCPU 平台。
在我的 GitHub 项目中有个 vJoyDemo 项目,是使用示例。
转载请完整保留以下内容并在显眼位置标注,未经授权删除以下内容进行转载盗用的,保留追究法律责任的权利!
本文地址:https://www.cnblogs.com/coredx/p/12455761.html
完整源代码:Github
里面有各种小东西,这只是其中之一,不嫌弃的话可以Star一下。
.Net Core 为 x86 和 x64 程序集编写 AnyCPU 包装的更多相关文章
- 使用CefSharp在C#访问网站,支持x86和x64
早已久仰CefSharp大名,今日才得以实践,我其实想用CefSharp来访问网站页面,然后抓取html源代码进行分析,如果使用自带的WebBrowser控件,可能会出现一些不兼容js的错误. Cef ...
- 关于C#编写x86与x64程序的分析
电脑硬件CPU可以分为x86与x64, x86的机器只能安装32位的操作系统,如XP, WIN7_86, x64的机器既可以安装32位的系统,又可以安装64位的系统,只是在x64的机器上安装32位的系 ...
- Mixing x86 with x64 code (混合编写x86和x64代码)
几个月前我小小的研究了在WOW64下的32位进程中运行native x64代码. 第二个设想是在64位进程下运行x86代码.它们都是可以的,如我google的一样, 已经有人在使用这两种方法了: ht ...
- Visual Studio中Debug与Release以及x86、x64、Any CPU的区别
Visual Studio中Debug与Release的区别: 在Visual Studio中,编译模式有2种:Debug与Release.这也是默认的两种方式,在新建一个project的时候,就已经 ...
- .Net编译环境x86,x64,anycpu的区别
一.定义 x86: 将程序集编译为由兼容 x86 的 32 位公共语言运行库运行. x64: 将程序集编译为由支持 AMD64 或 EM64T 指令集的计算机上的 64 位公共语言运行库运行. any ...
- 【Hardware】i386、x86和x64的故事
(1)x86的由来 x86架构首度出现在1978年推出的Intel 8086中央处理器,它是从Intel 8008处理器中发展而来的,而8008则是发展自Intel 4004的.在8086之后,Int ...
- Visual Studio中Debug与Release以及x86、x64、Any CPU的区别 &&&& VS中Debug与Release、_WIN32与_WIN64的区别
本以为这些无关紧要的 Debug与Release以及x86.x64.Any CPU 差点搞死人了. 看了以下博文才后怕,难怪我切换了一下模式,程序就pass了.... 转载: 1.https://ww ...
- cmake编译(编译目标)x86或x64
if(CMAKE_CL_64) #CMAKE的内建变量,如果是true,就说明编译器的64位的,自然可以编译64bit的程序 set(ADDRESS_MODEL 64) set(NODE_TAR ...
- cmake构建时指定编译器架构(x86 or x64)
vs2015 x64编译器为例,cmake命令如下: cmake -G "Visual Studio 14 Win64" path\to\source\dir 去掉Win64,就是 ...
随机推荐
- slqmap简单使用
网址:-u 指定哪个参数:-p 需要登录用--cookie 获得所有数据库:--dbs 获得所有用户:--users 指定某个数据库:-D 显示所有表--tables 指定某个数据表:-T 显示列:- ...
- 01-信贷路由项目架构和 rose 框架的搭建
1.信贷路由项目架构 2.工程搭建及测试 搭建tyrRouter-parent,tyrRouter-log-web,工程采用 maven 构建 配置 pom.xml 文件,父项目管理 jar 包的版本 ...
- 项目部署篇之二——linux下安装jdk1.8
1.下载jdk1.8 百度云下载后,直接通过xftp拖到你想放的目录下就行了,实在方便 链接:https://pan.baidu.com/s/1hQl0_3owT776lRO9mHSbXA 提取码:2 ...
- 4.Redis持久化方案
1.1 RDB持久化 RDB方式的持久化是通过快照(snapshotting)完成的,当符合一定条件时Redis会自动将内存中的数据进行快照并持久化到硬盘. RDB是Redis默认采用的持久化方式. ...
- idea,2018版破解方法
1.准备安装文件 2.安装 ideaIU-2018.3.5.exe ,如这里是安装到IDEA这个文件夹: 3.复制JetbrainsIdesCrack-4.2-releasexxx.jar放到IDEA ...
- python去除列表中重复元素的方法
列表中元素位置的索引用的是L.index 本文实例讲述了Python去除列表中重复元素的方法.分享给大家供大家参考.具体如下: 比较容易记忆的是用内置的set 1 2 3 l1 = ['b','c', ...
- HttpClient GET和POST请求
package com.rogue.hclient; import java.io.BufferedReader; import java.io.IOException; import java.io ...
- idea 集成svn
1.下载svn客户端 官网下载地址:https://tortoisesvn.net/downloads.html 2. 安装svn客户端 在安装svn客户端的时候一定要勾选,否则在idea上集成svn ...
- Java IO: OutputStream
原文链接 作者: Jakob Jenkov 译者: 李璟(jlee381344197@gmail.com) OutputStream类是Java IO API中所有输出流的基类.子类包括Buffere ...
- idea(or maven) 未结束字符串字面值 非法的表达式开始
[ERROR] *.java:[38,27] 未结束的字符串字面值 [ERROR] *.java:[38,53] 需要 ';' [ERROR] *.java:[41,19] 需要 ')' [ERROR ...