C# 是gcroot和/clr混合模式和C++;包装从纯C到C的最短路径?

C# 是gcroot和/clr混合模式和C++;包装从纯C到C的最短路径?,c#,c,clr,C#,C,Clr,因此,我正在编写一个插件DLL,它是纯C(和一堆外来包含),但大多数实际代码都在现有的C#类库中。我正在寻找从C(不是C++)到C#的最短路径。外文包含不是C++安全的。 < > C++中有大量的样本,对于纯C.< 似乎我应该能够将整个DLL编译为/clr,但不能将C编译为/clr;然后在同一个DLL中包含一个C++封装器,它提供了一个C API,但是包含了托管代码来调用C类。 实例化C++类,并在C++类中的GCROOT中挂起,并将C++类指针作为空代码返回给C代码,以保持将来调用。 有很多细

因此,我正在编写一个插件DLL,它是纯C(和一堆外来包含),但大多数实际代码都在现有的C#类库中。我正在寻找从C(不是C++)到C#的最短路径。外文包含不是C++安全的。 < > C++中有大量的样本,对于纯C.< 似乎我应该能够将整个DLL编译为/clr,但不能将C编译为/clr;然后在同一个DLL中包含一个C++封装器,它提供了一个C API,但是包含了托管代码来调用C类。

实例化C++类,并在C++类中的GCROOT中挂起,并将C++类指针作为空代码返回给C代码,以保持将来调用。 有很多细节需要处理,但不是所有的代码。有更好的办法吗


我想是时候添加一些代码了

// Wrapper.h
#pragma once
// API for call by C
#ifdef __cplusplus
extern "C" {
#endif
    void* wrap_create();
    void wrap_doit(void* wrapper, char* input, char* output, int maxlen);
#ifdef __cplusplus
}
#endif

// Wrapper.cpp

#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <vcclr.h>
#include "Wrapper.h"
using namespace System;

class Wrapper {
public:
    gcroot<Wrappee::Evaluator^> eval;
    Wrapper() {}
};

void* wrap_create() {
    Wrapper* w = new Wrapper();
    w->eval = gcnew Wrappee::Evaluator();
    return w;
}

void wrap_doit(void* wrapper, char* input, char* output, int maxlen) {
    Wrapper* w = (Wrapper*)wrapper;
    String^ s = w->eval->Doit(gcnew String(input));
    pin_ptr<const wchar_t> wch = PtrToStringChars(s);
    wcstombs(output, wch, maxlen);
}

// Wrappee.cs
using System;
namespace Wrappee {
  public class Evaluator {
    string _s;
    public static Evaluator Create() {
      return new Evaluator {
        _s = "wrapped evaluator"
      };
    }

    public string Doit(string s) {
      return _s + ":" + s;
    }
  }
}
//Wrapper.h
#布拉格语一次
//C调用的API
#ifdef_uucplusplus
外部“C”{
#恩迪夫
void*wrap_create();
void wrap_doit(void*包装,char*输入,char*输出,int-maxlen);
#ifdef_uucplusplus
}
#恩迪夫
//包装器
#定义\u CRT\u安全\u无\u警告
#包括
#包括
#包括“Wrapper.h”
使用名称空间系统;
类包装器{
公众:
根评估;
包装器(){}
};
void*wrap_create(){
包装器*w=新包装器();
w->eval=gcnewwrappee::Evaluator();
返回w;
}
void wrap_doit(void*包装,char*输入,char*输出,int-maxlen){
包装器*w=(包装器*)包装器;
字符串^s=w->eval->Doit(新字符串(输入));
引脚ptr wch=PtrToStringChars(s);
wcstombs(输出,wch,最大值);
}
//Wrappee.cs
使用制度;
名称空间包装{
公共类评估员{
字符串(s);
公共静态计算器Create(){
返回新的评估器{
_s=“包装评估器”
};
}
公共字符串Doit(字符串s){
返回_s+“:”+s;
}
}
}
那为什么不行呢?代码基于以下链接:


答案是否定的,那是行不通的。托管类依赖于CLR运行时,该运行时需要由应用程序托管。对于在启动期间自动发生的托管应用程序(
mscoree.dll
),但对于本机应用程序,没有主机,因此没有CLR

所以我们必须提供一个。正如@hanspassant有益地指出的,这是“反向P/Invoke”,它确实不同。您必须通过COM主机接口,特别是
ICLRMetaHost
到达那里

好消息是,这里有一个示例来展示它是如何完成的:


还有其他示例:如果C库接口简单,搜索
CppHostCLR

本机代码的直接PInvoke可能更容易。@AlexeiLevenkov:我想到的第一件事。但是C代码非常复杂,充满了宏、位掩码和全局变量。绝对没有机会。你需要记住这将是一件容易的事。这是“反向pinvoke”,调用C代码的本机代码需要处理CLR需要加载和初始化的细节。最重要的是,托管异常是可诊断的,不会使插件主机崩溃。只有COM才能正确地完成这项工作。通过在C#中编写使用[ComVisible(true)]的适配器,或者通过使用自定义CLR宿主接口,ICLRMetaHost是核心适配器。根据你长期以来回答这些问题的记录,我相信你和我会遵循这条线索。我还是不明白为什么它不起作用。MSDN主题“使用C++互操作(隐式pNoCKE)”建议它应该。[我说过我不是一个COM迷吗?]理解pinvoke和“反向pinvoke”之间的区别非常重要。托管代码调用本机代码很容易,并且得到了很好的支持。特别是在C++/CLI中,正如那篇文章所告诉您的。问题是你的插件主机不是托管代码。你没有一个C#类试图调用你的本机代码,你是在用相反的方法。这当然是可能的,只是不容易。