4

我被告知将我在 C++ 中编写的类导入 dll,然后在 ac# 应用程序中使用该 dll。按照本指南,我创建了 dll,但我不能简单地在 C# 应用程序中使用它,因为它存在一些问题:

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

  2. const wchar_t*我的构造函数参数类型相当于什么?

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

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

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

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

4

3 回答 3

4

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

在 Visual Studio 中选择Win32 Console Application并输入名称,然后单击下一步,然后在下一个窗格中选择DLL并单击完成。完成后,您会看到一个包含 3 个文件的 DLL 项目。

testdll.h 
testdll.cpp
dllmain

testdll.h删除您的和文件中存在的所有testdll.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);                         
}

它在这个外部“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 而不是 C++。
  2. 是您希望能够分配类的新对象,而不是仅使用一个全局对象来访问所有方法。

为此,如果您需要在成员函数之间传递您的类对象,您需要首先将其转换为void*C 可以理解的,然后传递它并使用它来访问您的任何成员函数。

例如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

于 2013-08-09T15:34:21.530 回答
2

直接从 C# 使用本机 C++ 类在技术上是可行的,但这并非易事,甚至都不是一个好主意。对于初学者,您必须知道用于从 DLL 导入的名称,这将是 C++ 名称修饰之后的名称。您也不能直接vector从 C# 访问诸如此类的东西。

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

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

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

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

有关 C++ 互操作的完整教程将相当冗长。我建议您在这里阅读并进一步调查 Google 上的 C++ Interop。

于 2013-07-28T21:39:45.920 回答
1

C++/CLI 引入了托管对象,其指针标记 * 应替换为 ^,'new' 应替换为 'gcnew',完成后无需删除这些对象,它们将被垃圾收集,[edit] 托管类在其定义中具有ref关键字 [/edit]。

在 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 集合,遍历您的向量,将 wstring 转换为 System::String,并将其添加到 .net 集合中,然后返回。

于 2013-07-28T22:12:32.817 回答