托管 C++ 作为 C# 和 C++ 之间的桥梁

16

我对C++有些生疏,实际上是非常陌生的。自从大学一年级以来就没有碰过它,所以已经有一段时间了。

无论如何,我正在做与大多数人相反的事情,即从C++调用C#代码。我在网上进行了一些研究,似乎需要创建一些托管的C++来构建一个桥梁。使用 __declspec(dllexport) 然后从中创建一个dll并将整个东西用作包装器。

但我的问题是 - 我真的很难找到示例。我找到了一些基本的东西,其中有人想使用C#版本的 String.ToUpper(),但那非常基础,只是一个小代码片段而已。

有人有任何思路可以给我提供一些更具体的帮助吗?请注意,我不想使用COM,目标是根本不触及C#代码。


你想向未经管理的C/C++公开多复杂的接口? - CuppM
请查看以下链接以获取类似问题/答案:https://dev59.com/XmYr5IYBdhLWcg3w6eQ3。 - amalgamate
2个回答

15

在 Visual Studio 中创建一个新的 C++/CLI 项目,并添加对你的 C# dll 的引用。假设我们有一个名为 DotNetLib.dll 的 C# dll,其中包含以下类:

namespace DotNetLib
{
    public class Calc
    {
        public int Add(int a, int b)
        {
            return a + b;
        }
    }
}

现在将一个CLR C++类添加到您的C++/CLI项目中:

// TestCPlusPlus.h

#pragma once

using namespace System;
using namespace DotNetLib;

namespace TestCPlusPlus {

    public ref class ManagedCPlusPlus
    {
    public:
        int Add(int a, int b)
        {
            Calc^ c = gcnew Calc();
            int result = c->Add(a, b);
            return result;
        }
    };
}

这将会从C++调用C#。

现在如果需要,你可以在你的C++/CLI项目中添加一个本地的C++类来与CLR C++类通信:

// Native.h
#pragma once

class Native
{
public:
    Native(void);
    int Add(int a, int b);
    ~Native(void);
};

并且:

// Native.cpp
#include "StdAfx.h"
#include "Native.h"
#include "TestCPlusPlus.h"

Native::Native(void)
{
}

Native::~Native(void)
{
}

int Native::Add(int a, int b)
{
    TestCPlusPlus::ManagedCPlusPlus^ c = gcnew TestCPlusPlus::ManagedCPlusPlus();
    return c->Add(a, b);
}
你应该可以像正常调用其他本地C ++ dll一样调用Native类。请注意,托管C ++与C ++ / CLI不同,并已被C ++ / CLI所取代。维基百科最好地解释了这一点:http://en.wikipedia.org/wiki/C%2B%2B/CLI

12

虽然lain已经写了一个例子,但我还是会把它发出来以防万一...

编写一个包装器以访问您自己的库的过程与访问标准 .Net 库之一的过程相同。

以下是在名为CsharpProject的项目中的示例C#类代码:

using System;

namespace CsharpProject {
    public class CsharpClass {
        public string Name { get; set; }
        public int Value { get; set; }

        public string GetDisplayString() {
            return string.Format("{0}: {1}", this.Name, this.Value);
        }
    }
}
您需要创建一个托管的C++类库项目(例如CSharpWrapper),并将您的C#项目作为其引用添加到其中。为了在内部使用和引用项目中使用相同的头文件,您需要一种方法来使用正确的declspec。这可以通过定义预处理器指令(在本例中为CSHARPWRAPPER_EXPORTS)并在头文件中使用#ifdef来设置C/C++接口中的导出宏来完成。未管理的接口头文件必须包含未管理的内容(或者由预处理器过滤掉)。
#pragma once

#include <string>

// Sets the interface function's decoration as export or import
#ifdef CSHARPWRAPPER_EXPORTS 
#define EXPORT_SPEC __declspec( dllexport )
#else
#define EXPORT_SPEC __declspec( dllimport )
#endif

// Unmanaged interface functions must use all unmanaged types
EXPORT_SPEC std::string GetDisplayString(const char * pName, int iValue);

你可以创建一个内部头文件,以便在管理库文件中包含。这将添加using namespace语句,并可以包括所需的辅助函数。

托管C ++接口头文件(CsharpInterface.h):

#pragma once

#include <string>

// .Net System Namespaces
using namespace System;
using namespace System::Runtime::InteropServices;

// C# Projects
using namespace CsharpProject;


//////////////////////////////////////////////////
// String Conversion Functions

inline
String ^ ToManagedString(const char * pString) {
 return Marshal::PtrToStringAnsi(IntPtr((char *) pString));
}

inline
const std::string ToStdString(String ^ strString) {
 IntPtr ptrString = IntPtr::Zero;
 std::string strStdString;
 try {
  ptrString = Marshal::StringToHGlobalAnsi(strString);
  strStdString = (char *) ptrString.ToPointer();
 }
 finally {
  if (ptrString != IntPtr::Zero) {
   Marshal::FreeHGlobal(ptrString);
  }
 }
 return strStdString;
}

那么您只需编写包装代码的接口。

托管 C++ 接口源文件 (CppInterface.cpp):

#include "CppInterface.h"
#include "CsharpInterface.h"

std::string GetDisplayString(const char * pName, int iValue) {
 CsharpClass ^ oCsharpObject = gcnew CsharpClass();

 oCsharpObject->Name = ToManagedString(pName);
 oCsharpObject->Value = iValue;

 return ToStdString(oCsharpObject->GetDisplayString());
}

然后在您的非托管项目中包含非托管头文件,告诉链接器在链接时使用生成的.lib文件,并确保.NET和封装器DLL与您的非托管应用程序位于同一文件夹中。

#include <stdlib.h>

// Include the wrapper header
#include "CppInterface.h"

void main() {
 // Call the unmanaged wrapper function
 std::string strDisplayString = GetDisplayString("Test", 123);

 // Do something with it
 printf("%s\n", strDisplayString.c_str());
}

你解释得更详细了 - 希望我能选择两个答案。谢谢! - ist_lion
字符串是一个很好的例子,因为当你需要将原生字符串转换为托管字符串时(在大多数交互操作中这通常非常快),就需要使用转换函数。 - Iain
针对"No such file or directory"错误,您需要执行以下操作之一:1)将CppInterface.h添加到非托管应用程序中。如果所有项目都位于同一位置/存储库中,则不建议这样做,因为这会导致重复文件的维护。2)在非托管应用程序的编译包含文件夹下添加包装器项目文件的路径。对于Visual Studio,请右键单击该项目并选择“属性”。然后在“配置属性”->“C/C++”->“常规”->“附加包含目录”下添加指向CppInterface.h所在位置的路径。 - CuppM
只有一个小问题:当你找不到.lib文件并且链接器报错时,很可能是因为你没有在你的类中添加“#define EXPORT_SPEC __declspec( dllexport )”。 - laishiekai
请确保将.Net [...] DLL文件放在与您的非托管应用程序相同的文件夹中。您确定需要复制.Net运行时吗?这看起来像是一场灾难的配方,重新部署可能已过时的.Net运行时构建,并且一旦您使用相同的方法加载其他组件,例如在文件选择对话框中使用资源管理器扩展等,就会出现问题。 - undefined
1
@Ext3h,你误解了这个说法。.Net DLL并不是指.NET运行时,而是你的托管代码DLL。它必须与你的非托管代码DLL位于同一位置。 - undefined

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