从非托管代码调用托管.NET代码的最佳方法

7
我正在寻找一种最佳的方法,从非托管C++代码中调用托管.NET代码。我已经找到了有关在C++应用程序中托管.NET的信息,并且可以轻松创建pRuntimeHost并启动它。然而,ExecuteInDefaultAppDomain似乎非常有限,因为我想向其发送一些参数并让它返回信息结构体。最明显的替代方案是使用COM方法,但是当前的C#代码没有设置为接口和方法。
无论哪种方式,我都希望返回整数、字符串(char *)、双精度和其他核心C++类型。由于在两端都有太多的代码需要转换C++到C#,使用Managed C++不是一个可接受的解决方案,因为使用此C++代码的其他组不想出于性能原因开始使用Managed code。
目标是尽可能少地修改现有的C++和C#代码,但仍然在C++内特定点使用C#代码中的方法,而不会对C++代码的速度造成重大影响。
根据在互联网上找到的代码,启动和关闭托管.NET的顺序如下:
#include "stdafx.h"
#include <metahost.h>

#pragma comment(lib, "mscoree.lib")

int _tmain(int argc, _TCHAR* argv[])
{
    ICLRMetaHost       *pMetaHost       = NULL;
    ICLRMetaHostPolicy *pMetaHostPolicy = NULL;
    ICLRDebugging      *pCLRDebugging   = NULL;

    HRESULT hr;
    hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (LPVOID*)&pMetaHost);
    hr = CLRCreateInstance(CLSID_CLRMetaHostPolicy, IID_ICLRMetaHostPolicy, (LPVOID*)&pMetaHostPolicy);
    hr = CLRCreateInstance(CLSID_CLRDebugging, IID_ICLRDebugging, (LPVOID*)&pCLRDebugging);

    DWORD dwVersion = 0;
    DWORD dwImageVersion = 0;
    ICLRRuntimeInfo *pRuntimeInfo;
    hr = pMetaHost->GetRuntime(L"v4.0.30319", IID_ICLRRuntimeInfo, (LPVOID *)&pRuntimeInfo);

    ICLRRuntimeHost * pRuntimeHost = NULL;
    hr = pRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (LPVOID *)&pRuntimeHost);

    hr = pRuntimeHost->Start();

    DWORD dwRetCode = 0;
    //hr = pRuntimeHost->ExecuteInDefaultAppDomain(argv[1], L"MyNamespace.MyClass", L"Message", L"Hello World!", &dwRetCode);

    // Stop the CLR runtime and shutdown cleanly.
    hr = pRuntimeHost->Stop();
    hr = pRuntimeHost->Release();
    hr = pRuntimeInfo->Release();
    hr = pCLRDebugging->Release();
    hr = pMetaHostPolicy->Release();
    hr = pMetaHost->Release();

    return 0;
}
2个回答

6

是的,我同意John的看法。您不需要显式地创建运行时的新实例并托管它。首先,这背后的技术细节并没有很好的文档说明,并且在未来的版本中可能会发生变化。其次,C++/CLI被设计成以最有效和安全的方式来实现这一点。

  1. 编写本机C++接口,表示所需的.Net功能。

  2. 使用支持CLR的dll设置,使用未管理的类实现本机接口。在其实现内部,您可以创建和访问CLR类型,并将实例变量存储在gcroot<T>字段中。使用clr互操作功能在托管/未托管代码之间进行转换,可以使用谷歌或必应搜索marshal_as

  3. 提供一个(未管理的)工厂函数,用于创建此组件的实例。这个加上未管理的C++接口就是您的本机代码将看到的API。使用这个dll的方式与使用未管理的dll完全相同。


创建额外的AppDomain的原因是C++代码已经在使用默认的AppDomain进行一些操作,我不想让我的额外.NET程序集干扰当前代码,并且避免它们的内容与我的内容相互干扰。顺便说一下,我已经成功地让CLI层正常工作,但我仍在努力弄清楚如何将整个CLI层放入一个不是默认AppDomain的单独AppDomain中。 - Andrew Stern
我自己还没有尝试过,但从理论上讲,这不应该是个问题。我理解你的情况是要从本地组件调用托管组件,对吗?有一个叫做“线程提升”的功能(谷歌或必应一下),它会在本地线程首次尝试执行托管代码时将其提升为托管线程。由于CLR不知道以这种方式调用的托管代码应该在哪个AppDomain中执行,因此它将其放入默认的AppDomain中。因此,您需要显式处理该转换,可能使用msclr::call_in_appdomain函数族。 - Paul Michalik
我已经在这个位置记录了我的最终解决方案:https://dev59.com/w2PVa4cB1Zd3GeqP2RIh - Andrew Stern

3
如果可行的话,最好的解决方案可能是创建一个托管的C++ DLL作为中间层。使用托管的C++代码是桥接托管和非托管代码的最佳/最有效的方式。
您真的不想将COM加入混合中。那会使事情变得更慢。
此外,这些“性能原因”要避免使用托管代码是否已经量化?听起来有点像一个插科打诨,用来避免他们不想要的东西。另外,您可以指出他们已经在使用托管代码,因为C#已经混合在其中了。

性能以微秒计算。因此,在所有这些之后,我已经添加了请求/响应的缓存作为C++ std::map。这允许快速查找以前请求的任何内容,而无需返回到C#层。我已在上面附加了其他信息。 - Andrew Stern

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