前言

这几天研究了一下 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. CHI 2013:人机交互领域那些令人兴奋的新技术

    2013:人机交互领域那些令人兴奋的新技术" title="CHI 2013:人机交互领域那些令人兴奋的新技术"> 编者按:CHI是人机交互领域首屈一指的国际盛会, ...

  2. 原生html,css+js写下载按钮有提示动画效果的落地页

    <!DOCTYPE html> <html lang="en"> <head>   <meta charset="UTF-8&q ...

  3. Ubuntu navicat 连接mysql:access denied for user 'root'@'localhost'

    真是醉了,Ubuntu装了navicat后,准备在桌面建立图标不成,结果直接打开后连接mysql都不行,真坑,奈何远程连接就成,这就尬了,今天终于解决了 问题 我也百度了好几个方案,奈何解决不了,最后 ...

  4. 奇点云COO刘莹应邀出席《APEC SME大数据与人工智能论坛》

    10月24日-25日,由亚太经合组织(APEC).韩国中小型及初创企业管理局(the Ministry of SMEs & Startups of Korea)主办的「APEC SME 大数据 ...

  5. 吴裕雄--天生自然 JAVASCRIPT开发学习:对象 实例(2)

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  6. Linux安装swoole拓展 (一键安装lnmp后安装可用完美)

    一键安装lnmp后安装可用完美 swoole(一键安装完lnmp重启下,之前出现502一直解决不了,不清楚啥情况) 找到对应php版本,在lnmp文件夹的src 1.安装swoole cd /usr/ ...

  7. C++对数组进行复制

    C++ 风格的复制操作 使用STL中的copy算法 int a[] = {1,2,3,4,5}; int b[5]; std::copy(std::begin(a),std::end(a),std:: ...

  8. Zabbix调用外部脚本发送邮件:python编写脚本

    Zabbix调用外部脚本发送邮件的时候,会在命令行传入两个参数,第一个参数就是要发送给哪个邮箱地址,第二个参数就是邮件信息,为了保证可以传入多个参数,所以假设有多个参数传入 #!/usr/bin/en ...

  9. DOCKER中centos7的中文支持

    直接编写看下能否改变成识别中文字体 写到你的~/.bashrc里吧,然后重启终端(我写的是英文的啊,改成你要的) export LC_ALL=en_US.UTF-8 export LANGUAGE=e ...

  10. Nginx笔记总结十三:sub_filter内容替换

    Nginx变异安装加上参数 --with-http_sub_module 配置文件: location ~* ^/portalproxy/([-]*)/portal(.*)$ { #sub_filte ...