【C#】 使用Gsof.Native 动态调用 C动态库金沙官网线

【C#】 使用Gsof.Native 动态调用 C动态库

前言:这是一篇总结性的文章,需要有一点C++和dll基本知识的基础,在网上查阅了很多资料感觉没有一篇详细、具体、全面的dll开发介绍,我这是根据最近项目和网上资料整理出来的,并附带实例的一个总结性的文章(由于篇幅较长故不附带源码解释)。另外,个人愚昧地认为以后C++的开发会更多地面向库的开发,所以学会库的开发必不可少。

一、背景

使用C# 开发客户端时候,我们经常会调用一些标准的动态库或是C的类库。
虽然C# 提供的PInvoke的方式,但因为使用的场景的多变,有些时候可能没办法,固定位置的调用,或是需要按需求调用不同的库。 设置当前目录的方式,有时会有加载不到的问题。
CLI的方式又需要用C++做再次的封装。

 

二、说明

  1. 支持根据路径动态加载DLL类库
  2. 支持using 销毁加载的类库
  3. 调用方便

github: https://github.com/gaoshang212/gsof/tree/master/Gsof.Native

nuget: https://www.nuget.org/packages/Gsof.Native/

接口说明:

NativeFactory 创建INative调用对象:

/// <summary>
/// 创建INative 对象
/// </summary>
/// <param name="p_fileName">文件路径</param>
/// <returns></returns>
public static INative Create(string p_fileName);

/// <summary>
/// 创建INative 对象
/// </summary>
/// <param name="p_fileName">文件路径</param>
/// <param name="p_calling">调用转换方式(同PInvoke CallingConvention)/param>
/// <returns></returns>
public static INative Create(string p_fileName, CallingConvention _calling);

/// <summary>
/// 销毁INative, 也可以调用 Native的Dispose方法
/// </summary>
/// <param name="p_native"></param>
public static void Free(INative p_native);

INative:

public interface INative : IDisposable
{
    /// <summary>
    /// 获取函数委托
    /// </summary>
    /// <typeparam name="TDelegate"></typeparam>
    /// <returns></returns>
    TDelegate GetFunction<TDelegate>();
    /// <summary>
    /// 函数委托调用方式
    /// </summary>
    /// <typeparam name="TResult">返回值类型</typeparam>
    /// <typeparam name="TDelegate">函数对应的委托类型</typeparam>
    /// <param name="p_params">函数传参</param>
    /// <returns></returns>
    TResult Invoke<TResult, TDelegate>(params object[] p_params);
    /// <summary>
    /// 函数名调用
    /// </summary>
    /// <typeparam name="TResult">返回值类型</typeparam>
    /// <param name="p_funName">函数名</param>
    /// <param name="p_params">函数传参</param>
    /// <returns></returns>
    TResult Invoke<TResult>(string p_funName, params object[] p_params);
    /// <summary>
    /// 函数名调用
    /// </summary>
    /// <typeparam name="TResult">返回值类型</typeparam>
    /// <param name="p_funName">函数名</param>
    /// <param name="p_calling">调用转换方式(同PInvoke CallingConvention)</param>
    /// <param name="p_params">函数传参</param>
    /// <returns></returns>
    TResult Invoke<TResult>(string p_funName, CallingConvention p_calling, params object[] p_params);
    /// <summary>
    /// 函数名调用(非泛型)
    /// </summary>
    /// <param name="p_funName">函数名</param>
    /// <param name="p_retrunType">返回值类型</param>
    /// <param name="p_params">函数传参</param>
    /// <returns></returns>
    object Invoke(string p_funName, Type p_retrunType, params object[] p_params);
    /// <summary>
    /// 函数委托调用方式
    /// </summary>
    /// <typeparam name="TDelegate">函数对应的委托类型</typeparam>
    /// <param name="p_params">函数传参</param>
    void Call<TDelegate>(params object[] p_params);
    /// <summary>
    /// 函数名调用
    /// </summary>
    /// <param name="p_funName">函数名</param>
    /// <param name="p_params">函数传参</param>
    void Call(string p_funName, params object[] p_params);
    /// <summary>
    /// 函数名调用
    /// </summary>
    /// <param name="p_funName">函数名</param>
    /// <param name="p_calling">调用转换方式(同PInvoke CallingConvention)</param>
    /// <param name="p_params">函数传参</param>
    void Call(string p_funName, CallingConvention p_calling, params object[] p_params);
}

1、 静态链接库和动态链接库

三、使用

libtest.dll 为 中包括一个test函数

 int test(int input)
 {
     return input;
 }

1.   静态链接库(LIB)只用在程序开发期间使用,而动态链接库(DLL)在执行期间使用。

方法名调用

int input = 0;
int result = -1;
using (var native = NativeFactory.Create(@"../../libtest.dll"))
{
    result = native.Invoke<int>("test", input);
}

2.   静态链接库和动态链接库的另外一个区别在于静态链接库中不能再包含其他的动态链接库或者静态库,而在动态链接库中还可以再包含其他的动态或静态链接库。

dynamic 方式调用

  • 优点:调用方便,简单类型调用时,不用做过多的定义。
  • 缺点:4.0下性能不理想,4.5+性能好很多,但相较于委托的方式,还差些。
int input = 0;
int result = -1;
using (dynamic native = NativeFactory.Create(@"../../libtest.dll"))
{
    result = native.test<int>(input);
}

3.   静态链接库,浪费空间和资源,因为所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件。另一个问题是静态库对程序的更新、部署和发布页会带来麻烦。如果静态库libname.lib更新了,所有使用它的应用程序都需要重新编译、发布给用户(对于用户来说,可能是一个很小的改动,却导致整个程序重新下载,全量更新)。

委托方式调用

  • 优化:效率高,没有了第一次动态构造委托的消耗,可获取到函数委托增加 重复调用消耗
  • 缺点:如果函数较多,委托定义较为繁琐
[NativeFuncton("test")]
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate int Test(int p_sleep);

public void DelegateFunction()
{
    int input = 0;
    int result = -1;
    using (var native = NativeFactory.Create(@"../../libtest.dll"))
    {
        // 直接调用
        var result1 = native1.Invoke<int, Test>(input);

        // 获取函数委托调用 
        var test = native.GetFunction<Test>();
        result = test(input);
    }

    Assert.AreEqual(input, result);

}

4.   动态库则实现了增量更新,进程间可以共享动态库,节约了资源,当程序第一次调用动态库时,系统将该库加载到内存中,当另一个程序也调用这个库时,系统不再加载,而是将状态+1,当某个程序退出或释放该库时,状态则-1,直到当系统中没有程序调用该库时,系统自动将其清理并释放内存。

 

2、 知识点

1.   在VS中创建的类库有2种类型,一种是直接选择VC++类库(是使用微软版本的C++创建的类库),一种是Win32项目或Win32控制台程序,然后选择对应的类库类型,也就是ANSI标准的C++类库,一般我们用这种方式创建的类库,而它又分三种:Non-MFC DLL(非MFC动态库)、MFC Regular DLL(MFC规则DLL)、MFC ExtensionDLL(MFC扩展DLL)。

2.   非MFC动态库不采用MFC类库结构,其导出函数为标准的C接口,能被非MFC或MFC编写的应用程序所调用;MFC规则DLL包含一个继承自CWinApp的类,但其无消息循环;MFC扩展DLL采用MFC的动态链接版本创建,它只能被用MFC类库所编写的应用程序所调用。

3.   当Windows要执行一个使用了动态链接库的程序而需要加载该链接库时,动态链接库文件必须储存在含有该.EXE程序的目录下、目前的目录下、Windows系统目录下、Windows目录下,或者是在通过MS-DOS环境中的PATH可以存取到的目录下(Windows会按顺序搜索这些目录)。

4.   动态链接库模块可能有其它扩展名(如.EXE或.FON),但标准扩展名是.DLL。只有带.DLL扩展名的动态链接库才能被Windows自动加载。如果文件有其它扩展名,则程序必须另外使用LoadLibrary或者LoadLibraryEx函数加载该模块。

5.   DLL 的编制与具体的编程语言及编译器无关。只要遵循约定的DLL接口规范和调用方式,用各种语言编写的DLL都可以相互调用。

 

3、 关键字

1.   当建立一个DLL时,它应该包含处理字符和字符串的Unicode和非Unicode版的所有函数,比如实现ANSI版和宽字符版。

#ifdef UNICODE
#define TextW  //定义宽字符版的函数
#else
#define TextA  //定义ANSI版的函数
#endif

 

2.  __declspec(dllexport),该关键字位于类/函数的声明和定义中,表示该类/函数为DLL的导出类/函数。而DLL内的类/函数分两种,一种是DLL导出类/函数供外部程序调用,一种是DLL内部函数供DLL自己调用。

本文由金沙官网线上发布于编程,转载请注明出处:【C#】 使用Gsof.Native 动态调用 C动态库金沙官网线

您可能还会对下面的文章感兴趣: