如何从Native C++调用C#库(使用C++\CLI和IJW)

ama*_*ate 47 c# c++ c++-cli visual-studio-2010 c#-4.0

背景:作为更大任务的一部分,我需要使C#库可以被非托管C++和C代码访问.为了自己回答这个问题,过去几天/几周我一直在学习C++/CLI.

似乎有许多不同的方法来实现使用来自非托管C++和C的C#dll.一些简单的答案似乎是:使用Interlope服务,使用.com.和regasm,使用PInvoke(似乎只从C#到C++),并在C++/CLR中使用IJW(看起来像是Interlope服务).我认为最好设置一个库,它可能是一个CLR包装器,它使用IJW代表本机C++和C代码调用我的C#dll.

细节:我需要将来自c ++代码的string和int的值传递给C#dll,并返回void.

相关性:许多公司有很多借口来混合和匹配C++,C和C#.性能:非托管代码通常更快,接口:托管接口通常更易于维护,部署,并且通常更容易看到,管理员也告诉我们.遗产代码也迫使我们.它就在那里(就像我们爬过的山一样).虽然如何从C#调用C++库的示例很丰富.如何通过Google搜索很难找到如何从C++代码调用C#库的示例,尤其是如果您想要查看更新的4.0+代码.

软件: C#,C++/CLR,C++,C,Visual Studio 2010和.NET 4.0

问题详情:好的多部分问题:

  1. 使用com对象有优势吗?还是PInvoke?还是其他一些方法?(我觉得这里的学习曲线同样陡峭,尽管我确实在Google Land中找到了有关该主题的更多信息.IJW似乎承诺我想要它做什么.我应该放弃寻找IJW解决方案和转而专注于此?)(优势/劣势?)

  2. 我是否正确想象有一个解决方案,我在C++/CLR中编写了一个利用IJW的包装器?我在哪里可以找到关于这个主题的更多信息,并且没有说我没有足够的Google /或者在没有告诉我你在那里看到它的情况下查看MSDN.(我想我更喜欢这个选项,努力编写清晰简单的代码.)

  3. 问题范围缩小:我觉得我的真正问题和需求是回答下面的小问题:如何设置一个非托管C++文件可以在visual studio中使用的C++/CLR库.我认为,如果我可以简单地在非托管C++代码中实例化托管C++类,那么我可能能够解决其余的问题(接口和包装等).我希望我的主要愚蠢是尝试在Visual Studio中设置引用/ #include等,我想可以有其他误解.也许这整个问题的答案可能只是一个教程或指令的链接,可以帮助我解决这个问题.

研究:我一遍又一遍地用谷歌搜索和Binged取得了一些成功.我找到了许多链接,向您展示如何使用C#代码中的非托管库.我承认有一些链接显示如何使用com对象.针对VS 2010的结果并不多.

参考文献: 我已经阅读了很多帖子.我试图解决最相关的问题.有些人似乎非常接近答案,但我似乎无法让他们工作.我怀疑我遗漏的东西非常小,比如滥用关键字ref,或者缺少#include或者使用语句,或者滥用命名空间,或者没有正确使用IJW功能,或者缺少VS的设置需要正确处理编译等.所以你想知道,为什么不包括代码?好吧,我觉得我不在我理解的地方,并期望我必须工作的代码.我想要在一个我理解的地方,当我到达那里时,我可能需要帮助修复它.我将随机包含两个链接,但我不允许在我当前的Hitpoint级别显示所有链接.

http://www.codeproject.com/Articles/35437/Moving-Data-between-Managed-Code-and-Unmanaged-Cod

这从两个方向调用托管和非托管代码的代码,从C++到Visual Basic,然后通过C++ CLR返回,当然我对C#感兴趣:http://www.codeproject.com/Articles/9903/Calling -managed码-从-非托管代码和副

Smo*_*oke 34

你可以很容易地做到这一点.

  1. 创建一个.h/.cpp组合
  2. 在新创建的.cpp文件上启用/ clr.(CPP - >右键单击 - >属性)
  3. 将"其他#using目录"的搜索路径设置为指向C#dll.

Native.h

void NativeWrapMethod();
Run Code Online (Sandbox Code Playgroud)

Native.cpp

#using <mscorlib.dll>
#using <MyNet.dll>

using namespace MyNetNameSpace;

void NativeWrapMethod()
{
    MyNetNameSpace::MyManagedClass::Method(); // static method
}
Run Code Online (Sandbox Code Playgroud)

这是使用C++\CLI中的C#lib和本机代码的基础知识.(只需在需要的地方引用Native.h,然后调用该函数.)

使用带有托管C++\CLI代码的C#代码大致相同.

关于这个问题有很多错误的信息,所以,希望这能为人们带来很多麻烦.:)


我这样做了:VS2010 - VS2012(也可能在VS2008中有效.)

  • 问题是"来自Native C++".这个答案来自C++/CLI. (10认同)

0ll*_*cks 20

更新2018年

似乎该解决方案不适用于Visual Studio 2017及以后版本.不幸的是,我目前没有使用Visual Studio,因此无法自行更新此答案.但是kaylee发布了我的答案的更新版本,谢谢!

更新结束

如果你想使用COM,这是我解决这个问题的方法:

C#库

首先,您需要一个COM兼容的库.

  • 你已经有一个?完美,你可以跳过这一部分.

  • 你有权访问图书馆吗?按照以下步骤确保COM兼容.

    1. 确保在项目属性中选中了"注册COM互操作"选项.属性 - >构建 - >向下滚动 - >注册COM互操作

以下屏幕截图显示了您找到此选项的位置.

截图项目属性构建

  1. 应该可用的所有接口和类都需要具有GUID

    namespace NamespaceOfYourProject
    {
        [Guid("add a GUID here")]
        public interface IInterface
        {
            void Connect();
    
            void Disconnect();
        }
    }
    
    namespace NamespaceOfYourProject
    {
         [Guid("add a GUID here")]
         public class ClassYouWantToUse: IInterface
         {
             private bool connected;
    
             public void Connect()
             {
                 //add code here
             }
    
             public void Disconnect()
             {
                 //add code here
             }
         }
    }
    
    Run Code Online (Sandbox Code Playgroud)

所以这几乎与你的C#代码有什么关系.让我们继续使用C++代码.

C++

  1. 首先,我们需要导入C#库.

编译C#库后,应该有一个.tlb文件.

#import "path\to\the\file.tlb"
Run Code Online (Sandbox Code Playgroud)

如果将此新创建的文件导入file.cpp,则可以将对象用作局部变量.

#import "path\to\the\file.tlb"

int _tmain(int argc, _TCHAR* argv[])
{
    CoInitialize(NULL);

    NamespaceOfYourProject::IInterfacePtr yourClass(__uuidof(NamespaceOfYourProject::ClassYouWantToUse));

    yourClass->Connect();

    CoUninitialize();
}
Run Code Online (Sandbox Code Playgroud)
  1. 使用您的类作为属性.

您会注意到第一步仅适用于局部变量.以下代码显示了如何将其用作属性.与问题相关.

您将需要CComPtr,它位于atlcomcli.h.将此文件包含在头文件中.

CPlusPlusClass.h

#include <atlcomcli.h> 
#import "path\to\the\file.tlb"

class CPlusPlusClass
{
public:
    CPlusPlusClass(void);
    ~CPlusPlusClass(void);
    void Connect(void);

private:
    CComPtr<NamespaceOfYourProject::IInterface> yourClass;
}
Run Code Online (Sandbox Code Playgroud)

CPlusPlusClass.cpp

CPlusPlusClass::CPlusPlusClass(void)
{
    CoInitialize(NULL);

    yourClass.CoCreateInstance(__uuidof(NamespaceOfYourProject::ClassYouWantToUse));

}

CPlusPlusClass::~CPlusPlusClass(void)
{
    CoUninitialize();
}

void CPlusPlusClass::Connect(void)
{
    yourClass->Connect();
}
Run Code Online (Sandbox Code Playgroud)

而已!使用COM在C++中使用C#类获得乐趣.


jsm*_*ith 10

我发现这样做的绝对最佳方法是创建一个c ++/cli桥,将c#代码连接到本机C++.我不建议使用COM对象来解决您的问题.您可以使用3个不同的项目执行此操作.

  • 第一个项目:C#库
  • 第二个项目:C++/CLI桥(这包装了C#库)
  • 第三个项目:使用第二个项目的Native C++应用程序

在这方面的一个很好的视觉/教程可以找到这里,而且我发现做到这一点最好的完整演练,可以发现在这里!在这两个链接和一点点之间,您应该能够创建一个C++/CLI桥接器,允许您在本机C++中使用C#代码.

  • 感谢您的更新。是的,我还测试了 C++/CLI 桥。工作非常好 (2认同)
  • 它有效,救生员! (2认同)

kay*_*eck 10

不幸的是,0lli.rocks答案已经过时或不完整。我的同事帮助我完成了这项工作,老实说,其中一两个实施细节并不是很明显。该答案纠正了这些空白,应该可以直接复制到Visual Studio 2017中供您自己使用。

注意事项:我还无法为C ++ / WinRT(仅供参考)使用此功能。由于IUnknown接口的歧义而导致的各种编译错误。我也遇到了使它仅用于库实现而不是在应用程序主体中使用它的问题。我尝试按照0lli.rocks的指示进行操作,但是始终无法对其进行编译。

步骤01:创建您的C#库


这是我们将用于演示的一个:

using System;
using System.Runtime.InteropServices;

namespace MyCSharpClass
{
    [ComVisible(true)] // Don't forget 
    [ClassInterface(ClassInterfaceType.AutoDual)] // these two lines
    [Guid("485B98AF-53D4-4148-B2BD-CC3920BF0ADF")] // or this GUID
    public class TheClass
    {
        public String GetTheThing(String arg) // Make sure this is public
        {
            return arg + "the thing";
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

步骤02-配置C#库以实现COM可见性


子步骤A-注册COM互操作性

在此处输入图片说明

子步骤B-使装配COM可见

在此处输入图片说明

步骤3-建立.tlb档案的资料库


除非确实需要更具体的内容ReleaseAnyCPU否则您可能只想这样做。

第4步-将.tlb文件复制到C ++项目的源位置


在此处输入图片说明

第5步-将.tlb文件导入C ++项目


#include "pch.h"
#include <iostream>
#include <Windows.h>
#import "MyCSharpClass.tlb" raw_interfaces_only

int wmain() {
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

步骤6-当Intellisense失败时不要惊慌


在此处输入图片说明

它仍然会建立。一旦将实际的类实现到C ++项目中,您将看到更多的红线代码。

步骤7-建立C ++专案以产生.tlh档案


首次构建后,此文件将进入您的中间对象构建目录

在此处输入图片说明

步骤8-评估.tlh文件以获取实施说明


这是.tlh在中间对象文件夹中生成的文件。不要编辑它。

// Created by Microsoft (R) C/C++ Compiler Version 14.15.26730.0 (333f2c26).
//
// c:\users\user name\source\repos\consoleapplication6\consoleapplication6\debug\mycsharpclass.tlh
//
// C++ source equivalent of Win32 type library MyCSharpClass.tlb
// compiler-generated file created 10/26/18 at 14:04:14 - DO NOT EDIT!

//
// Cross-referenced type libraries:
//
//  #import "C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscorlib.tlb"
//

#pragma once
#pragma pack(push, 8)

#include <comdef.h>

namespace MyCSharpClass {

//
// Forward references and typedefs
//

struct __declspec(uuid("48b51671-5200-4e47-8914-eb1bd0200267"))
/* LIBID */ __MyCSharpClass;
struct /* coclass */ TheClass;
struct __declspec(uuid("1ed1036e-c4ae-31c1-8846-5ac75029cb93"))
/* dual interface */ _TheClass;

//
// Smart pointer typedef declarations
//

_COM_SMARTPTR_TYPEDEF(_TheClass, __uuidof(_TheClass));

//
// Type library items
//

struct __declspec(uuid("485b98af-53d4-4148-b2bd-cc3920bf0adf"))
TheClass;
    // [ default ] interface _TheClass
    // interface _Object

struct __declspec(uuid("1ed1036e-c4ae-31c1-8846-5ac75029cb93"))
_TheClass : IDispatch
{
    //
    // Raw methods provided by interface
    //

      virtual HRESULT __stdcall get_ToString (
        /*[out,retval]*/ BSTR * pRetVal ) = 0;
      virtual HRESULT __stdcall Equals (
        /*[in]*/ VARIANT obj,
        /*[out,retval]*/ VARIANT_BOOL * pRetVal ) = 0;
      virtual HRESULT __stdcall GetHashCode (
        /*[out,retval]*/ long * pRetVal ) = 0;
      virtual HRESULT __stdcall GetType (
        /*[out,retval]*/ struct _Type * * pRetVal ) = 0;
      virtual HRESULT __stdcall GetTheThing (
        /*[in]*/ BSTR arg,
        /*[out,retval]*/ BSTR * pRetVal ) = 0;
};

} // namespace MyCSharpClass

#pragma pack(pop)
Run Code Online (Sandbox Code Playgroud)

在该文件中,我们看到了以下要使用的公共方法的行:

virtual HRESULT __stdcall GetTheThing (
        /*[in]*/ BSTR arg,
        /*[out,retval]*/ BSTR * pRetVal ) = 0;
Run Code Online (Sandbox Code Playgroud)

这意味着导入的方法将期望输入类型为type的字符串BSTR,并且指向BSTR输出字符串的a指针,导入的方法将成功返回该字符串。您可以这样设置它们,例如:

BSTR thing_to_send = ::SysAllocString(L"My thing, or ... ");
BSTR returned_thing;
Run Code Online (Sandbox Code Playgroud)

在使用导入方法之前,我们将必须构造它。从.tlh文件中,我们看到以下几行:

namespace MyCSharpClass {

//
// Forward references and typedefs
//

struct __declspec(uuid("48b51671-5200-4e47-8914-eb1bd0200267"))
/* LIBID */ __MyCSharpClass;
struct /* coclass */ TheClass;
struct __declspec(uuid("1ed1036e-c4ae-31c1-8846-5ac75029cb93"))
/* dual interface */ _TheClass;

//
// Smart pointer typedef declarations
//

_COM_SMARTPTR_TYPEDEF(_TheClass, __uuidof(_TheClass));

//
// Type library items
//

struct __declspec(uuid("485b98af-53d4-4148-b2bd-cc3920bf0adf"))
TheClass;
    // [ default ] interface _TheClass
    // interface _Object
Run Code Online (Sandbox Code Playgroud)

首先,我们需要使用该类的名称空间,即 MyCSharpClass

接下来,我们需要从名称空间确定智能指针,即_TheClass+ Ptr;这一步并不是很明显,因为它不在.tlh文件中。

最后,我们需要为该类提供正确的构造参数,即 __uuidof(MyCSharpClass::TheClass)

最后,

MyCSharpClass::_TheClassPtr obj(__uuidof(MyCSharpClass::TheClass));
Run Code Online (Sandbox Code Playgroud)

步骤9-初始化COM并测试导入的库


您可以使用CoInitialize(0)任何特定的COM初始值设定项来实现。

#include "pch.h"
#include <iostream>
#include <Windows.h>
#import "MyCSharpClass.tlb" raw_interfaces_only

int wmain() {
    CoInitialize(0); // Init COM
    BSTR thing_to_send = ::SysAllocString(L"My thing, or ... ");
    BSTR returned_thing;
    MyCSharpClass::_TheClassPtr obj(__uuidof(MyCSharpClass::TheClass)); 
    HRESULT hResult = obj->GetTheThing(thing_to_send, &returned_thing);

    if (hResult == S_OK) {
        std::wcout << returned_thing << std::endl;
        return 0;
    }
    return 1;
}
Run Code Online (Sandbox Code Playgroud)

再一次,当Intellisense吓坏了时不要惊慌。您属于Black Magic,Voodoo和Thar Be Dragons领域,所以请继续前进!

在此处输入图片说明


ama*_*ate 4

我发现一些东西至少开始回答我自己的问题。以下两个链接包含来自 Microsoft 的 wmv 文件,演示了如何在非托管 C++ 中使用 C# 类。

第一个使用 COM 对象和 regasm:http://msdn.microsoft.com/en-us/vstudio/bb892741

第二个使用 C++/CLI 的功能来包装 C# 类: http: //msdn.microsoft.com/en-us/vstudio/bb892742。我已经能够从托管代码实例化 ac# 类并检索视频中的字符串。它非常有帮助,但它只回答了我问题的 2/3,因为我想将一个带有字符串周边的类实例化为 ac# 类。作为概念证明,我将示例中提供的代码更改为以下方法,并实现了此目标。当然,我还添加了更改后的 {public string PickDate(string Name)} 方法来对名称字符串执行某些操作,以向自己证明它有效。

wchar_t * DatePickerClient::pick(std::wstring nme)
{
    IntPtr temp(ref);// system int pointer from a native int
    String ^date;// tracking handle to a string (managed)
    String ^name;// tracking handle to a string (managed)
    name = gcnew String(nme.c_str());
    wchar_t *ret;// pointer to a c++ string
    GCHandle gch;// garbage collector handle
    DatePicker::DatePicker ^obj;// reference the c# object with tracking handle(^)
    gch = static_cast<GCHandle>(temp);// converted from the int pointer 
    obj = static_cast<DatePicker::DatePicker ^>(gch.Target);
    date = obj->PickDate(name);
    ret = new wchar_t[date->Length +1];
    interior_ptr<const wchar_t> p1 = PtrToStringChars(date);// clr pointer that acts like pointer
    pin_ptr<const wchar_t> p2 = p1;// pin the pointer to a location as clr pointers move around in memory but c++ does not know about that.
    wcscpy_s(ret, date->Length +1, p2);
    return ret;
}
Run Code Online (Sandbox Code Playgroud)

我的部分问题是:什么更好?根据我在许多研究工作中读到的内容,答案是 COM 对象被认为更易于使用,而使用包装器则可以实现更好的控制。在某些情况下,使用包装器可以(但并非总是)减小 thunk 的大小,因为 COM 对象自动具有标准大小的占用空间,而包装器仅根据需要大小。

thunk(正如我上面所使用的)指的是在 COM 对象的情况下 C# 和 C++ 之间使用的空间、时间和资源,以及在使用 C++/CLI 进行编码的情况下 C++/CLI 和本机 C++ 之间使用的空间、时间和资源。包装纸。因此,我的答案的另一部分应该包括一个警告,即超过绝对必要的跨越 thunk 边界是不好的做法,不建议访问循环内的 thunk 边界,并且可能会错误地设置包装器,使其双重 thunk (跨越边界两次,只需要一次重击),对于像我这样的新手来说,代码似乎不正确。

关于 wmv 的两个注释。首先:一些镜头在两者中都重复使用,不要被愚弄。乍一看它们似乎相同,但它们确实涵盖了不同的主题。其次,还有一些额外的功能,例如编组,它们现在是 CLI 的一部分,但 wmv 中没有涵盖。

编辑:

请注意,您的安装会产生一个后果:CLR 将找不到您的 C++ 包装器。您必须确认 c++ 应用程序安装在使用它的任何/每个目录中,或者在安装时将库(然后需要强命名)添加到 GAC。这也意味着在开发环境中的任何一种情况下,您可能都必须将库复制到应用程序调用它的每个目录。