5

我在问这个之前提到了这个有点相似的问题,但无法解决我的问题

我正在查看具有许多解决方案的旧应用程序。问题发生在其中一种解决方案中(比如 S)。情况如下:

  • S里面的一个项目(比如P1)有所有的C/C++文件,需要调用一个C#函数
  • 由于 P1 还包含 .c 文件,因此我不能使用/clr该选项
  • 如果我将 P1 中的 .c 文件编译为 .cpp 文件,则会产生很多错误,我不打算更改该旧 .c 文件中的源代码
  • 所以我创建了另一个启用的项目(比如 P2),/clr并创建了一个用于函数声明的头文件和一个用于函数定义的 .cpp 文件;C#调用是在它下面进行的;P2 编译良好
  • 请注意,P1 是一个 .dll,而 P2 是作为静态库创建的;
  • P2 在 P1 的“框架和参考”中提到

和一个警告:

警告 LNK4098:defaultlib 'MSVCRT' 与使用其他库冲突;使用 /NODEFAULTLIB:library

现在有了所有这些,我在 P1 中得到 3 个链接器错误:

错误 LNK2005:“私有:__thiscall type_info::type_info(class type_info const &)”(??0type_info@@AAE@ABV0@@Z) 已在 libcmtd.lib(typinfo.obj) 中定义

错误 LNK2005: "private: class type_info & __thiscall type_info::operator=(class type_info const &)" (??4type_info@@AAEAAV0@ABV0@@Z) 已在 libcmtd.lib(typinfo.obj) 中定义

错误 LNK1169:找到一个或多个多重定义的符号

此错误可在包括本网站在内的许多在线论坛上找到。但不知何故,在尝试了这些选项后我无法修复它(我是 .NET 框架的新手)。
重要的一点是,即使我从 P2 中删除了 C# 代码,也会出现相同的错误。

修复它的正确方法是什么?

更新

P2 只包含 1 个带有函数声明的头文件和 1 个带有函数定义的源文件,这是对 C# 方法的 1 行调用;例如

void Class::foo () {  // A static function inside Class
  std::string x = marshal_as<std::string>(C#_function);
  // ...
}

新添加了 P2 以进行编译/clr(删除 P2 使解决方案编译良好)。
我正在使用/MD[d]选项编译 P1 和 P2。上面的错误是P1抛出的。

如果我将 P2 从静态库 (.lib) 制作为动态链接库 (.dll),那么上述错误就会消失。新的链接器错误来自于foo未定义的引用本身:

错误 LNK2019:未解析的外部符号“public:void __cdecl Class::foo()”在函数 { P1 的某个函数} 中引用

4

3 回答 3

5

我终于能够通过大量的试验和错误以及进出 StackOverflow 的互联网搜索来解决这个问题。至少链接器错误消失了,不知道会弹出什么其他东西,但这是一个好兆头。
我将尝试在下面尽可能多地记录:

换句话说,问题:

如何将同一项目下的 2 个 dll 与一个存在/clr和另一个非链接clr

实际问题简介:

  • 一切工作正常,直到需要在其中一个解决方案中出现,我必须调用 C# 模块。
  • 为了调用 C# 代码(或托管代码),项目必须是 /clr项目
  • 一个项目可以是/clr,如果它包含所有 .cpp 代码并且没有 .c 代码
  • 就我而言,解决方案中的主要项目包含 .c 文件;如果我尝试使用 .cpp 选项编译它,那么它会出现很多错误,并且由于遗留原因我无法更改该文件
  • 所以最好的选择是使用 .h 和 .cpp 文件创建一个新项目,这些文件将分别包含接口方法及其实现(调用 C# 或 C++/CLI)

到目前为止它很好,但是当新项目(P2)功能定义与原始项目(P1)没有链接时,问题就来了。它给出了各种链接器错误。

解决方案:

这些步骤适用于新手用户(如我)的 VC++2010。

配置 P1

  • 右键单击解决方案 S 并将Add -> New Project -> Other languages -> VC++ -> CLR empty project其命名(例如 P2);在项目的适当部分添加头文件和 .cpp 文件
  • 这会自动设置Properties -> Configuration Properties -> C/C++ -> Code Generation -> Runtime Library to Multi threaded DLL: /MD[d]; 哪个是必不可少的
  • 对于原始项目 P1,在Properties -> Configuration Properties -> C/C++ -> General -> Additional Include Directories;下添加适当的包含路径 这样您就可以在 P1 的源文件中的任何位置包含 P2 的新头文件
  • 再次对于项目 P1,转到Properties -> Common Properties -> Framework and Reference -> Add New Reference,您应该可以在那里看到 P2;只需添加它

配置 P2

  • 第一步是可选的,因为即使没有它,构建也会成功,但我正在记录;Properties -> Common Properties -> Framework and References -> Add New Reference -> <Select the C# or whatever external DLL you would want to call from P2>
  • 对于新项目 P2,将配置设置DLLProperties -> Configuration Properties -> General -> Project Defaults -> Configuration Type -> Dynamic Library (DLL)
  • 如果对项目 P2 有意义,则在同一页面中,您应该设置Output Directory并且Intermediate Directory也与 P1 同步(不完全相同)
  • 再次为项目 P2 转到Properties -> Configuration Properties -> Linker -> General -> Ignore Import Library -> No;我这样做是因为它在 P1 中也是如此
  • 现在最重要的部分:无论您在 P2 的新头文件中添加了什么类,我们都需要提及 __declspec(dllexport)(或者__declspec(dllimport),不确定,但两者都有效);我从这个问题这个问题中得到了这个重要信息

通过以上步骤构建成功!
可能会遗漏一些东西,因此我面临一些运行时问题。但是,至少我能够在有和没有/clr.

于 2013-08-19T17:05:39.613 回答
3

好吧,您没有链接 C# 代码,这是不可能的,所以这不是问题的根源。该警告是核心问题的第一个提示,您正在尝试链接使用 /MT 编译的代码,因此依赖于 CRT 的静态版本 libcmtd.lib。您的 C++/CLI 代码将始终使用 /MD 编译,因此依赖于 msvcrtd.lib,即存储在 DLL 中并可在多个模块之间共享的 CRT 版本。

您不能在一个可执行文件中混合 CRT 的两个版本,这就是链接器对象与 LNK4098 的原因。当它看到 type_info 类实现的两个副本时,链接失败,一个来自 libcmtd.lib,另一个来自 msvcrtd.lib,并且无法确定您真正想要哪个。

此外,C++/CLI 项目有一个非常严格的要求,即您必须使用 /MD 并与 msvcrtd.lib 链接,不支持 CRT 的静态版本。您必须返回到使用 /MT 编译代码的项目并将设置更改为 /MD。项目 + 属性、C/C++、代码生成、运行时库设置。目前尚不清楚哪个特定项目存在此问题。当心您从其他地方获得的 .lib 文件,这些文件刚刚使用错误的设置进行编译。如果您不知道哪个是麻烦制造者,那么 grep 文件“-MTd”,.lib 文件包含原始编译命令的副本。

于 2013-08-17T15:00:33.557 回答
3

另一种解决方案是使您的项目成为混合模式的可执行文件。您可以将不同的 C++ 文件编译为不同的东西。大部分都和以前一样,但是您可以为各个 C++ 文件设置不同的编译器设置,并只编译带有编译器标志的那些。/clr您可以直接从该 C++/CLI 代码调用 .NET 对象。

你如何链接它们是有一个共同的头文件,它没有 .NET 方面,只是一个类或函数原型,并/clr.

这里的主要“陷阱”是:

  • 如果您只编译一个文件/clr,如预编译头文件等,则需要禁用许多其他选项。您仍然可以在项目的其余部分使用它,但不是那个。基本上,添加新文件,使用/clr选项,然后开始检查编译器错误,取消检查并更改属性中的字段,直到它起作用。
  • 三重确保您正在更改一个文件的编译选项,而不是整个项目的。右键单击 .cpp 文件本身。
  • .h文件包含在您需要从(非托管)调用函数的位置和实现它们的位置,即.cpp使用/clr.
  • 另一个网站的一个“骗子”实际上是向项目中添加一个“UI->Windows 窗体”,因为那是 CLR,它会添加一个文件,并为您设置项目的其余部分。然后您只需将其删除,然后添加回您想要的实际源文件。一类的作品。
  • 另一个技巧是添加一个类型为“C++->CLR->CLR Console Application”的新项目,然后比较项目文件,这样你就可以得到一些像FrameWork版本这样的东西
  • 完成所有这些操作后,保存、关闭并重新打开您的解决方案。然后在 C++ 项目的“属性”中,您将能够添加基本的东西,例如对 .NET 程序集的引用,System以及 CLR 部分所需的任何其他内容。与上面相反,编译器选项需要是每个文件的,在这种情况下,程序集包含的是项目范围的。

所以要非常小心,只为你想要编译的可以访问 .NET 的特定 .cpp 文件(可能只有 1 个)设置 /clr。编译的其余部分应该不受影响。然后只需从这些特定的 C++ 文件中调用您的托管静态方法(如有必要,还可以实例化类)。

如果您想要一个工作项目,请给我发邮件,我会通过电子邮件向您发送一个 2012 年的项目,该项目正是这样做的。

编辑:这是项目:http ://www.mediafire.com/download/rfhk5hx6x27fp0m/MixedModeExecutable.7z

将其放入 2012 年的解决方案中,然后编译它。它应该对事物“正常工作”。

至于如何制作:

  1. 制作一个新的 Win32 控制台应用程序。
  2. 将 .cpp 文件和 .h 文件添加到项目中。
  3. 使用纯 C++ 代码在 .h 文件中定义您的函数和/或类,而不是任何需要 CLR(无String^或其他)的东西,但您可以前向声明指向将包含此类内容的类的指针。您将在示例项目中看到这一点。
  4. 右键单击将包含类实现的 .cpp 文件,并确保在“C/C++->常规”选项下启用“公共语言运行时支持”。您还必须将“调试信息格式”更改为“运行时数据库”在“代码生成”下,您还必须将“启用最小重建”更改为“否”,并将“启用 C++ 异常”更改为“有 SEH 异常”或“否”和“基本运行时检查”为“默认”。将“预编译头文件”更改为“不使用”。有一种使用它们的方法,但是需要做很多额外的工作。
  5. 特殊 C++ 文件中的函数和方法的主体可以包含 CLR 代码,并且应该包含。
  6. 如果要引用其他程序集,则必须保存,然后关闭并重新打开解决方案,然后可以在项目菜单的“公共属性->框架和引用”下添加它们。如果要使框架版本为 4.5(默认为 4.0),则必须更改项目文件并确保该行<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>位于.vcxproj文件中的正确位置。在附加的项目中它是正确的 4.5。

所以我尝试了,它对我有用。如果没有,请检查附件并查看这是否适用于 VS 2012。

于 2013-08-20T18:05:27.087 回答