65

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

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

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

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

4

4 回答 4

57

你看过 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”在这里很有用。

于 2010-02-06T03:55:22.917 回答
9

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

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

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

于 2010-02-06T09:16:18.337 回答
4

我将创建一个标准(非 COM/托管)动态链接库,如此处所述,然后在 c# 代码中使用DllImport 属性(平台调用)来访问导出的函数。

那篇文章的关键点:

请注意此代码中方法声明中的 __declspec(dllexport) 修饰符。这些修饰符使该方法能够由 DLL 导出,以便其他应用程序可以使用它。有关详细信息,请参阅 dllexport、dllimport。

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

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

于 2010-02-06T03:24:29.287 回答
4

我会远离 P/Invoke,因为它与 IJW(它只是工作)相比相当慢。后者允许您无缝地交织托管和非托管 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)] 公共结构 DevMode { [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] 公共字符串 dmDeviceName; }

  • 您不必将所有参数基元类型转换为它们的 .NET 对应项(此页面上的表格列出了托管类型如何映射到非托管类型)。
  • 您可以使用 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。

于 2010-02-06T03:30:05.420 回答