如何从C#调用C++/CLI?

67

我有一个用C++实现的类,负责程序的算术计算,还有一个使用WPF的接口。我使用C#处理输入,但如何使用我的C++类呢?

我看到一些关于创建托管C++包装类以与之交互的评论,但我不知道从哪里开始。我也不知道该如何将其与所有其他代码一起编译。我找不到任何教程,谷歌上显示的关于托管C++的东西似乎并不有用。

有什么东西可以帮助我吗?这对我来说似乎不过分要求。

编辑 尝试了m3rLinEz的解决方案,但它给出了BadImageFormatException,我认为这是因为未生成DLL。我按照说明做了一切,不知道发生了什么。有什么想法吗?


这里有一个不错的教程:http://www.codeproject.com/KB/mcpp/quickcppcli.aspx#A8 - Hans Passant
1
我知道本地的C++比C#/WPF运行得更快,但是在你的本地C++周围编写CLI包装器的开销是多少?所有这些工作真的值得吗?将C++包装在CLI中是否比将代码移植到C++中更快? - Steve H.
3
我解决了BadImageFormatException的问题,因为我的CLI项目默认编译为x86模式,而我的C#应用程序是在Any CPU模式下。改变其中一个可能适用于你。 - Steve H.
4个回答

59

你是否看过C++/CLI?

让我举一个非常简短的例子。这是来自Visual C++ -> CLR -> Class Library项目的源文件。它基本上获取Windows用户名并返回它。

请注意,为了编译此代码,您需要进入项目设置,并将“Additional Dependencies”标记为“Inherit from parent”,因为我们正在使用这些Windows库(kernel32.lib、user32.lib等)。

// CSCPP.h

#pragma once

#include "windows.h"

using namespace System;

namespace CSCPP {

    public ref class Class1
    {
        // TODO: Add your methods for this class here.
    public:
        String^ GetText(){
            WCHAR acUserName[100];
            DWORD nUserName = sizeof(acUserName);
            if (GetUserName(acUserName, &nUserName)) {
                String^ name = gcnew String(acUserName);
                return String::Format("Hello {0} !", name);
            }else{
                return gcnew String("Error!");
            }
        }
    };
}

现在创建一个新的C#项目,并添加对我们第一个C++/CLI类库项目的引用。然后调用实例方法。

namespace CSTester
{
    class Program
    {
        static void Main(string[] args)
        {
            CSCPP.Class1 instance = new CSCPP.Class1();
            Console.WriteLine(instance.GetText());
        }
    }
}

在我的机器上,这产生了以下结果:

Hello m3rlinez!

C++/CLI基本上是C++标准的托管扩展。它允许您在C++/CLI项目中利用CLR类和数据类型,并将其暴露给托管语言。使用它可以为旧的C++库创建托管包装器。有一些奇怪的语法,比如String^用于定义对CLR String的引用类型。我发现“快速C++/CLI - 在不到10分钟内学习C++/CLI”在这里很有用。


3
要进入“附加依赖项”设置,请前往 项目 -> 属性 -> 配置属性 -> 链接器。 - Kashif

9

至少有三种方法可以在同一进程中从托管代码调用非托管代码:

  1. C++/CLI
  2. 平台调用(Platform Invoke)
  3. 将C++封装在COM对象中

在工作中,我们使用C++/CLI来实现此功能,它似乎很有效。


3
现在有一个叫做CXXI(发音为sexy)的东西。 - Justin
你在工作中编写什么类型的软件?你是编写需要高速运行的高性能代码吗? - Aran Mulholland
这是一个20多年前用C++编写的旧应用程序。新代码是用.NET编写的,它使用了旧的C和C++代码。 - Arve

4
我会创建一个标准的动态链接库(非COM/托管),如这里描述的,然后在C#代码中使用DLLImport属性(平台调用)来访问导出函数。该文章的关键点是:请注意此代码中方法声明中的__declspec(dllexport)修饰符。这些修饰符使得该方法能够被DLL导出,以便其他应用程序可以使用。有关更多信息,请参见dllexport、dllimport。这是实际的COM互操作包装器的轻量级替代方案,并避免了诸如注册等问题(DLL可以简单地放置在应用程序目录中)。
另一种选择是 It Just Works(IJW)。如果您有托管 C++ 代码并需要从其他 .NET 语言访问它,则这可能是更好的选择。但前提是您能够或者乐意将您的非托管 C++ 转换为托管 C++。

2
p/invoke是用来描述这个功能的术语。 - Aryabhatta
我在许多文章中读到它很慢,因此建议谨慎使用。我建议使用C++\CLI和混合程序集。 - MasterMastic

4
我建议避免使用P/Invoke,因为与IJW(It Just Works)相比,它的速度要慢得多。后者允许您无缝地交织托管和非托管的c ++。您只需要创建一个托管的c ++程序集,编写一个从c#可见的托管类,并从中调用非托管代码即可。
嗯...好吧。我认为P/Invoke调用会更慢,但它们本质上并不是。然而,通过对封送进行明确控制,您可以使C++ / CLI版本在很多情况下性能更好。
这是微软关于两种机制的文章:

http://msdn.microsoft.com/en-us/library/ms235282.aspx

IJW的优点

  • 程序使用非托管API时无需编写DLLImport属性声明。只需包含头文件并链接导入库。
  • IJW机制略微更快(例如,IJW存根不需要检查是否需要固定或复制数据项,因为开发人员明确地执行这些操作)。
  • 它清晰地说明了性能问题。在这种情况下,您正在从Unicode字符串转换为ANSI字符串,并且您有附带的内存分配和释放。在这种情况下,使用IJW编写代码的开发人员会意识到调用_putws并使用PtrToStringChars对于性能更好。
  • 如果使用相同的数据调用许多非托管API,则一次进行编组并传递编组副本比每次重新编组要高效得多。

此外,还有美学优势:

  • C#代码看起来就像C#代码,没有任何交互奇怪的东西。
  • 你不需要定义DLLImport属性,也不需要定义任何数据结构(包括p/invoke特定的属性),可能会像这样:

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public struct DevMode { [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string dmDeviceName; }

  • 你不需要将所有参数原始类型转换为它们的.NET对应类型(在this page上有一个列出管理类型映射到非托管类型的表格)。
  • 你可以使用C++/CLI进行开发,这非常有趣并且非常成熟。自VS 2003以来,它已经走了很长的路程,现在是一个完全功能的.NET语言。微软对它的文档相当不错,所有的IJW信息也都很好。
  • 在C++/CLI中做C++交互感觉非常自然,而不是在C#中。这完全是主观的,但我更喜欢在C++中进行字符串编组,而不是使用Marshal.PtrToString(ptr)
  • 如果要公开API,则可能希望将所有P/Invoke内容都包装在另一层中,这样您就不必处理P/Invoke的丑陋。这样,您有所有编组的开销和围绕它的C#层的开销。使用C++/CLI,编组和交互抽象在一个地方,您可以选择需要多少编组。
如果您在Windows SDK中调用奇数函数,建议使用P/Invoke。如果您要将中等复杂的C++ API暴露给托管世界,一定要使用C++/CLI。

除非OP谈论创建托管包装器,我理解为他们已经有了非托管代码。我猜在托管C++中进行重写可能是一个选项。 - Ash
你的意思是P/Invoke调用相对较慢,还是它比仅仅是性能障碍更具有影响力? - John K
根据 Mono C++ 页面的描述,IJW thunk 是 p/invoke 调用。http://www.mono-project.com/CPlusPlus - Matthew Olenik
微软似乎在这篇文章中也提到了IJW和P/Invoke的相似之处:http://msdn.microsoft.com/en-us/library/aa712981(VS.71).aspx,“无论是IJW还是DLLImport PInvoke属性,都使用相同的基础机制”。 - John K
1
@John:你电脑上的所有代码都使用相同的底层机制(x86 CPU)。但这并不意味着效率没有差异。 - Ben Voigt
在Mono下不支持C++/CLI。 - malat

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