在本地dll中创建c ++类以在C#中使用

时间:2014-01-02 18:34:52

标签: c# .net c++-cli pinvoke wrapper

我花了大约3天时间阅读这个话题......

由于许多教程并回答了有关如何创建本机DLL的问题,我现在完全迷失了。如果你有空闲时间,请小心解释一下这个话题,并帮助我 - 如果你没有时间,那就去那里简单的问题形式......


以下是我目前对该主题的了解:

1)我需要在类名之前使用定义为__declspec(ddlexport)__declspec(ddlimport)的宏来导出所有类方法和变量

2)我需要在某个地方使用extern "C",但我不确定

的确切位置

3)有很多方法可以做到这一点(将类作为参数传递给接受它的方法c approch / export class / use interface)


这就是我迷失的原因和方式:

1)大多数教程都是用于导出方法,我怀疑与类相比很容易(在C#中你使用[Dllimport,DLL的名称]然后你调用每个方法)

2)我是否需要在课程中使用extern "C"

3)如果我使用带接口的工厂方法,我是否需要分发包含接口的.h文件?


这是我想要做的:

1)创建一个带有类的C ++ DLL并导出该类以在.NET或C ++中使用(我想保护我的代码,因为我看到使用存储的IL可以轻松地反转托管代码。 )

2)我想拥有2个DLL,一个C ++本机DLL,另一个将是包装DLL,所以如果有人想在C ++中使用我的类,他可以直接使用本机DLL,如果他想在C#/ VB.net中使用它,他可以使用C ++ / CLI包装器DLL ...

3)没有libs,没有头文件,没有def文件,......等.....只有纯DLL(2个文件将被释放)


简单形式


假设我想从C ++类

中实例化C#中的对象
Class Human
{
 private:
    int Pee_Meter;
 public:
    Void Do_Pee()
       {
         //stuff here
       };
};

我需要做什么,只有基本的东西?使用尽可能少的文件和最大程度的代码保护,不释放头文件或任何东西,只使用DLL和可能是提及方法名称和在DLL中使用的东西的txt文件。

换句话说,这些步骤是否正确?

1)在VS2012中创建新的Win32项目,然后选择DLL作为项目类型

2)定义宏__declspec(ddlexport) / __declspec(ddlimport)并在类名之前使用它(我应该在课程中使用extern "C"吗?可能不会......)

3)编译DLL

4)在VS2012中创建一个CLR项目以使用C ++ / CLI

5)链接本机DLL(我不知道如何?PInvoke全班????????????

6)定义包装类(我还在学习,但我认为你在CLI中为本机类中的每个方法创建一个方法)

7)编译CLI DLL

我是否应该说我有Deitel和Ditel C // Deitel和Ditel C ++ //由D. S. Malik编写的C ++编程,而这三本书中没有提到任何关于制作DLL的内容,我觉得这有点愚蠢。

最后,非常感谢你浪费在帮助我的每一秒,我非常感谢你提供的每一个帮助,即使你指导我参加我以前读过的教程......我可能错过了一些内容:)

3 个答案:

答案 0 :(得分:3)

这样做了很多次,最简单的方法是在现有的类中编写C ++ / CLI包装器。原因是P / Invoke在严格C函数的调用上工作得最好,而不是C ++类中的方法。在您的示例中,如何为您指定的类调用operator new

如果你可以把它写成C ++ / CLI dll,那么你得到的是这样的:

public ref class CliHuman {
public:
    CliHuman() : _human(new Human()) { }
    ~CliHuman() { delete _human; }
protected:
    !CliHuman() { delete _human; }
public:
    void DoPee() { _human->Do_Pee(); }
private:
    Human *_human;
};

现在,你可能无法自由地做到这一点。在这种情况下,最好的办法是考虑如何公开C ++对象的C API。例如:

extern "C" {

void *HumanCreate() { return (void *)new Human(); }
void HumanDestroy(void *p) { Human *h = (Human *)h; delete h; }
void HumanDoPee(void *p) { Human *h = (Human *)h; h->Pee(); }

};

你可以很容易地调用这些包装器。

从工程角度来看,您永远不会想要这样做,因为调用.NET代码可以传递任意IntPtr。在我的代码中,我喜欢这样做:

#define kHumanMagic 0xbeefbeef;

typedef struct {
    int magic;
    Human *human;
} t_human;

static void *AllocateHuman()
{
    t_human *h = (t_human *)malloc(sizeof(t_human));
    if (!h) return 0;
    h->magic = kHumanMagic;
    h->human = new Human();
    return h;
}

static void FreeHuman(void *p) /* p has been verified */
{
    if (!p) return;
    t_human *h = (t_human)p;
    delete h->human;
    h->human = 0;
    h->magic = 0;
    free(h);
}

static Human *HumanFromPtr(void *p)
{
    if (!p) return 0;
    t_human *h = (t_human *)p;
    if (h->magic != kHumanMagic) return 0;
    return h->human;
}
void *HumanCreate() { return AllocateHuman(); }
void HumanDestroy(void *p)
{
    Human *h = HumanFromPtr(p);
    if (h) {
       FreeHuman(p);
    }
    else { /* error handling */ }
}
void HumanPee(void *p)
{
    Human *h = HumanFromPtr(p);
    if (h) h->Do_Pee();
    else { /* error handling */ }
}

你可以看到我所做的是在类的顶部创建一个光包装器,让我验证进入的内容更可能是指向我们想要的正确指针。安全性很可能不是针对您的客户而是针对您 - 如果您必须包装大量的类,这将更有可能在您使用一个包装器代替另一个包装器的代码中捕获错误。

在我的代码库中,我们发现它有一个结构,我们用低级代码和C-ish API构建一个静态库,然后将其链接到C ++ / CLI项目中调用它(虽然我想也可以从C#调用它来调用它),而不是让C ++ / CLI直接包装C ++。原因是(令我们惊讶的是),所有使用STL的低级代码都是在CLI中而不是在x86或x64中完成STL实现。这意味着,假定在STL集合上迭代的低级代码将执行类似4n CLI转换的操作。通过隔离代码,我们很好地解决了这个问题。

答案 1 :(得分:0)

我认为你最好为你的C ++代码制作一个简单的C接口。由于name mangling,C ++链接实际上只对其他C ++程序有用。但是,C函数可以在没有任何问题的情况下用于多种语言 - python,C#,haskell等。

但是,假设您希望从C接口访问某些C ++类。我喜欢这样做的方式是:

  • 在我的C ++ DLL中有一个全局对象注册表。基本上是从int到object的映射。
  • 每当我创建一个对象时,它都会获得一个新的注册表ID。
  • 每当我调用一个使用该对象的函数时,我都会传入ID。

这样的事情:

int CreateNiftyInstance()
{
   int i = global_store.get_id();
   Nifty *n = new Nifty();
   global_store.save_obj(i, n);

   return i;
}

void DoSomethingNifty(int id, const char *aCData)
{
   // lame dynamic cast.  Making it type safe is possible with dedicated stores for 
   // each type of object.
   Nifty *n = dynamic_cast<Nifty*>(global_store.get_obj(i));
   if n
   {
      n->DoSomething(aCData);
   }
}

答案 2 :(得分:0)

啊我觉得我在阅读完这篇文章后找到了我想要的东西[http://www.codeproject.com/Articles/9405/Using-classes-exported-from-a-DLL-using-LoadLibrar]

如果错误,请纠正我

  1. 首先我需要导出本机类或将工厂方法标记为extern“C”
  2. 然后在CLR项目中我使用工厂方法或使用Loadlibrary + malloc命令获取类的实例,如果我没有采用工厂方法方法
  3. 创建包装类,就像plinth告诉我要做的那样(很多人都是他)。并使用上一步中的实例来调用我的类中的方法
  4. 在发行版中包含两个dll,并指示开发人员仅引用CLR dll。
  5. 如果这样的话,那么对你们所有人来说非常棒的话

    即将开始研究......

    ...此致