如何在基于C的DLL或基于CLI的DLL中包装C ++类?

时间:2013-07-28 19:41:20

标签: c++ c dll c++-cli

我被告知要将我在C ++中的写入类导入到dll中,然后在c#应用程序中使用该dll。在this guide之后我创建了dll,但我不能简单地在C#应用程序中使用它,因为它存在一些问题:

  1. 我应该为工厂功能的返回类型放置什么?

  2. const wchar_t*是构造函数参数类型的等价物吗?

  3. 如何检索和使用vector< wstring>类型的函数返回类型?

  4. 这些问题阻止我在C#应用程序中使用我的C ++ DLL。有人告诉我,我需要用C ++ / CLI创建一个包装器,然后在我的C#中使用它。但遗憾的是我不知道它,我不知道C ++ .net。

    目前似乎对我来说更耸人听闻的唯一一件事就是让它以某种方式与C兼容然后创建一个C DLL并在我的C#应用​​程序中使用它。我已经读过,在C语言中,类对象指针可以通过HANDLE来访问,所以我认为在不进行大量更改的情况下进行操作是个好主意。

    所以问题是如何使用Handles访问C中的类对象并使用它们?我怎样才能将vector<wstring>转换为C对应的? 如果我想使用CLI为我的C ++ DLL创建包装器(DLL?),要在其他dotnet应用程序中使用,我该怎么办?

3 个答案:

答案 0 :(得分:2)

直接从C#使用本机C ++类在技术上是可行的,但它并不是微不足道的,而且它甚至都不是一个好主意。对于初学者,您必须知道用于从DLL导入的名称,这将是C ++名称修改后的名称。您也无法直接从C#中访问vector之类的内容。

基本上有两个不错的选择:

第一种是编写一个带有C接口的DLL,它只使用可以编组成CLR类型的类型。您可以使用指针和IntPtr类型,但是您无法真正取消引用这些指针。您几乎可以将它们存储在C#代码中,然后在需要时将它们传递回本机DLL。您也可以使用简单的struct类型,只要您不需要深层复制即可。此选项涉及使用P / Invoke。

第二个选项是编写混合模式C ++ / CLI程序集,该程序集实现了访问本机代码所需的所有逻辑。这个程序集可以直接访问C#代码中的类和数据,也可以直接访问您的本机代码,尽管您应该预先警告,有些烦人的中断,你不能混合这两个。例如,C ++ / CLI中的ref class不能拥有shared_ptr成员。但是,它可以将原始C ++指针作为成员。 (混合模式)本机类也可以访问CLR句柄类型,并通过它调用C#代码。此选项涉及使用C ++ Interop。

值得注意的是,你也可以采用C ++ Interop的另一种方式。您可以让C#代码访问混合模式C ++ / CLI程序集,该程序集为某些本机代码提供.NET接口。但是,在这种情况下你仍然需要做一些翻译,所以它并不比第一个选项好很多。

关于C ++ Interop的完整教程会相当冗长。我建议您阅读here并对Google上的C ++ Interop进行进一步调查。

答案 1 :(得分:2)

为了使C wrapper类的C++用于例如C#应用程序,您可以执行以下操作。

在Visual Studio中选择Win32 Console Application并输入名称,然后单击“下一步”,在下一个窗格中选择DLL,然后单击“完成”。完成后,您将使用包含3个文件的DLL项目进行表示。

testdll.h 
testdll.cpp
dllmain

删除testdll.htestdll.cpp文件中存在的所有内容,并将以下内容分别复制到每个文件中。将这些行添加到testdll.h

// Our C wrapper for creating a dll to be used in C# apps

// The following ifdef block is the standard way of creating macros which make exporting 
// from a DLL simpler. All files within this DLL are compiled with the TESTDLL_EXPORTS
// symbol defined on the command line. This symbol should not be defined on any project
// that uses this DLL. This way any other project whose source files include this file see 
// TESTDLL_API functions as being imported from a DLL, whereas this DLL sees symbols
// defined with this macro as being exported.
#ifdef  TESTDLL_EXPORTS
#define  TESTDLL_API __declspec(dllexport)
#else
#define  TESTDLL_API __declspec(dllimport)
#endif

extern "C"
{
     TESTDLL_API int OurTestFunction(int x, int y);                         
}

在extern“C”块中,您可以在其中定义接口,用于访问类成员函数的函数。在函数原型之前注意TESTDLL。您的所有功能都必须按此进行。

将这些添加到testdll.cpp文件中:

#include "testdll.h"
#include "ourClass.h"

#define DLL_EXPORT

extern "C"
{
    OurClass ourObject;
    TESTDLL_API int OurTestFunction(int x, int y)
    {
        return ourObject.Add(x,y);
    }
}

你编译它并获得一个可以在C#应用程序中使用的基于C的dll 有几点需要注意,更重要的是:

  1. 您需要了解您用作代理的代码 - 我的意思 testdll.h内的函数定义只能使用C 兼容类型,它毕竟不是C ++。
  2. 是你希望能够分配你的新对象 而不只是使用一个全局对象来访问所有方法。
  3. 为此,如果需要在成员函数之间传递类对象,则需要先将其转换为C可以理解的void*,然后传递它并使用它来访问您的成员函数。

    例如,我在testdll.h中会有这样的内容,以使用户能够间接管理对象:

    #ifdef TESTDLL_EXPORTS
    #define TESTDLL_API __declspec(dllexport)
    #else
    #define TESTDLL_API __declspec(dllimport)
    #endif
    
    extern "C"
    {
        TESTDLL_API int OurTestFunction(int x, int y);                      
    
        TESTDLL_API void*  CreateHandle();
        TESTDLL_API void*  GetCurrentHandle();
        TESTDLL_API void   DisposeCurrentHandle();
        TESTDLL_API void   SetCurrentHandle(void* handle);
        TESTDLL_API void*  GetHandle();
        TESTDLL_API void   DisposeHandle(void*);
        TESTDLL_API void   DisposeArrayBuffers(void);
    }
    

    在我的testdll.cpp中,我将它们定义为:

    #include "testdll.h"
    #include "ourClass.h"
    
    #define DLL_EXPORT
    
    extern "C"
    {
        OurClass *ourObject;
    
        TESTDLL_API int OurTestFunction(int x, int y)
        {
            //return ourObject.Add(x,y); -- not any more !!
            ourObject = reinterpret_cast<OurClass *>(GetHandle());
        }
    
        //Handle operations
        TESTDLL_API void* CreateHandle()
        {
            if (ourObject == nullptr)
            {
                ourObject = new OurClass ;
            }
            else
            {
                delete ourObject ;
                ourObject = new OurClass ;
            }
            return reinterpret_cast<void*>(ourObject);
        }
    
        TESTDLL_API void* GetCurrentHandle()
        {
            return reinterpret_cast<void*>(ourObject );
        }
    
        TESTDLL_API void  DisposeCurrentHandle()
        {
            delete ourObject ;
            ourObject = nullptr;
        }
    
        TESTDLL_API void  SetCurrentHandle(void* handle)
        {
            if (handle != nullptr)
            {
                ourObject = reinterpret_cast<OurClass *>(handle);
            }
            else
            {
                ourObject = new OurClass ;
            }
    
        }
    
        //factory utility function
        TESTDLL_API void* GetHandle()
        {
            void* handle = GetCurrentHandle();
            if (handle != nullptr)
            {
                return handle;
            }
            else
            {
                ourObject = new OurClass ;
                handle = reinterpret_cast <void*>(ourObject );
            }
            return handle;
        }
    
        CDLL_API void  DisposeHandle(void* handle)
        {
            OurClass * tmp = reinterpret_cast<OurClass *>(handle);
            delete tmp;
        }
    
        TESTDLL_API void DisposeArrayBuffers(void)
        {
            ourObject = reinterpret_cast<OurClass *>(GetHandle());
            return ourObject ->DisposeBuffers();//This is a member function defined solely for this purpose of being used inside this wrapper to delete any allocated resources by our class object.
        }
    }
    

    当我们编译这个Dll时,我们可以在C#应用程序中轻松使用它。在能够使用本dll中定义的函数之前,我们需要使用适当的[ImportDll()]。所以对于我们的TestDll,我们会写:

    [DllImport(@"TestDll.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern int OurTestFunction(int firstNumber,int secondNumber); 
    

    最后使用它:

    private void btnReadBigram_Click(object sender, EventArgs e)
    {
        int x = OurTestFunction(10,50);
        MessageBox.Show(x.ToString());
    }
    

    这就是我所做的一切,使我的C ++类成员函数可以在C#应用程序中轻松访问而不会有任何麻烦。

    注意
    编译C#应用程序时,请确保选择x86平台来编译项目而不是AnyCpu。您可以通过属性更改平台。

    注2
    要了解如何为本机C ++类创建C ++ / CLI包装器,请阅读:C++/CLI wrapper for your native C++ class

答案 2 :(得分:1)

C ++ / CLI引入了托管对象,其指针标记*应该用^替换,而'new'应该替换为'gcnew',完成后不需要删除这些对象与他们一起,他们将被垃圾收集,[编辑]托管类在他们的定义[/ edit]中有一个 ref 关键字。

在C ++ / CLI包装器类 WrapperCLass 中包装C ++ MyClass 类可能如下所示:

#include <stdio.h>

class MyClass
{
public:
    void ShowStuff(const wchar_t *a)
    {
        wprintf(a);
    }
};

public ref class WrapperClass
{
    MyClass *wrapped;
public:
    WrapperClass()
    {
        wrapped = new MyClass;

    }
    ~WrapperClass()
    {
        delete wrapped;
    }
    void ShowStuff(IntPtr string)
    {
        wrapped->ShowStuff((const wchar_t *)string.ToPointer());
    }
};

如果使用此方法生成dll,则可以将其用作C#项目中的引用 而且您不必使用工厂功能机制。 在C ++ / CLI中可用,所以 const wchar_t * 也是如此。

要将 System :: String 转换为 const wchar_t * ,您可以使用以下内容:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Client
{
    class Program
    {
        static void Main(string[] args)
        {
            WrapperClass w = new WrapperClass();
            IntPtr tmp;
            w.ShowStuff(tmp = System.Runtime.InteropServices.Marshal.StringToHGlobalUni("Test"));
            System.Runtime.InteropServices.Marshal.FreeHGlobal(tmp);
        }
    }
}

(很可能有更好的方法来做到这一点......)

对于您的返回类型,您必须在包装器类中进行转换。制作一些.net集合,遍历你的vector,将wstring转换为System :: String,然后将其添加到.net集合中,然后返回。