前言

这几天研究了一下 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 包装的更多相关文章

  1. 使用CefSharp在C#访问网站,支持x86和x64

    早已久仰CefSharp大名,今日才得以实践,我其实想用CefSharp来访问网站页面,然后抓取html源代码进行分析,如果使用自带的WebBrowser控件,可能会出现一些不兼容js的错误. Cef ...

  2. 关于C#编写x86与x64程序的分析

    电脑硬件CPU可以分为x86与x64, x86的机器只能安装32位的操作系统,如XP, WIN7_86, x64的机器既可以安装32位的系统,又可以安装64位的系统,只是在x64的机器上安装32位的系 ...

  3. Mixing x86 with x64 code (混合编写x86和x64代码)

    几个月前我小小的研究了在WOW64下的32位进程中运行native x64代码. 第二个设想是在64位进程下运行x86代码.它们都是可以的,如我google的一样, 已经有人在使用这两种方法了: ht ...

  4. Visual Studio中Debug与Release以及x86、x64、Any CPU的区别

    Visual Studio中Debug与Release的区别: 在Visual Studio中,编译模式有2种:Debug与Release.这也是默认的两种方式,在新建一个project的时候,就已经 ...

  5. .Net编译环境x86,x64,anycpu的区别

    一.定义 x86: 将程序集编译为由兼容 x86 的 32 位公共语言运行库运行. x64: 将程序集编译为由支持 AMD64 或 EM64T 指令集的计算机上的 64 位公共语言运行库运行. any ...

  6. 【Hardware】i386、x86和x64的故事

    (1)x86的由来 x86架构首度出现在1978年推出的Intel 8086中央处理器,它是从Intel 8008处理器中发展而来的,而8008则是发展自Intel 4004的.在8086之后,Int ...

  7. Visual Studio中Debug与Release以及x86、x64、Any CPU的区别 &&&& VS中Debug与Release、_WIN32与_WIN64的区别

    本以为这些无关紧要的 Debug与Release以及x86.x64.Any CPU 差点搞死人了. 看了以下博文才后怕,难怪我切换了一下模式,程序就pass了.... 转载: 1.https://ww ...

  8. cmake编译(编译目标)x86或x64

    if(CMAKE_CL_64)    #CMAKE的内建变量,如果是true,就说明编译器的64位的,自然可以编译64bit的程序 set(ADDRESS_MODEL 64) set(NODE_TAR ...

  9. cmake构建时指定编译器架构(x86 or x64)

    vs2015 x64编译器为例,cmake命令如下: cmake -G "Visual Studio 14 Win64" path\to\source\dir 去掉Win64,就是 ...

随机推荐

  1. ORs-2-Genome Coverage and the OR Subgenome

    Genome Coverage and the OR Subgenome 因为: 爬行类动物的的gene numbers比较大,而birds 的 gene numbers 处于(182-688) 其中 ...

  2. 聊聊HTTPS和SSL/TLS协议 【基础入门】

    要说清楚 HTTPS 协议的实现原理,至少需要如下几个背景知识.1. 大致了解几个基本术语(HTTPS.SSL.TLS)的含义2. 大致了解 HTTP 和 TCP 的关系(尤其是“短连接”VS“长连接 ...

  3. Spring-增强方式注解实现方式

    SpringAOP增强是什么,不知道的到上一章去找,这里直接上注解实现的代码(不是纯注解,纯注解后续会有) 创建业务类代码 @Service("dosome")//与配置文件中&l ...

  4. 16)用了session会话技术

    为什么用session会话技术? 因为假如你进入后台,不可能随意进入,即使你的验证通过了,那么还需要一个变量来存一个标志,假如标志的值是yes,那么我们可以直接进入后台的首页,无需验证,但是,标志是n ...

  5. Linux主机下如何查询自己使用的公网IP

    curl http://members.3322.org/dyndns/getip 可以解析出自己是使用哪个公网IP访问外网的

  6. miracle|

    N-COUNT 奇迹;出人意料的事If you say that a good event is a miracle, you mean that it is very surprising and ...

  7. 3DSMAX安装失败,如何卸载修复重新安装3dmax 2017?

    一些同学安装3dmax出错了,也有时候想重新安装3dmax的时候会出现这种本电脑已安装3dmax,你要是不留意直接安装,只会按装3dmax的附件,3dmax是不会按装上的.这种原因呢就是大家在之前卸载 ...

  8. struts2学习笔记之十四:使用注解配置Action(不是和spring集成使用)

    Struts2支持使用注解配置Action,减少配置文件的配置 Struts2如果要支持注解配置Action,需要插件的支持,导入插件struts2-convention-plugin-2.1.8.1 ...

  9. unittest(22)- p2p项目实战(4)-read_config

    # 4. read_config.py import configparser class ReadConfig: @staticmethod def get_config(file_path, se ...

  10. 吴裕雄--天生自然 R语言开发学习:重抽样与自助法(续一)

    #-------------------------------------------------------------------------# # R in Action (2nd ed): ...