互操作之DotnetFramework跨平台解决方案

14 篇文章 0 订阅
7 篇文章 0 订阅

首先说明下,这里“跨平台”是指Windows下的32位与64位平台,不涉及windows以外的平台。 

以前的项目都是x86平台下运行, 自然也没有考虑过 DotnetFramework 跨多平台解决方案 这个问题。

最近在实现不同平台运行的时候发现有些与我之前想法不符合的地方。

于是便决定记录下来。

平台

dotnetframework 下配置解决方案平台有四种类型:Itanium 、X86、X64、AnyCPU 

 

  • Itanium  是Intel“为未来设计的”处理器架构, 当然由于现在还没有到未来,所以就不用考虑它了。
  • X86 x86架构的32位系统平台,多是历史原因留下的平台(目前应该很多应用的设备都是基于32位平台的)
  • X64 x64架构的64位系统平台,当前流行的平台
  • AnyCPU  任意平台,VS默认选项。可以理解为见人说人话,见神说神话。

AnyCPU的优势是显而易见的, 正常纯托管代码都不会出现什么问题,编译一个版本即可在32、64位系统下通用。

不过由于依赖的C/C++等非托管库必须以x86平台编译,这样就无法通用。仅能在x86上运行,

如果需要在64位系统上运行还需要将库编译为x86特定平台,然后将应用程序以32位运行。

而进行互操作的dll文件路径只能是const的, 所以不能通过代码动态给互操作库路径赋值。

那么,若需要同时支持32、64位系统,我们得发布两个版本(x86与x64)

那么是不是在使用互操作的时候,就不能通过AnyCPU的方式生成通用的程序了呢?

 

跨平台方案

借助搜索引擎没有找到想要的结果, 于是考虑了几种也许可行的方案:

方案一:

思路:设置当前工作目录后加载目录中的指定dll

 a. 将本机代码分别编译x86与x64版本。(假设名称为invoke.dll) 然后分别放入运行目录(/x64/invoke.dll  /x86/invoke.dll)

 b.互操作调用之前使用托管代码Directory.SetCurrentDirectory设置当前的工作目录。

( 通过Environment.Is64BitProcess() 判断平台。 若32位平台,设置成 ./x86/ ; 64位平台设置成./x64/)

 c. 调用互操作(试验后发现并不需要每次调用前都设置工作目录,第一次调用设置好即可)

 d.编译托管代码为AnyCPU

 

方案一设置了工作目录后加载目录下的dll是可行的,不过此方法有个致命的缺陷:

 在调用Directory.SetCurrentDirectory后,调用互操作之前,工作目录可能被设置到其它位置

这个问题限制了所有互操作的DLL,必须在x86与x64目录中。(或是你取的其它目录名称)

 

方案二:

思路:使用WIN32 API 直接调用LoadLibrary加载动态库的全路径

 a.将本机代码分别编译x86与x64版本。(假设名称为invoke.dll) 然后分别放入运行目录(/x64/invoke.dll  /x86/invoke.dll)

 b.在调用互操作之前,生成当前本地代码库的全路径名称

  例如:在win32下的全路径代码是 D:\test\x86\invoke.dll;  在x64下就是D:\test\x64\invoke.dll

c.调用 WIN32 API LoadLibrary 加载步骤b生成的dll路径

d.调用互操作

e. 调用WIN32 API FreeLibrary 释放c步骤加载的dll

预加载代码如下

 

 private const string LibraryName = "kernel32";
        [DllImport(LibraryName, CharSet = CharSet.Auto, BestFitMapping = false, SetLastError = true)]
        private static extern IntPtr LoadLibrary(string fileName);

        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        [DllImport(LibraryName, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool FreeLibrary(IntPtr moduleHandle);

        /// <summary>
        /// 预加载本地dll
        /// </summary>
        /// <param name="modlueName"></param>
        /// <returns></returns>
        public static IntPtr PreLoad(string modlueName)
        {
            string baseDir = AppDomain.CurrentDomain.BaseDirectory;
            string platform = "x86";
            if (Environment.Is64BitProcess)
            {
                platform = "x64";
            }
            string modPath = string.Format("{0}{1}\\{2}", baseDir, platform, modlueName);
            return LoadLibrary(modPath);
        }
        public static bool FreeLib(IntPtr ptr)
        {
            return FreeLibrary(ptr);
        } 



  方案二实际上是借助LoadLibrary直接加载动态库到进程中,

该方案是缺点是:每个需要互操作的dll都需要通过PreLaod方法加载下,

而且对于用户控件来说,如果有调用了互操作的控件,需要打开设计视图时候,会报错找不到所需的dll

 

方案三:

思路:为了突破方案二的局限性,我们得考虑将调用的dll放入系统目录中,并在加载的时候从多个目录中查找

a. 注册dll所在目录到系统的环境变量中

下面的代码功能为:把 CDllPath 加入系统变量,并赋值为dll存放的路径,然后以管理员权限启动

@echo off 
rem  将代码保存为bat文件,存放到DLL所在目录,并以管理员权限执行。(这里只测试了win10系统)
%1 mshta vbscript:CreateObject("Shell.Application").ShellExecute("cmd.exe","/c %~s0 ::","","runas",1)(window.close)&&exit

echo 将当前目录添加到环境变量中...【仅测试Win10的环境变量添加】
echo Andwp 2017年3月12日

ver | find "10.0" > NUL &&  goto win10  
   
set strReg="HKLM\system\controlset\control\session manager\environment" 
goto Next
  
:win10  
@echo            Win10系统特殊处理 
set strReg="HKLM\system\ControlSet001\control\session manager\environment" 
goto Next
 
:Next   

set strDir=%~dp0
echo  获取到当前目录路径  %strDir%

reg add %strReg% /v CDllPath /t REG_SZ /d %strDir%


echo  环境变量添加成功! 

pause

b.C#调用C/C++动态库之前,在默认路径中查找dll路径,加载成功才返回。

代码如下:

/// <summary>
/// 预加载本地dll
/// </summary>
/// <param name="modlueName"></param>
/// <param name="loadPath">加载路径</param>
/// <returns></returns>
public static IntPtr PreLoad(string modlueName, ref string loadPath)
{
    List<string> baseDir = new List<string>();
    baseDir.Add(AppDomain.CurrentDomain.BaseDirectory);
    // 从系统环境变量加载
    string evnPath = System.Environment.GetEnvironmentVariable("CDllPath");
    string[] evns = evnPath.Split(';');
    baseDir.AddRange(evns);
    string platform = "x86";
    if (Environment.Is64BitProcess)
    {
        platform = "x64";
    }


    IntPtr ptr = IntPtr.Zero;
    loadPath = string.Empty;
    for (int i = 0; i < baseDir.Count; i++)
    {
        if (!string.IsNullOrEmpty(baseDir[i]))
        {
            string strPath = string.Format("{0}\\{1}\\{2}", baseDir[i].TrimEnd('\\'), platform, modlueName);
            ptr = LoadLibrary(strPath);
            if (ptr != IntPtr.Zero)
            {
                loadPath = strPath;
                break;
            }
            loadPath += strPath;
        }
    }
    return ptr;
}

 

这里仅查找了a步骤中的导入的目录与当前目录下的x64与x86,若有更多的要添加,可以在此思路上扩展。

方法四:

windowsAPI提供了函数SetDllDirectory,设置动态库的搜索路径,这就比较方便,是什么平台就加载什么目录,具体使用可以自己去了解下。https://docs.microsoft.com/zh-cn/windows/desktop/api/winbase/nf-winbase-setdlldirectorya

 

 结束语

本文仅仅介绍一些互操作跨平台的思路,并不是标准答案或最佳答案。

我相信一定还有更多的可行方案,在此抛砖引玉,欢迎各位提出。

 .NET Framework 是支持生成和运行下一代应用程序和 XML Web services 的内部 Windows 组件。.NET Framework 旨在实现下列目标:   提供一个一致的面向对象的编程环境,而无论对象代码是在本地存储和执行,还是在本地执行但在 Internet 上分布,或者是在远程执行的。   提供一个将软件部署和版本控制冲突最小化的代码执行环境。   提供一个可提高代码(包括由未知的或不完全受信任的第三方创建的代码)执行安全性的代码执行环境。   提供一个可消除脚本环境或解释环境的性能问题的代码执行环境。   使开发人员的经验在面对类型大不相同的应用程序(如基于 Windows 的应用程序和基于 Web 的应用程序)时保持一致。   按照工业标准生成所有通信,以确保基于 .NET Framework 的代码可与任何其他代码集成。   .NET Framework 具有两个主要组件:公共语言运行库和 .NET Framework 类库。公共语言运行库是 .NET Framework 的基础。您可以将运行库看作一个在执行时管理代码的代理,它提供内存管理、线程管理和远程处理等核心服务,并且还强制实施严格的类型安全以及可提高安全性和可靠性的其他形式的代码准确性。事实上,代码管理的概念是运行库的基本原则。以运行库为目标的代码称为托管代码,而不以运行库为目标的代码称为非托管代码。.NET Framework 的另一个主要组件是类库,它是一个综合性的面向对象的可重用类型集合,您可以使用它开发多种应用程序,这些应用程序包括传统的命令行或图形用户界面 (GUI) 应用程序,也包括基于 ASP.NET 所提供的最新创新的应用程序(如 Web 窗体和 XML Web services)。   .NET Framework 可由非托管组件承载,这些组件将公共语言运行库加载到它们的进程中并启动托管代码的执行,从而创建一个可以同时利用托管和非托管功能的软件环境。.NET Framework 不但提供若干个运行库宿主,而且还支持第三方运行库宿主的开发。   .NET Framework是Microsoft为开发应用程序而创建的一个富有革命性的新平台。   这句话最有趣的地方是它的含糊不清,但这是有原因的。首先,注意这句话没有说“在Windows操作系统上开发应用程序”。尽管.NET Framework的Microsoft版本运行在Windows操作系统上,但以后将推出运行在其他操作系统上的版本,例如Mono,它是.NET Framework的开发源代码版本(包含一个C#编译器),该版本可以运行在几个操作系统上,包括各种Linux版本和Mac OS。许多这类项目正在开发,在读者阅读本书时可能就已发布了。另外,还可以在个人数字助手(PDA)类设备和一些智能电话上使用Microsoft .NET Compact Framework(基本上是完整 .NET Framework的一个子集)。使用.NET Framework的一个主要原因是它可以作为集成各种操作系统的方式。   另外,上面给出的.NET Framework定义并没有限制应用程序的类型。这是因为本来就没有限制。.NET Framework可以创建Windows应用程序、Web应用程序、Web服务和其他各种类型的应用程序。   .NET Framework的设计方式保证它可以用于各种语言,包括本书要介绍的C#语言,以及C++、Visual Basic、JScript,甚至一些旧的语言,如COBOL。为此,还推出了这些语言的.NET版本,目前还在不断推出更多的.NET版本的语言。所有这些语言都可以访问.NET Framework,它们还可以彼此交互。C#开发人员可以使用Visual Basic程序员编写的代码,反之亦然。   所有这些提供了意想不到的多样性,这也是.NET Framework具有诱人前景的部分原因。.NET Framework的内容   .NET Framework主要包含一个非常大的代码库,可以在客户语言(如C#)中通过面向对象编程技术(OOP)来使用这些代码。这个库分为不同的模块,这样就可以根据希望得到的结果来选择使用其中的各个部分。例如,一个模块包含Windows应用程序的构件,另一个模块包含联网的代码块,还有一个模块包含Web开发的代码块。一些模块还分为更具体的子模块,例如在Web开发模块中,有用于建立Web服务的子模块。   其目的是,不同的操作系统可以根据自己的特性,支持其中的部分或全部模块。例如,PDA支持所有的核心.NET功能,但不需要某些更深奥的模块。   部分.NET Framework库定义了一些基本类型。类型是数据的一种表达方式,指定其中最基础的部分(例如32位带符号的整数),以便使用.NET Framework在各种语言之间进行交互操作。这称为通用类型系统(Common Type System,CTS)。   除了支持这个库以外,.NET Framework还包含.NET公共语言运行库(Common Language Runtime,CLR),它负责管理用.NET库开发的所有应用程序的执行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值