在C#项目中调用C++编写的动态库的三种方式

 更新时间:2024年01月11日 08:25:44   作者:小乖兽技术  
这篇文章给大家介绍了三种方式详解如何在C#项目中调用C++编写的动态库,文中通过代码示例给大家介绍的非常详细,具有一定的参考价值,需要的朋友可以参考下

场景和优点

在以下场景下,可能会使用C#调用C++编写的dll:

  • C++库已经存在并且经过了充分测试和验证,需要被C#项目重复使用时;
  • C++编写的库中包含高性能计算、海量数据处理等需要使用底层语言实现的操作时,可以考虑将这些操作封装为动态链接库供C#调用;
  • 在跨平台开发时,C++可在多个平台上运行,通过封装为dll,可以让C#项目也能够在多个平台上运行;
  • 需要将不同的功能模块拆分成独立的组件,C++编写的dll可以作为一个独立的组件,供C#项目或其他语言的项目调用。

此外,使用C#调用C++编写的dll还有以下优点:

  • C#具有较高的开发效率和易用性,通过调用C++编写的dll可以兼顾高性能和高开发效率。
  • C#可以使用.NET Framework提供的强大工具和库,如LINQ、异步编程等等,这些工具和库可以提高开发效率,同时也可以利用C++的性能优势。
  • C#可以与其他语言,如Java、Python等配合使用,借助各种技术,如SOAP、WCF、gRPC等实现多语言之间的互操作。
  • C++作为一种系统级编程语言,可以访问系统底层资源,如内存、磁盘、网络等,C#调用C++编写的dll可以实现访问这些底层资源的功能,从而提供更多的功能。

C#调用C++编写的动态库的方式

在C#中调用C++编写的动态库有以下几种方式:

1. 使用DllImport特性

使用DllImport特性可以直接引入动态链接库中的C++函数,并在C#中进行调用。

下面是一个简单的示例:

首先,我们在C++中编写一个简单的dll,里面包含一个计算两数之和的函数addition:

c++Copy Code// file: mylib.cpp
#include "pch.h"
#include "mylib.h"

int addition(int a, int b) {
    return a + b;
}

然后,我们在C++中将其封装为一个dll,并导出addition函数:

// file: mylib.h

#ifdef MYLIB_EXPORTS
#define MYLIB_API __declspec(dllexport)
#else
#define MYLIB_API __declspec(dllimport)
#endif

extern "C" MYLIB_API int addition(int a, int b);  // export the function

接着,在C#项目中使用DllImport特性导入这个dll,并调用其中的函数:

using System.Runtime.InteropServices;

class Program
{
    [DllImport("MyLib.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern int addition(int a, int b);

    static void Main(string[] args)
    {
        int result = addition(1, 2);
        Console.WriteLine("The sum is: " + result);
    }
}

在上述示例中,我们使用DllImport特性声明了一个addition方法,将其与C++中的addition函数进行绑定。在Main函数中,我们调用了这个方法,并输出计算结果。

需要注意的是,在使用DllImport特性时,需要指定正确的dll名称和函数调用规约,否则可能会出现运行时错误。

2. 使用C++/CLI

另一种实现方式是使用C++/CLI(C++/Common Language Infrastructure)。

C++/CLI是一种结合了C++和CLR(Common Language Runtime)的语言,它可以编写针对.NET Framework/CLR的代码,同时也可以访问C++的底层资源。因此,我们可以使用C++/CLI来封装C++库,并将其作为dll供C#调用。

下面是一个简单的示例:

首先,在C++/CLI中编写一个类LibraryWrapper,里面包含一个使用C++库计算两数之和的方法Addition:

// file: LibraryWrapper.h
#pragma once

namespace MyLibrary {

    public ref class LibraryWrapper
    {
    private:
        Library* lib;  // the C++ object we want to wrap
    public:
        LibraryWrapper();  // constructor
        ~LibraryWrapper();  // destructor
        int Addition(int a, int b);  // method used to add two numbers
    };
}

其中,Library是我们需要封装的C++库中的一个类。

然后,在实现文件LibraryWrapper.cpp中实现类的构造函数、析构函数和Addition方法:

// file: LibraryWrapper.cpp
#include "pch.h"
#include "LibraryWrapper.h"
#include "Library.h"

using namespace MyLibrary;

LibraryWrapper::LibraryWrapper()
{
    lib = new Library();  // create a new Library object
}

LibraryWrapper::~LibraryWrapper() {
    delete lib;  // release the memory
}

int LibraryWrapper::Addition(int a, int b)
{
    return lib->addition(a, b);  // call the addition method in C++ library
}

这里我们实例化了一个C++库中的对象,然后在Addition方法中调用了它的addition方法。

最后,在C++/CLI项目中发布dll,并在C#项目中引用。在C#项目中,我们可以创建一个LibraryWrapper对象,并调用其中的Addition方法:

using System;
using System.Runtime.InteropServices;

namespace CppCLILibraryTest
{
    class Program
    {
        static void Main(string[] args)
        {
            MyLibrary.LibraryWrapper wrapper = new MyLibrary.LibraryWrapper();
            int result = wrapper.Addition(1, 2);
            Console.WriteLine("The sum is: " + result);
        }
    }
}

需要注意的是,当使用C++/CLI封装C++库时,我们需要确保两者所使用的Runtime是相同的。比如,如果C++库是使用静态连接的方式与CRT(C Runtime)链接的,那么我们需要在C++/CLI项目的属性中设置“/MT”选项,以保证代码使用相同的CRT版本。

3. 使用COM组件

另一种实现方式是使用COM组件。COM是微软推出的一种二进制接口标准,它可以让不同的应用程序之间以二进制码互相通信。

下面是一个简单的示例:

首先,在C++中编写一个简单的dll,里面包含一个计算两数之和的函数addition:

// file: MyLibrary.h
#pragma once

#ifdef MYLIBRARY_EXPORTS
#define MYLIBRARY_API __declspec(dllexport)
#else
#define MYLIBRARY_API __declspec(dllimport)
#endif

namespace MyLibrary {
    class MyMath {
    public:
        static int Addition(int a, int b);
    };
}

然后,我们将这个dll封装为一个COM组件。我们需要创建一个类,其中包含COM接口和类工厂:

// file: MathCOM.h
#pragma once

#include "MyLibrary.h"

class MathCOM : public IUnknown {
private:
    ULONG m_cRef;
public:
    MathCOM();
    ~MathCOM();

    // IUnknown methods
    STDMETHODIMP QueryInterface(REFIID riid, void** ppv);
    STDMETHODIMP_(ULONG) AddRef(void);
    STDMETHODIMP_(ULONG) Release(void);

    // COM interface method
    STDMETHODIMP Addition(int a, int b, int* result);
};

class MathClassFactory : public IClassFactory {
private:
    ULONG m_cRef;
public:
    MathClassFactory();
    ~MathClassFactory();

    // IUnknown methods
    STDMETHODIMP QueryInterface(REFIID riid, void** ppv);
    STDMETHODIMP_(ULONG) AddRef(void);
    STDMETHODIMP_(ULONG) Release(void);

    // IClassFactory methods
    STDMETHODIMP CreateInstance(IUnknown* pUnknownOuter, REFIID riid, void** ppvObject);
    STDMETHODIMP LockServer(BOOL fLock);
};

在实现文件MathCOM.cpp中,我们需要为这些接口方法提供具体的实现:

// file: MathCOM.cpp
#include "stdafx.h"
#include "MathCOM.h"

MathCOM::MathCOM() {
    m_cRef = 1;
}

MathCOM::~MathCOM() {}

STDMETHODIMP MathCOM::QueryInterface(REFIID riid, void** ppv) {
    *ppv = NULL;

    if (riid == IID_IUnknown || riid == IID_IDispatch)
        *ppv = this;

    if (*ppv != NULL) {
        ((LPUNKNOWN)*ppv)->AddRef();
        return S_OK;
    }

    return E_NOINTERFACE;
}

STDMETHODIMP_(ULONG) MathCOM::AddRef() {
    return InterlockedIncrement((LONG*)&m_cRef);
}

STDMETHODIMP_(ULONG) MathCOM::Release() {
    ULONG cRef = InterlockedDecrement((LONG*)&m_cRef);
    if (cRef == 0) delete this;
    return cRef;
}

STDMETHODIMP MathCOM::Addition(int a, int b, int* result) {
    *result = MyLibrary::MyMath::Addition(a, b);
    return S_OK;
}

MathClassFactory::MathClassFactory() {
    m_cRef = 1;
}

MathClassFactory::~MathClassFactory() {}

STDMETHODIMP MathClassFactory::QueryInterface(REFIID riid, void** ppv) {
    *ppv = NULL;

    if (riid == IID_IUnknown || riid == IID_IClassFactory)
        *ppv = this;

    if (*ppv != NULL) {
        ((LPUNKNOWN)*ppv)->AddRef();
        return S_OK;
    }

    return E_NOINTERFACE;
}

STDMETHODIMP_(ULONG) MathClassFactory::AddRef() {
    return InterlockedIncrement((LONG*)&m_cRef);
}

STDMETHODIMP_(ULONG) MathClassFactory::Release() {
    ULONG cRef = InterlockedDecrement((LONG*)&m_cRef);
    if (cRef == 0) delete this;
    return cRef;
}

STDMETHODIMP MathClassFactory::CreateInstance(IUnknown* pUnknownOuter, REFIID riid, void** ppvObject) {
    if (pUnknownOuter) return CLASS_E_NOAGGREGATION;

    MathCOM* pMathCOM = new MathCOM();
    if (!pMathCOM) return E_OUTOFMEMORY;

    HRESULT hResult = pMathCOM->QueryInterface(riid, ppvObject);
    pMathCOM->Release();
    return hResult;
}

STDMETHODIMP MathClassFactory::LockServer(BOOL fLock) {
    return S_OK;
}

在项目中使用C++编译器生成COM组件dll之后,在C#项目中使用COM互操作性来调用这个COM组件,代码如下:

using System.Runtime.InteropServices;

namespace COMTest
{
    [ComImport, Guid("B9D43B8A-61F3-4668-AB30-C2BE194AD0AA")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IMathCOM {
        [PreserveSig]
        int Addition(int a, int b, out int result);
    }

    [ComImport, Guid("8CFD0B22-24A3-4490-9127-9DB3FD53E15F")]
    class MathCOM { }

    class Program
    {
        static void Main(string[] args)
        {
            IMathCOM mathCOM = (IMathCOM)new MathCOM();
            int result = 0;
            mathCOM.Addition(1, 2, out result);
            Console.WriteLine("The sum is: " + result);
        }
    }
}

在这个示例中,我们声明了一个用来调用COM组件的接口IMathCOM,然后实例化MathCOM类并把它转换为IMathCOM类型,就可以调用其中的Addition方法了。

以上三种方式都可用于调用C++编写的动态库,选择使用哪种方式应该根据具体的场景和需求来决定。

到此这篇关于在C#项目中调用C++编写的动态库的三种方式的文章就介绍到这了,更多相关C#调用C++动态库内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Unity TextMeshPro实现富文本超链接默认字体追加字体

    Unity TextMeshPro实现富文本超链接默认字体追加字体

    这篇文章主要为大家介绍了Unity TextMeshPro实现富文本超链接默认字体追加字体示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-01-01
  • C#使用doggleReport生成pdf报表的方法

    C#使用doggleReport生成pdf报表的方法

    这篇文章主要介绍了C#使用doggleReport生成pdf报表的方法,结合实例形式分析了C# doggleReport安装及使用具体操作技巧,需要的朋友可以参考下
    2017-06-06
  • C#使用FileSystemWatcher控件实现的文件监控功能示例

    C#使用FileSystemWatcher控件实现的文件监控功能示例

    这篇文章主要介绍了C#使用FileSystemWatcher控件实现的文件监控功能,结合实例形式分析了C# FileSystemWatcher组件的功能及监控文件更改情况的具体使用技巧,需要的朋友可以参考下
    2017-08-08
  • C#中String类常用方法汇总

    C#中String类常用方法汇总

    这篇文章主要介绍了C#中String类常用方法,较为详细的汇总了String类中的常用方法,对于深入掌握C#字符串操作有着很好的学习借鉴价值,需要的朋友可以参考下
    2014-11-11
  • Unity实现场景漫游相机

    Unity实现场景漫游相机

    这篇文章主要为大家详细介绍了Unity实现场景漫游相机,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-10-10
  • c#中返回文章发表的时间差的示例

    c#中返回文章发表的时间差的示例

    现在是2012-12-04 11:29:59,发表时间是:2012-12-02 21:29:59,传统的ts.Days因为值为1天14小时0分0秒,会返回“昨天”,而这个会返回“前天”
    2012-12-12
  • C#调用存储过程详解(带返回值、参数输入输出等)

    C#调用存储过程详解(带返回值、参数输入输出等)

    这篇文章主要介绍了C#调用存储过程的方法,结合实例形式详细分析了各种常用的存储过程调用方法,包括带返回值、参数输入输出等,需要的朋友可以参考下
    2016-06-06
  • 访问修饰符(C# 编程指南)

    访问修饰符(C# 编程指南)

    所有类型和类型成员都具有可访问性级别,用来控制是否可以在您程序集的其他代码中或其他程序集中使用它们。您在声明类型或成员时使用以下访问修饰符之一来指定其可访问性
    2011-02-02
  • C#下使用XmlDocument操作XML详解

    C#下使用XmlDocument操作XML详解

    本文详细讲解了C#使用XmlDocument操作XML的方法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-06-06
  • C#实现单例模式的几种方法总结

    C#实现单例模式的几种方法总结

    这篇文章主要介绍了C#实现单例模式的几种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-01-01

最新评论