【问题标题】:How do I call C++/CLI from C#?如何从 C# 调用 C++/CLI?
【发布时间】:2011-01-13 18:55:30
【问题描述】:

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

我见过一些关于制作托管 C++ 包装类以与之交互的 cmets,但我不知道从哪里开始。我也不知道如何将它与所有其他代码一起编译。我真的找不到这方面的教程,而且谷歌在托管 C++ 上显示的内容似乎并没有帮助。

有什么可以帮助我的吗?这对我来说似乎不无道理。

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

【问题讨论】:

  • 这里有一个不错的教程:codeproject.com/KB/mcpp/quickcppcli.aspx#A8
  • 我知道 natice C++ 的运行速度比 C#/WPF 快,但是围绕原生 C++ 编写 CLI 包装器的开销是多少?所有这些工作真的值得吗?将 C++ 封装在 CLI 中是否比简单地将代码移植到 C++ 中更快?
  • 我修复了我的 BadImageFormatException,因为我的 CLI 项目默认在 x86 模式下编译,而我的 C# 应用程序处于任何 CPU 模式。更改它可能对您有用。

标签: c# c++ c++-cli pinvoke managed-c++


【解决方案1】:

你看过 C++/CLI 吗?

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

请注意,为了编译它,您必须进入项目设置并将“附加依赖项”标记为“从父级继承”,因为我们正在使用这些 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());
        }
    }
}

这在我的机器上给出了以下结果:

你好 m3rlinez !

C++/CLI 基本上是基于 C++ 标准的托管扩展。它允许您在 C++/CLI 项目中使用 CLR 类和数据类型,并将其公开给托管语言。您可以使用它为您的旧 C++ 库创建一个托管包装器。有一些奇怪的语法,例如 String^ 来定义 CLR 字符串的引用类型。我发现"Quick C++/CLI - Learn C++/CLI in less than 10 minutes" 在这里很有用。

【讨论】:

  • 要进入“附加依赖项”设置,请转到项目 -> 属性 -> 配置属性 -> 链接器
【解决方案2】:

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

  1. C++/CLI
  2. 平台调用
  3. 将 C++ 包装在 COM 对象中

在工作中,我们为此使用 C++/CLI,它似乎可以工作。

【讨论】:

  • 现在有CXXI(发音为sexy)。
  • 你在工作中写什么样的软件?您是否在速度至关重要的情况下编写高性能代码?
  • 这是一个古老(20 多年)的 C++ 应用程序。新代码是用 .NET 编写的,它使用 C 和 C++ 中的旧代码。
【解决方案3】:

我会远离 P/Invoke,因为它与 IJW(It Just Works)相比相当慢。后者允许您无缝地交织托管和非托管 c++。您所要做的就是创建一个托管 c++ 程序集,编写一个在 c# 中可见的托管类,然后从中调用非托管代码。

嗯……好的。我的印象是 P/Invoke 调用速度较慢,而它们本来就不是这样。但是,通过对编组进行显式控制,您可以使您的 C++/CLI 版本在很多情况下表现得更好。

这是微软关于这两种机制的文章:

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

IJW 的优势

  • 无需为 程序使用的非托管 API。只是 包括头文件和链接 导入库。
  • IJW 机制稍快一些(例如,IJW 存根不 需要检查是否需要固定或 复制数据项,因为已经完成 由开发人员明确表示)。
  • 它清楚地说明了性能问题。在这种情况下,事实是 您正在从 Unicode 翻译 字符串转换为 ANSI 字符串,并且您 有一个伴随的内存分配 和解除分配。在这种情况下,一个 开发人员使用 IJW 编写代码 会意识到调用 _putws 和 使用 PtrToStringChars 会更好 性能。
  • 如果您使用相同的数据调用多个非托管 API,则将其封送 一次并通过封送副本是 比重新编组更有效 每次。

还有审美优势:

  • C# 代码看起来像 C# 代码,没有任何互操作性。
  • 您不必定义DLLImport 属性,您不必定义任何可能如下所示的数据结构(也具有p/invoke 特定属性):

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] 公共结构 DevMode { [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] 公共字符串 dmDeviceName; }

  • 您不必将所有参数基元类型都转换为对应的 .NET(this page 上的表格列出了托管类型如何映射到非托管类型)。
  • 您将开始使用 C++/CLI,学习起来非常有趣,而且非常精巧。自 VS 2003 以来,它已经走过了漫长的道路,现在是一种功能齐全的 .NET 语言。它的 Microsoft 文档非常好,所有 IJW 信息也是如此。
  • 与 C# 相比,在 C++/CLI 中执行 C++ 互操作感觉非常自然。这完全是主观的,但我更愿意在 C++ 中执行 Marshal.PtrToString(ptr) 的字符串编组。
  • 如果公开一个 API,您可能希望将所有 P/Invoke 内容封装在另一层中,因此您不必处理 P/Invoke 丑陋的问题。这样,您就拥有了所有编组和围绕它的 C# 层的开销。使用 C++/CLI,编组和互操作抽象集中在一处,您可以选择需要多少编组。

恕我直言,如果您在 Windows SDK 中调用一个奇怪的函数,请使用 P/Invoke。如果您要向托管世界公开一个中等复杂的 C++ API,那么肯定是 C++/CLI。

【讨论】:

  • 除了 OP 谈到创建一个托管包装器,我读到它说他们已经有非托管代码。我想在托管 C++ 中重写可能是一种选择。
  • 您的意思是 P/Invoke 调用相对较慢,还是它不仅仅是性能障碍?
  • 根据 C++ 上的 Mono 页面,IJW thunk 是 p/invoke 调用。 mono-project.com/CPlusPlus
  • 微软似乎对 IJW 和 P/Invoke msdn.microsoft.com/en-us/library/aa712981(VS.71).aspx 说同样的话“IJW 和 DLLImport PInvoke 属性使用相同的底层机制”
  • @John:您计算机上的所有代码都使用相同的底层机制(x86 CPU)。并不意味着效率没有差异。
【解决方案4】:

我将创建一个标准(非 COM/托管)动态链接库作为 described here,然后在 c# 代码中使用 DllImport attribute(平台调用)来访问导出的函数。

那篇文章的重点:

注意 __declspec(dllexport) 方法声明中的修饰符 这段代码。这些修饰符使 由 DLL 导出的方法,所以 它可以被其他人使用 应用程序。了解更多信息, 请参阅 dllexport、dllimport。

这是实际 COM 互操作包装器的更轻量级替代方案,并且避免了诸如注册等问题(DLL 可以简单地放在应用程序目录中) .

另一种选择是It Just Works (IJW)。如果您已管理 C++ 代码并且需要从其他 .NET 语言访问它,这可能是一个更好的选择。但是,如果您能够/乐意将非托管 C++ 转换为托管 C++,这只是一种选择。

【讨论】:

  • p/invoke 是用来描述这一点的术语。
  • 看了很多文章说很慢,建议谨慎使用。我建议使用 C++\CLI 和混合程序集。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-30
相关资源
最近更新 更多