如何在C#应用程序中使用C++/CLI

16

我正在尝试通过C++/CLI从我的C#应用程序中调用我的C++库。我按照此问题的示例(针对我的具体应用程序)。我的应用程序设置如下:

  • Project1:C++项目(我将其编译为DLL)
  • Project2:C++项目(我的CLR包装器;仅按照上述示例提供头文件;引用Project1)
  • Project3:C#项目(引用Project2)

不幸的是,当我实际尝试在我的C#应用程序中访问CLR包装器对象时,我收到以下错误消息:

找不到类型或命名空间名称“YourClass”(是否缺少using指令或程序集引用?)

我是否设置项目有误,或者还有其他事项需要注意?(由于专利原因,我无法发布代码,但它非常简单,可以轻松地遵循上述示例。)

更新:

所以,我完全按照Chris所说的做了(请参见下面的答案),但我仍然从我的C#应用程序接收到一条消息:“找不到类型或命名空间名称'MyProgram'(是否缺少using指令或程序集引用?)。这是我的代码(模拟版)。

  • Project1 - 这是我的C++应用程序。它可以编译/运行。我已在其他地方使用过它。 (我从此构建中获得了DLL。)
  • Project2 - 这是我的包装器代码。

MyWrapper.h

#pragma once

#include "myorigapp.h"

using namespace System;

namespace MyProgram
{
    public ref class MyWrapper
    {
    private:
        myorigapp* NativePtr;

    public:
        MyWrapper() 
        {
            NativePtr = new myorigapp();
        }

        ~MyWrapper() 
        { 
            delete NativePtr;
            NativePtr = NULL;
        }

        void dostuff()
        { 
            NativePtr->dostuff(); 
        }
    }
}
  • Project3 - 这是我的C#应用程序。

Program.cs

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

using MyProgram;

namespace Testing
{
    class Program
    {
        static void Main(string[] args)
        {
            MyWrapper p = new MyWrapper();
            p.dostuff();
        }
    }
}

Project3 引用了 Project2,而 Project2 又引用了 Project1。所有项目都能够顺利构建,没有错误(除了在 C# 代码中 using MyProgram 行处描述的错误)。


你是否缺少了using指令或程序集引用? - Hans Passant
如果您不想发布原始内容,也许可以提取相关部分,简化和修改一些名称等,但是您应该发布一些代码。 - Doc Brown
@Hans @Doc - 已更新问题,附带了代码和更多信息。 - JasCav
3个回答

9

仅仅从一个纯C++应用程序中包含头文件是不够的。在Project2中,您需要使用托管对象来包装您的非托管对象(即public ref class YourClassDotNet)。

#include "YourHeader.h"

namespace MyManagedWrapper
{
    public ref class YourClassDotNet
    {
    private:
        YourClass* ptr;

    public:
        YourClassDotNet()
        {
            ptr = new YourClass();
        }

        ~YourClassDotNet()
        {
            this->!YourClassDotNet();
        }

        !YourClassDotNet()
        {
            delete ptr;
            ptr = NULL;
        }

        void SomeMethod()
        {
            ptr->SomeMethod();
        }
    }
}

2
+1. 别忘了提到MyManagedWrapper的这些方法通常需要进行一些类型转换,从.NET数据类型到本地C++数据类型,反之亦然。 - Doc Brown
手动确保本地对象被正确释放很快变得复杂。我建议使用智能指针,而不是在每个包装类中重复该逻辑。[这是我写的一个:C++/CLI的scoped_ptr(确保托管对象正确释放所拥有的本地对象)](http://codereview.stackexchange.com/q/1695/2150) - Ben Voigt
@Doc Brown,是的,那里变得相当复杂。 - Chris Eberle
如果您已经完成此操作,则只需从C#项目中添加对Project2的引用,将using MyManagedWrapper添加到您的C#源代码中,然后像正常调用托管C++类一样调用即可。另外,重要的是两个项目(本机C++和托管C++)创建的.dll文件在运行时对您的C#项目都是可访问的。 - Chris Eberle
这是我C#项目中的所有内容,使其调用C++项目:<ProjectReference Include="..\lib\managedcpp\managedcpp.vcproj"> <Project>{126ABE8D-016D-47CF-8B39-F0FAFFF637A6}</Project> <Name>managedcpp</Name> </ProjectReference> - Chris Eberle
显示剩余8条评论

4

好的,嗯,我现在感觉很蠢。

事实证明,我遇到的问题(几周前就解决了 - 只是现在才更新这个答案)是因为我已经包含了头文件(请参见Chris的答案),但我实际上没有包含CPP文件(除了包含头文件外什么都没有)。

一旦我这样做了,DLL就能正确编译,并且我可以从我的C#代码中调用C++函数(使用C++/CLI)。


我尝试了您的示例并生成了一个dll,但我无法在c#程序中使用它。当我在对象浏览器中查看dll时,它是空的。我不认为我完全理解了您提出的解决方案。 - dwbrito
3
我现在遇到同样的问题,但不太理解你的解决方案。也许你可以解释一下吗?谢谢。 - VladL
头文件需要告诉编译器“你使用的功能已经有实现了”,如果Project1中有dll和相应的头文件,我就不必在任何CPP文件中关心它们(也许它们不是免费获取的)。我错过了什么,或者作者,或者C# + C++更复杂?下一个问题是否有意义——用C++/CLI包装C++头文件以在C#中使用,而没有那个C++部分的实现文件,只有为您的架构构建的库?如果是这样,你说的是哪个CPP? - amordo
@VladL OP的意思是在Project2中需要一些CPP文件才能使它“可构建”,最初他们只有头文件。10分钟后,我想到了答案。OP并不意味着来自DLL的CPP。它只是一个cpp文件,让Proj2正常工作 :) - amordo

3

Chris向您展示了如何创建一个使用非托管代码的托管类。在C#中,您可以使用unsafe完成很多操作(只是很少有人这样做)。

然而,反过来也是可能的:直接从本机类型/函数中使用.NET类型。

需要注意的是,任何托管指针都必须标记为这样。为此,C++/CLI定义了一种特殊类型的智能指针gcroot<T>(以某种方式模仿boost::shared_pointer或std::auto_ptr)。因此,要在C++类中存储托管字符串,请使用以下内容:

#include <string>
#include <vcclr.h>
using namespace System;

class CppClass {
public:
   gcroot<String^> str;   // can use str as if it were String^
   CppClass(const std::string& text) : str(gcnew String(text.c_str())) {}
};

int main() {
   CppClass c("hello");
   c.str = gcnew String("bye");
   Console::WriteLine( c.str );   // no cast required
}

请注意,如果这些问题未被解决,您将遇到一些摩擦,因为托管的null和C/C++ NULL之间存在不匹配。 您不能像预期的那样轻松地键入:
gcroot<Object^> the_thing;
...
if (the_thing != nullptr)
 ...
}

相反,您必须使用本机样式(智能包装器gcroot处理此操作)

gcroot< Object^ > the_thing;
if ( the_thing != NULL ) {} // or equivalently...
if ( the_thing ) {}

// not too sure anymore, but I thought the following is also possible:
if ( the_thing != gcroot<Object>(nullptr) ) {}

注意: 我现在无法接近任何Windows机器,所以这是我从记忆中引用的


1
为了使其正常工作,您需要为该项目打开CLI(即这不适用于纯C++应用程序)。 - Chris Eberle
1
使用/clr标志编译 - sehe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接