背景:作为一个更大的任务的一部分,我需要一个C#库访问的非托管C ++和C代码。 在试图回答这个问题我自己,我一直在学习C ++ / CLI过去数天/周。
似乎是许多不同的方式使用C#DLL从非托管C ++和C中的一些简单问题的答案似乎来实现:使用侵犯服务,使用.COM。 和regasm,使用的PInvoke(这似乎去从C#和C ++只),并在C ++ / CLR使用IJW(它似乎被侵犯的服务)。 我想这将是最好建立一个库,也许是使用IJW调用原生C ++和C代码的代表我的C#DLL中的CLR包装。
细节:我需要字符串的值以及从INT C ++代码传递到C#的DLL,并返回void。
相关性:很多公司有很多借口混搭C ++,C和C#。 性能:非托管代码通常更快,接口:托管接口通常更易于维护,部署,并经常对眼睛更容易,经理告诉我们了。 遗留代码迫使我们太多。 正是在那里(就像我们爬过山)。 虽然如何调用从C#中的C ++库的例子有很多。 如果你想看到更新4.0+代码如何调用从C ++代码的C#库的例子是很难找到通过谷歌搜索特别。
软件:C#,C ++ / CLR,C ++,C,Visual Studio 2010中,和.NET 4.0
问细节:OK多部分的问题:
是否有优势,使用COM对象? 或PInvoke的? 或者一些其他的方法? (我觉得学习曲线,这里将是一样陡峭,尽管我确实觉得在谷歌土地的主题的更多信息。IJW似乎答应我想要做的事。我应该放弃寻找一个IJW解决方案,关注这个呢?)(优势/劣势?)
我在想象,有一个解决方案,我写了一个包装,利用IJW在C ++ / CLR正确吗? 我在哪里可以找到关于此主题的更多信息,不要说我没有足够的谷歌/或看MSDN没有告诉我你在哪里看到它。 (我想我更喜欢这个选项,在撰写清晰,简单的代码的努力。)
问题范围变窄:我觉得我真正的问题,需要的是回答下面的问题更小:我如何设置一个非托管C ++文件可以在Visual Studio中使用C ++ / CLR库。 我想,如果我可以简单地在非托管C初始化一个托管C ++类++代码,那么我也许能制定出其余的(接口和包装等)。 我想到的是我的主要的愚蠢是在试图建立参考/#包括等Visual Studio中,想清楚我能有其他的误解。 也许答案这件事可能是只是一个教程,或者帮助我说明链接。
研究:我用Google搜索,并取得了一些成功猛吃了一遍又一遍。 我发现有很多链接,告诉你如何使用从C#代码的非托管库。 我会承认出现了一些链接,展示了如何使用COM对象来做到这一点。 没有多少结果在VS 2010的目标。
参考文献:我看了一遍又一遍的许多职位。 我曾尝试通过最相关的工作。 有些人似乎功亏一篑的答案,但我似乎无法让他们的工作。 我怀疑,我缺少的是诱人小,如滥用关键字裁判,或缺少的#include或using语句,或命名空间的误用或不正确的实际使用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码-从-非托管代码和副
你可以这样做很容易。
- 创建一个.H /组合的.cpp
- 启用新创建的.cpp文件/ CLR。 (CPP - >右键 - >属性)
- 设置“附加使用#using目录”的搜索路径来对你的C#DLL点。
Native.h
void NativeWrapMethod();
Native.cpp
#using <mscorlib.dll>
#using <MyNet.dll>
using namespace MyNetNameSpace;
void NativeWrapMethod()
{
MyNetNameSpace::MyManagedClass::Method(); // static method
}
这是利用C ++ \ CLI一个C#的lib与本地代码的基本知识。 (只是参考Native.h在需要的地方,并调用该函数)。
使用C#代码与托管C ++ \ CLI代码是大致相同。
有关于这个问题很多误传,所以,希望这样可以节省别人很多的麻烦。 :)
我已经做到了这一点:VS2010 - VS2012(它可能在VS2008也可以。)
UPDATE 2018
看起来好像解决方案并不适用于Visual Studio 2017年及以后的工作。 不幸的是我目前不使用Visual Studio的工作,因此不能由我自己更新这个答案。 但王小强张贴了我的一个更新版本的答案 ,谢谢!
更新结束
如果你想使用COM,这是我为这个问题的解决方案:
C#库
首先你需要一个COM兼容库。
下面的截图显示了你发现这个选项。
所有的接口和应提供必要有一类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 } } }
所以,这几乎是你有你的C#代码做什么。 让我们继续用C ++代码。
C ++
- 首先,我们需要导入C#库。
编译你的C#库后应该有一个.tlb文件。
#import "path\to\the\file.tlb"
如果你把这个新创建的文件到您的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();
}
- 使用你的类作为属性。
你会注意到,第一步只能用一个局部变量的作品。 下面的代码演示了如何使用它作为一个属性。 有关这个问题。
您将需要但是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;
}
CPlusPlusClass.cpp
CPlusPlusClass::CPlusPlusClass(void)
{
CoInitialize(NULL);
yourClass.CoCreateInstance(__uuidof(NamespaceOfYourProject::ClassYouWantToUse));
}
CPlusPlusClass::~CPlusPlusClass(void)
{
CoUninitialize();
}
void CPlusPlusClass::Connect(void)
{
yourClass->Connect();
}
而已! 与在C ++中的C#类与COM乐趣。
我发现这样做绝对最好的方法是创建一个C ++的C#代码连接到你的本地C ++ / CLI的桥梁。 我不建议使用COM对象来解决你的问题。 你可以用3个不同的项目做到这一点。
- 第一个项目:C#库
- 二期工程:C ++ / CLI桥(这个封装了C#库)
- 第三个项目:本机C ++应用程序,使用第二个项目
在这方面的一个很好的视觉/教程可以找到这里 ,而且我发现做到这一点最好的完整演练,可以发现在这里 ! 这两个环节,一个小砂粒之间,你应该能够制定出建立一个C ++ / CLI桥梁,可以让你在你的本地C ++使用C#代码。
在从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";
}
}
}
步骤02 - 配置你的C#库COM能见度
子步骤 - 注册为COM互操作
子步骤B - 使装配COM可见
第3步-建立你的图书馆为.tlb
文件
你可能只想做,因为Release
的AnyCPU
除非你真的需要更具体的东西。
第4步-复制.tlb
文件到源位置的C ++项目
第5步-导入.tlb
文件到你的C ++项目
#include "pch.h"
#include <iostream>
#include <Windows.h>
#import "MyCSharpClass.tlb" raw_interfaces_only
int wmain() {
return 0;
}
第6步 - 当智能感知失败,不要惊慌
它仍然会建立。 你会看到更多的红色内衬代码一旦我们实现实际的类到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)
在该文件中,我们可以看到这几行,因为我们要使用的公共方法:
virtual HRESULT __stdcall GetTheThing (
/*[in]*/ BSTR arg,
/*[out,retval]*/ BSTR * pRetVal ) = 0;
这意味着,导入的方法将期望类型的输入字符串BSTR
,和一个指向BSTR
用于输出串导入的方法将返回成功。 你可以设置他们这样的,例如:
BSTR thing_to_send = ::SysAllocString(L"My thing, or ... ");
BSTR returned_thing;
之前,我们可以采用进口方式,我们将不得不建立它。 从.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
首先,我们需要使用类,这是命名空间MyCSharpClass
接下来,我们需要确定从命名空间中,这是智能指针_TheClass
+ Ptr
; 这一步是不是远程显而易见的,因为它远不在.tlh
文件。
最后,我们需要提供的类正确的施工参数,这是__uuidof(MyCSharpClass::TheClass)
与结束了,
MyCSharpClass::_TheClassPtr obj(__uuidof(MyCSharpClass::TheClass));
第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;
}
再次,当智能感知怪胎不要惊慌。 你在黑魔法,巫术,与塔尔作野狗的领土,所以记者辗转!
我发现的东西,至少开始回答我的问题。 以下两个链接有一个演示使用C#类在非托管C ++从微软的WMV文件。
这第一个使用的COM对象和regasm: http://msdn.microsoft.com/en-us/vstudio/bb892741 。
此第二个使用C ++ / CLI的特征以包裹C#类: http://msdn.microsoft.com/en-us/vstudio/bb892742 。 我已经能够从托管代码实例化一个C#类和检索字符串作为视频。 这是非常有益的,但它只是回答我的问题的2 /三分之二,因为我想实例化一个类的字符串周界进入到C#类。 作为一个概念证明我改变了的例子以下方法提供的代码,并实现了这一目标。 当然,我还添加了一个改变了{公共字符串PickDate(字符串名称)}方法做一些与名称字符串来证明自己,它的工作。
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;
}
我的问题的部分原因是:什么是好? 从我在很多很多的努力研究,答案已经阅读是COM对象被认为更容易使用,以及使用的包装,而不是允许更大的控制权。 在使用的包装可以(但不总是)减少thunk的大小,作为COM对象有时是自动地有一个标准尺寸封装和包装只有那么大,他们需要的人。
形实转换(如我在上文中使用)是指在COM对象的情况下,C#和C ++之间使用在空间时间和资源,并且在编码-使用C ++ / CLI的情况下的C / CLI和本地C ++之间包装。 所以我的答案的另一部分应包括警告,越过thunk的边界超过绝对必要的是不好的做法,访问环路内的thunk边界不推荐,而且它可能错误地设置了一个包装,使其双的thunk (穿越其中只有两次一个thunk是呼吁边界)无代码似乎是不正确的像我这样一个新手。
有两点需要注意对WMV的。 首先:一些画面在两个重复使用,千万不要上当。 起初,他们似乎相同,但它们确实涉及不同的主题。 其次,还有一些额外的功能,如编组,而现在未涵盖的WMV的CLI中的一部分。
编辑:
注意:有您的安装结果,你的C ++包装将不会被发现的CLR。 你将不得不要么确认C ++应用程序在任何安装/每次使用它,或图书馆(然后将需要强命名)在安装时添加到GAC目录。 这也意味着,在开发环境中这两种情况下,你可能将不得不库复制到每个地方的应用程序调用它的目录。
我做了一堆环顾四周,发现微软相对最近的一篇文章详细介绍它如何做(有很多老infomration左右浮动的)。 从文章本身:
的代码示例使用CLR 4层托管的API来承载CLR在本地C ++项目,负载并调用.NET组件
https://code.msdn.microsoft.com/CppHostCLR-e6581ee0
基本上,它描述的两个步骤:
- 加载CLR到进程
- 装入装配。