VC++ Calling a function of /clr project from a fun

2019-04-08 23:47发布

I referred this somewhat similar question before asking this, but unable to solve my problem

I am looking at an old application with many solutions. The problem is happening in one of the solutions (say S). Here is the situation:

  • A project (say P1) inside S has all C/C++ files and needs to call a C# function
  • Since P1 also contains .c files, I can't use /clr option with that
  • If I compile the .c files in P1 as .cpp files then it generates lots of errors, I don't intend to change the source in that legacy .c file
  • So I created another project (say P2) with /clr enabled and created one header file for function declaration and a .cpp file for the function definition; The C# call is made under it; P2 compiles fine
  • Note that P1 is a .dll and P2 is created as a static library;
  • P2 is mentioned under the P1's "Framework and refernces"

and a warning:

warning LNK4098: defaultlib 'MSVCRT' conflicts with use of other libs; use /NODEFAULTLIB:library

Now with all these, I get 3 linker errors in P1:

error LNK2005: "private: __thiscall type_info::type_info(class type_info const &)" (??0type_info@@AAE@ABV0@@Z) already defined in libcmtd.lib(typinfo.obj)

error LNK2005: "private: class type_info & __thiscall type_info::operator=(class type_info const &)" (??4type_info@@AAEAAV0@ABV0@@Z) already defined in libcmtd.lib(typinfo.obj)

error LNK1169: one or more multiply defined symbols found

This error is available at many online forums including this website. But somehow I am not able to fix it after trying those options (I am new to .NET framework).
Important point is that, even if I remove the C# code from P2 then also the same error appears.

What is the correct way to fix it?

Update:

P2 just contains 1 header file with function declaration and 1 source file with function definition which is a 1 line call to the C# method; e.g.

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

P2 is newly added to compile with /clr (Removing P2 makes the solution compile fine).
I am compiling both P1 and P2 with /MD[d] options. And the above error is thrown by P1.

If I make P2 from static library (.lib) to dynamic linked library (.dll), then the above errors goes away. And the new linker error comes for the foo itself for undefined reference:

error LNK2019: unresolved external symbol "public: void __cdecl Class::foo()" referred in function { some function of P1 }

3条回答
Emotional °昔
2楼-- · 2019-04-09 00:31

I was finally able to solve this problem with lots of trial and error and internet searches in and out of StackOverflow. At least the linker errors are gone, don't know what other things may pop up, but it's a good sign.
I will try to document as much as possible below:

Question in other words:

How to link 2 dlls under the same project with one being /clr and other non clr?

Actual Problem in Brief:

  • Everything is working fine, until a requirement comes where in one of the solutions, I have to make a call to the C# module.
  • In order to make a call to C# code (or managed code), the project has to be a /clr project
  • A project can be /clr, if it contains all .cpp code and no .c code
  • In my case the main project in the solution was containing .c files; If I try to compile it with .cpp option then it will give lot of errors and I can't change that file for legacy reasons
  • So the best option is to create a new project with .h and .cpp files which would contain the interface method and its implementation (which calls C# or C++/CLI) respectively

So far it's good, but the problem comes when the new project (P2) function definition is not linked with the original project (P1). It gives various linker errors.

Solution:

The steps are with VC++2010 for novice users (like me).

Configuring P1:

  • Right click the Solution S and Add -> New Project -> Other languages -> VC++ -> CLR empty project and name it (say P2) ;Add the header file and .cpp file in appropriate sections of the project
  • This automatically sets the Properties -> Configuration Properties -> C/C++ -> Code Generation -> Runtime Library to Multi threaded DLL: /MD[d]; Which is essential
  • For the original project P1, add the appropriate include paths under Properties -> Configuration Properties -> C/C++ -> General -> Additional Include Directories; So that you can include the new header file of P2 anywhere in the P1's source files
  • Again for project P1, go to Properties -> Common Properties -> Framework and Reference -> Add New Reference and you should be able to see the P2 there; Just add it

Configuring P2:

  • The 1st step is optional as the build goes successful even without that, but I am documenting; Properties -> Common Properties -> Framework and References -> Add New Reference -> <Select the C# or whatever external DLL you would want to call from P2>
  • For new project P2, set the configuration as DLL under Properties -> Configuration Properties -> General -> Project Defaults -> Configuration Type -> Dynamic Library (DLL)
  • If it makes sense for the project P2, In the same page you should set the Output Directory and Intermediate Directory also in sync (not exactly same) with that of P1
  • Again for project P2 go to Properties -> Configuration Properties -> Linker -> General -> Ignore Import Library -> No; I did this because it's like that in P1 as well
  • Now the most important part: Whatever classes you have added inside the new header files of P2, we need to mention __declspec(dllexport) (or __declspec(dllimport), not sure but both works); I got this crucial info from this question and this question

And with above steps the build was successful!
Probably there could be things which are missed, and due to that I face some runtime issues. However at least I was able to link 2 DLL projects under the same solution which are with and without /clr.

查看更多
贼婆χ
3楼-- · 2019-04-09 00:33

Another solution is to make your project a mixed-mode executable. You can have different C++ files in it compiling as different things. Most of it will be just as it was, but you can have different compiler settings for individual C++ files, and compile just those with the /clr compiler flag. You can call .NET objects from that C++/CLI code directly.

How you link them is to have a header file in common that has NO .NET aspects to it, just a class or function prototypes, and have the implementation of those in the file that's compiled with /clr.

The main "gotchas" here are:

  • You'll need to disable a lot of other options if you're only compiling that one file /clr like precompiled headers and such for that file. You can still have it on the rest of the project, but not that one. Basically, add your new file, use the /clr option, then start going through your compiler errors, un-checking and changing fields in the properties until it works.
  • Be triple-sure that you're altering the one file's compile options, not the entire project's. Right-click the .cpp file itself.
  • Include the .h file both where you need to call the functions from (unmanaged) and where you're implementing them, which is the .cpp file compiled with /clr.
  • One "cheat" from another website is to actually add to the project a "UI->Windows Form" and since that's CLR, it'll add one file, and set up the rest of the project for you. Then you just remove it, and add back the actual source file you want. Kind of works.
  • Another cheat is to add a new project of type "C++->CLR->CLR Console Application" and then compare the project files so you can get a few things like FrameWork version and such right
  • After doing all of this, save, close, and re-open your solution. Then in the "properties" of the C++ project, you'll be able to add essential things like references to .NET assemblies, like System and anything else you'll need for your CLR sections. In contrast to above, where compiler options need to be per-file, in this case the assembly includes are project-wide.

So be very careful to ONLY set /clr for the specific .cpp files (probably just 1) you want compiled that can access .NET. The rest of your compilation should be unaffected. Then just call your managed static methods (and instantiate classes too, if necessary) from those specific C++ files.

If you want a working project for this, send me a PM and I'll email you a 2012 project that does exactly this.

Edit: Here's the project: http://www.mediafire.com/download/rfhk5hx6x27fp0m/MixedModeExecutable.7z

Put that into a 2012 solution, and compile it. It should "just work" for things.

As for how to make it:

  1. Make a new Win32 console application.
  2. Add both a .cpp file and a .h file to the project.
  3. Define your functions and/or classes in the .h file using PURE C++ code, not anything that requires the CLR (no String^ or whatever) but you CAN forward-declare pointers to classes that WILL contain such things. You'll see this in the example project.
  4. Right-click the .cpp file that will contain the implementation of the class and ensure that "Common Language Runtime support" is enabled under "C/C++->General" options. You will also have to change "Debug Information Format" to "Runtime Database" Under "code generation" you will also have to change "Enable Minimal Rebuild" to "No", and "Enable C++ Exceptions" to "Yes with SEH Exceptions" or "No" and "Basic Runtime checks" to "Default". Change "Precompiled Headers" to "Not using". There's a way to use them, but it's a lot of extra work.
  5. The bodies of the functions and methods in the special C++ file can contain CLR code, and should.
  6. If you want to reference additional assemblies, you have to save, then close and re-open the solution, and then you can add them under "Common Properties->Frameworks and references" in the project menu. If you want to make the framework version 4.5 (defaults to 4.0), you have to change the project file and ensure that the line <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> is there in the right spot in the .vcxproj file. It's correctly 4.5 in the attached project.

So I tried that, and it worked for me. If it doesn't, examine the attached file and see if THAT works for you on VS 2012.

查看更多
我只想做你的唯一
4楼-- · 2019-04-09 00:40

Well, you are not linking C# code, that's just not possible so that's not the source of the problem. The warning is the first hint at the core problem, you are trying to link code that was compiled with /MT and thus has a dependency on libcmtd.lib, the static version of the CRT. Your C++/CLI code will always be compiled with /MD and thus has a dependency on msvcrtd.lib, the version of the CRT that's stored in a DLL and can be shared between multiple modules.

You cannot mix both versions of the CRT in one executable, that's why the linker objects with LNK4098. The link fails when it sees two copies of the type_info class implementation, one from libcmtd.lib and another from msvcrtd.lib and cannot decide which one you really want.

Furthermore, a C++/CLI project has a rock hard requirement that you must use /MD and link with msvcrtd.lib, the static version of the CRT is not supported. You must go back to the project that compiles the code with /MT and change the setting to /MD. Project + Properties, C/C++, Code Generation, Runtime library setting. It isn't otherwise clear which particular project has this problem. Beware of .lib files you got from elsewhere that were just compiled with the wrong setting. If you have no idea which is the troublemaker then grep the files for "-MTd", the .lib files contain a copy of the original compile command.

查看更多
登录 后发表回答