【问题标题】:C++ code that compiles as managed .NET in visual studio (C++/CLI) and as native C++ otherwise在 Visual Studio (C++/CLI) 中编译为托管 .NET 的 C++ 代码,否则编译为本机 C++
【发布时间】:2014-01-16 19:47:32
【问题描述】:

我有一个在 Linux 上编译的 C++ 库,并希望在 Visual Studio 中编译相同的代码(.NET 兼容代码)。

我已在 Visual Studio 中成功编译了相同的本机代码(不涉及图形)。但是,我想向 .NET 公开一些类。我可以为一些非托管类编写一个托管包装器——如果我花费了我真正没有的时间。但是,对于一些简单的类,似乎在开始时使用指令

#ifdef _MSC_VER
    public ref class myclass
#else
    class myclass
#endif 

可以解决问题。代码编译为在 Visual Studio 中管理,否则编译为本机。但是,一些涉及指针的类在编译时会返回错误。我知道“*”是非托管指针,“^”是托管指针。我可以定义吗

#ifdef _MSC_VER
    #define POINTER ^
#else
    #define POINTER *
#endif 

这是经常修改的研究代码。编写包装器将非常耗时,并且每次修改本机类时还需要修改包装器。因此,我更喜欢使用上面的条件语句(但我想尽可能少地使用)。是否有编写在本机 C++ 和 C++/CLI 之间最大程度兼容的代码的教程。

提前致谢

【问题讨论】:

  • 注意你没有GC!!
  • 或者其他方式 - 原生 C++ 不期待 GC
  • 在原生 C++ 中编译时不要使用裸指针。您有机会使用 refcounted 智能指针获得成功(不过要小心循环)。

标签: c++ .net visual-studio g++ c++-cli


【解决方案1】:

C++/CLI ref 指针与 * 指针不同。没有 1 对 1 的等价物。 ref ^ 指针指的是 CLR 中定义的“引用类型”(与“值类型”相反)的对象,它们可以用 gcnew 实例化。 Gcnew 非常重要,因为它告诉编组器要创建的对象正在垃圾回收中,因此这些类型不适用于本机代码,它们需要进行编组。但是,为了实际通过引用传递某些东西,例如为了能够写回函数中传递的参数,还需要使用%...所以Form ^ myForm,如果被传递以获取返回值需要写Form ^ %myForm。

您最好的做法是编写一个包装器并使用混合汇编范式。

我不确定是否来自 Linux C++ 代码,但要从 Windows 中的 go 一词执行此操作,您将创建一个 MFC C++ 库(或可执行文件),然后手动打开公共语言运行时并手动添加一个 AssemblyInfo。 cpp 和 app.manifest 文件添加到项目中。

从那里,在您的代码中,有一个 #pragma 用于定义哪些代码段是哪些。

对于在本机或托管上下文之外运行的代码,您可能需要编写:

#pragma unmanaged

它告诉编译器将此后的所有内容都视为非托管代码。

要为同一文件中的一段代码重新打开托管代码:

#pragma managed

等等等等。

您可能需要编写一个包装器。它不必是一个完整的包装器......也许只是一个入口点,用于返回您正在尝试做的任何事情的最终结果......以及任何需要完成的独特编程,都应该在混合程序集二进制文件,.NET 类型仅针对最面向目标的任务公开。

【讨论】:

    【解决方案2】:

    我结束了定义一个允许我在托管(在 Visual Studio 下)或非托管模式下编译的头文件。

    我必须做的另一项更改是删除类定义中的所有对象实例。例如,我必须将所有“字符串”对象更改为指针“字符串 *”(然后还要更改声明分配等),以便它可以编译为非托管代码和托管代码。

    #ifndef COMMONHEADER_H_
    #define COMMONHEADER_H_
    
    #ifdef _MSC_VER
    #define CLASS public ref class
    #define POINTER ^
    #define NEW gcnew
    #define ARRAY(A, B) array<A ^> ^ B
    #define VECTOR(A, B) array<A> ^ B
    #define NEWARRAY(A, SIZE) gcnew array<A ^>(SIZE)
    #define NULLPTR nullptr
    #else
    #define CLASS class
    #define POINTER *
    #define NEW new
    #define ARRAY(A, B) A * * B
    #define VECTOR(A, B) vector<A> * B
    #define NEWARRAY(A, SIZE) new A*[SIZE];
    #define NULLPTR NULL
    #endif
    
    #endif /* COMMONHEADER_H_ */
    

    然后我继续在我的类定义中使用这些。我用 POINTER 替换了所有对象指针“*”,用“NEW”替换了所有“new”实例,等等......

    CLASS myclass
    {
    public:
        MyObject POINTER obj1;
        ARRAY(MyObject, objArray);
    ...
    

    在某些情况下,我还必须为我的一些函数编写一个包装器。例如,下面要重载一个接受托管字符串的函数并将其传递给接受非托管字符串作为输入的函数。

    #ifdef _MSC_VER
    bool saveToTxtFile(array<unsigned char>^ file) { pin_ptr<unsigned char> tmp = &file[0]; return saveToTxtFile((char *)tmp); }
    #endif
    

    或下面将非托管数据数组作为托管数据数组返回。

    #ifdef _MSC_VER
    array<double> ^ getValues() {
         array<double> ^tmpArray = gcnew array<double>(numberOfSamples);
         for (int ind1 = 0; ind1 < numberOfSamples; ind1++) tmpArray[ind1] = data[ind1];
         return tmpArray;
    }
    #endif
    

    我知道,从长远来看,编写一个完整的包装器比使用这个技巧更优雅,而且可能更健壮。但是,我有大约 2000 行代码,它节省了我很多时间,而不必为我的 30 个对象编写包装器。此外,这样,我不必维护包装器,以防我更改类并且我不需要将类的数量加倍。最后一个好处是,当我的代码被编译为托管代码时,我的所有对象都由垃圾收集器处理。

    所以,总而言之,这不是最优雅的解决方案,但它是可能的并且有效。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-10-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-14
      • 1970-01-01
      • 2019-04-26
      相关资源
      最近更新 更多