将 C++/CLI 转换为 C#

发布于 2024-07-04 03:01:42 字数 153 浏览 7 评论 0原文

我有一个使用 C++/CLI 的中小型项目。 我真的很讨厌 C++/CLI 的语法扩展,我更喜欢使用 C# 工作。 有没有一种工具可以很好地将一种方法转换为另一种方法?

编辑: 当我在说托管 c++ 之前我显然是指 c++/CLI

I have a small to medium project that is in C++/CLI. I really hate the syntax extensions of C++/CLI and I would prefer to work in C#. Is there a tool that does a decent job of translating one to the other?

EDIT: When I said Managed c++ before I apparently meant c++/CLI

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

没有你我更好 2024-07-11 03:01:42

.NET 托管 C++ 就像火车失事一样。 但是您研究过 C++ CLI 吗? 我认为微软在这个领域做得很好,让 C++ 成为了一流的 .NET 公民。

http://msdn.microsoft.com/en-us/magazine/cc163852。 ASPX

.NET Managed C++ is like a train wreck. But have you looked into C++ CLI? I think Microsoft did a great job in this field to make C++ a first class .NET citizen.

http://msdn.microsoft.com/en-us/magazine/cc163852.aspx

感性不性感 2024-07-11 03:01:42

我不确定这是否有效,但请尝试使用 .Net Reflector使用 ReflectionEmitLanguage 插件。 RelelectionEmitLanguage 插件声称可以将您的程序集转换为 C# 代码。

I'm not sure if this will work, but try using .Net Reflector along with ReflectionEmitLanguage plug-in. The RelelectionEmitLanguage plug-in claims to convert your assembly to c# code.

回忆凄美了谁 2024-07-11 03:01:42

不幸的是,它必须手动完成,但如果代码主要是 C++/CLI(不是本机 C++),那么实际上可以很快完成。 我在不到几个月的时间里成功地将大约 250,000 行 C++/CLI 代码移植到 C# 中,而我什至根本不太了解 C++。

如果保留 Git 历史记录很重要,您可能需要将 cpp 文件git mv 转换为 cs 文件,提交,然后开始移植。 这样做的原因是,如果你重命名后修改太多,Git 会认为你的文件是新的。

这是我移植大量代码时的方法(这样就不会永远花费时间):

  • 创建分支的另一个工作树/克隆并始终保持打开状态
    • 这非常重要,因为您将想要将您的 C# 与旧的 C++/CLI 代码进行比较
  • 将 cpp 重命名为 cs,删除头文件,提交
    • 我选择重命名 cpp 文件,因为它的 git 历史记录可能比头文件更重要
  • 在cs文件中创建命名空间+类,添加任何基类/接口(如果抽象密封,在C#中使静态)
  • 首先复制字段,然后复制构造函数,然后是属性,最后是函数
  • 开始用 Ctrl+H 替换:
    • ^ 为空
    • ::.
    • ->.
    • nullptrnull
    • 对于每个foreach
    • gcnewnew
    • L""
      • 启用区分大小写以避免意外重命名(例如,L"cool" 应变为 "cool",而不是 "coo"< /里>
    • ClassName:: 等前缀清空,以便 MyClass::MyMethod 变为 MyMethod
    • 检查红色代码并手动移植不能被替换的代码(例如一些特殊的 C++ 转换),除非您有一些很酷的正则表达式来快速完成
    • 代码编译后,再次检查它,逐行与 C++/CLI 进行比较,检查错误,清理它,然后继续。
    • 如果您遇到需要移植的依赖项,您可以暂停、移植它,然后再回来。 我做到了,但可能没那么容易。

移植,因为我必须删除 getter 和 setter 之前和之后的所有内容。我本来可以为它编写一个正则表达式,但没有这样做。

一旦移植完成,逐行检查更改、阅读代码并进行比较是非常重要的。 C++/CLI 代码并修复可能的错误。

这种方法的一个问题是您可能会在变量声明中引入错误,因为在 C++/CLI 中您可以通过两种方式声明变量:

  • MyType^ 变量; <- null
  • MyType 变量;< /code> <- 调用默认构造函数

在后一种情况下,您实际上想要执行 MyType variable = new MyType(); 但由于您已经删除了所有 ^ 您必须手动检查并测试哪一个是正确的。 您当然可以手动替换所有 ^ ,但对我来说,这会花费太长时间(加上懒惰),所以我就这样做了。

其他建议:

我在移植时遇到的一些问题:

  • 可以在 C++/CLI 中调用基类,如下所示:BaseClass->DoStuff,但在 C# 中,您必须执行 base.DoStuff 相反。

  • C++/CLI 允许这样的语句:if (foo),但在 C# 中这必须是显式的。 对于整数,它是 if (foo != 0) 或对于对象 if (foo != null)

  • 基类中的事件可以在 C++/CLI 中调用,但在 C# 中则不可能。 解决方案是在基类中创建一个方法,例如 OnSomeEvent,并在其中调用事件。

  • C++/CLI 自动生成事件调用的 null 检查,因此在 C# 中请确保添加显式 null 检查:MyEvent?.Invoke(this, EventArgs.Empty);。 注意问号。

  • dynamic_cast相当于C#中的as强制转换,其余可以直接强制转换((int) Something)。

  • gcnew 可以在没有括号的情况下完成。 在 C# 中,您必须使用 new 来获取它们。

  • 注意头文件中的virtual override关键字,您很容易忘记用override关键字标记C#方法。

  • 接口可以有实现! 在这种情况下,您可能需要重新考虑一下架构。 一种选择是将实现拉入抽象类并从中派生

  • 在 C# 中用 Convert 调用替换强制转换时要小心

    • Convert.ToInt32 四舍五入到最接近的 int,但转换总是向下舍入,因此在这种情况下我们不应该使用转换器。
    • 始终先尝试强制转换,如果不起作用,请使用 Convert 类。
  • C++/CLI 中的变量可以在本地范围内重新声明,但在 C# 中会出现命名冲突。 这样的代码如果移植不仔细,很容易导致难以发现的错误。

    • 示例:事件处理程序可以采用参数 e,但也有一个 try-catch,例如 catch (Exception e),这意味着有 2 个 e 变量。
    • 另一个例子:
    //数字是2 
      整数 = 2; 
    
      for (int number = 0; number < 5; number++) 
      { 
         // number 现在是 0,并且增加到 4 
      } 
    
      // 数字又是 2! 
      

    上面的代码在C#中是非法的,因为存在命名冲突。 了解代码在 C++ 中的具体工作原理,并使用完全相同的逻辑移植它,并且显然使用不同的变量名称。

  • 在 C++/CLI 中,可以只编写 throw; 来创建通用 C++ 异常 SEHException。 只需将其替换为适当的异常即可。

  • 移植使用引用 % 符号的代码时要小心,这通常意味着您必须在中使用 refout 关键字C#。

    • 同样,请注意指针*& 引用。 您可能需要编写额外的代码来写回更改,而在 C++ 中您可以只修改指针指向的数据。
  • 可以在 C++/CLI 中调用空对象实例的方法。 是的,认真的。 所以在函数内部你可以这样做 If (this == null) { return; }.

    • 请小心移植此类代码。 您可能必须创建一个扩展方法来包装此类方法,以避免破坏代码。
  • 检查并确保旧项目文件 vcxproj 中的所有内容均已正确移植。 您是否错过了任何嵌入式资源?

  • 移植像 #ifdef 这样的指令时要小心,“if not”(#ifndef) 看起来非常相似,但可能会带来灾难性的后果。

  • C++/CLI 类在添加析构函数时自动实现 IDisposable,因此在 C# 中,您需要实现该接口或重写 Dispose 方法(如果基类中可用)。

其他提示:

  • 如果需要调用 Win32 函数,只需使用 P/Invoke,而不是创建 C++/CLI 包装器
  • 对于复杂的本机 C++ 代码,最好使用托管包装器创建 C++/CLI 项目
  • 再次注意指针。 我忘记在 P/Invoke 代码中执行 Marshal.StructureToPtr ,这在 C++ 版本中是不必要的,因为我们拥有实际的指针而不是其数据的副本。

我肯定错过了一些东西,但希望这些技巧对那些因需要移植的代码量而士气低落的人有一些帮助,尤其是在很短的时间内:)

移植完成后,使用 VS/ReSharper重构和清理代码。 它不仅提高了可读性(这是我编写代码时的首要任务),而且还迫使您与代码进行交互,并可能发现否则会错过的错误。

哦,最后一个仅供参考,可以让您省去麻烦:如果您创建一个公开本机 C++ 指针的 C++/CLI 包装器,并且需要在外部 C++/CLI 程序集中使用该指针,则必须使用 < 来公开本机类型code>#pragma make_public 否则您将收到链接器错误:

// put this at the top of the wrapper class, after includes
#pragma make_public(SomeNamespace::NativeCppClass)

如果您在 C++/CLI 代码中发现错误,请保留它。 您想要移植代码,而不是修复代码,所以请将事情保持在范围内!

对于那些想知道的人来说,移植后我们可能得到了大约 10 次回归。 一半是错误,因为我已经处于自动驾驶模式并且没有注意我在做什么。

移植愉快!

It has to be done manually unfortunately, but if the code is mostly C++/CLI (not native C++) then it can actually be done pretty quickly. I managed to port around 250,000 lines of C++/CLI code into C# in less than a couple of months, and I don't even know C++ very well at all.

If preserving Git history is important, you might want to git mv your cpp file into a cs file, commit, then start porting. The reason for this is that Git will think your file is new if you modify it too much after renaming it.

This was my approach when porting large amounts of code (so that it wouldn't take forever):

  • Create another worktree / clone of the branch and keep it open at all times
    • This is extremely important as you will want to compare your C# to the old C++/CLI code
  • Rename cpp to cs, delete header file, commit
    • I chose to rename the cpp file since its git history is probably more important than the header file
  • Create namespace + class in cs file, add any base classes/interfaces (if abstract sealed, make static in C#)
  • Copy fields first, then constructors, then properties, and finally functions
  • Start replacing with Ctrl+H:
    • ^ to empty
    • :: to .
    • -> to .
    • nullptr to null
    • for each to foreach
    • gcnew to new
    • L" to "
      • Turn on case sensitivity to avoid accidental renames (for example L"cool" should become "cool", not "coo"
    • Prefixes like ClassName:: to empty, so that MyClass::MyMethod becomes MyMethod
    • Go through the red code and port manually code that cannot be just replaced (e.g. some special C++ casts), unless you have some cool regex to do it fast
    • Once code compiles, go through it again, compare to C++/CLI line by line, check for errors, clean it up, move on.
    • If you encounter a dependency that needs to be ported, you could pause, port that, then come back. I did that, but it might not be so easy.

Properties were the most annoying to port, because I had to remove everything before and after the getters and setters. I could have maybe written a regex for it but didn't bother doing so.

Once the porting is done, it's very important that you go through the changes line by line, read the code, and compare with C++/CLI code and fix possible errors.

One problem with this approach is that you can introduce bugs in variable declarations, because in C++/CLI you can declare variables in 2 ways:

  • MyType^ variable; <- null
  • MyType variable; <- calls default constructor

In the latter case, you want to actually do MyType variable = new MyType(); but since you already removed all the ^ you have to just manually check and test which one is correct. You could of course just replace all ^'s manually, but for me it would have taken too long (plus laziness) so I just did it this way.

Other recommendations:

  • Have a dummy C++/CLI project and a tool like LinqPad or another C# project to test differences between C++/CLI and C# if you're unsure of a piece of ported code
  • Install Match Margin to help highlight similar code (helped me when porting WinForms code)
  • ReSharper! It helped with finding bugs and cleaning up the code a LOT. Truly worth the money.

Some gotchas that I encountered while porting:

  • Base classes can be called in C++/CLI like so: BaseClass->DoStuff, but in C# you would have to do base.DoStuff instead.

  • C++/CLI allows such statements: if (foo), but in C# this has to be explicit. In the case of integers, it would be if (foo != 0) or for objects if (foo != null).

  • Events in base classes can be invoked in C++/CLI, but in C# it's not possible. The solution is to create a method, like OnSomeEvent, in the base class, and inside that to invoke the event.

  • C++/CLI automatically generates null checks for event invocations, so in C# make sure to add an explicit null check: MyEvent?.Invoke(this, EventArgs.Empty);. Notice the question mark.

  • dynamic_cast is equivalent to as cast in C#, the rest can be direct casts ((int) something).

  • gcnew can be done without parentheses. In C# you must have them with new.

  • Pay attention to virtual override keywords in the header files, you can easily forget to mark the C# methods with override keyword.

  • Intefaces can have implementations! In this case, you might have to rethink the architecture a bit. One option is to pull the implementation into an abstract class and derive from it

  • Careful when replacing casts with Convert calls in C#

    • Convert.ToInt32 rounds to the narest int, but casting always rounds down, so in this case we should not use the converter.
    • Always try casting first, and if that doesn't work, use the Convert class.
  • Variables in C++/CLI can be re-declared in a local scope, but in C# you get naming conflicts. Code like this easily lead to hard to find bugs if not ported carefully.

    • Example: An event handler can take a parameter e, but also has a try-catch like catch (Exception e) which means there are 2 e variables.
    • Another example:
    // number is 2
    int number = 2;
    
    for (int number = 0; number < 5; number++)
    {
       // number is now 0, and goes up to 4
    }
    
    // number is again 2!
    

    The above code is illegal in C#, because there is a naming conflict. Find out exactly how the code works in C++ and port it with the exact same logic, and obviously use different variable names.

  • In C++/CLI, it's possible to just write throw; which would create a generic C++ exception SEHException. Just replace it with a proper exception.

  • Be careful when porting code that uses the reference % sign, that usually means that you will have to use ref or out keywords in C#.

    • Similarly, pay attention to pointers * and & references. You might have to write additional code to write changes back whereas in C++ you can just modify the data pointed to by the pointer.
  • It's possible to call methods on null object instances in C++/CLI. Yes seriously. So inside the function you could do If (this == null) { return; }.

    • Port this type of code carefully. You might have to create an extension method that wraps over this type of method in order to avoid breaking the code.
  • Check and make sure everything in the old project file vcxproj was ported correctly. Did you miss any embedded resources?

  • Careful when porting directives like #ifdef, the "if not" (#ifndef) looks awfully similar but can have disastrous consequences.

  • C++/CLI classes automatically implement IDisposable when adding a destructor, so in C# you'll need to either implement that interface or override the Dispose method if it's available in the base class.

Other tips:

  • If you need to call Win32 functions, just use P/Invoke instead of creating a C++/CLI wrapper
  • For complex native C++ code, better create a C++/CLI project with managed wrappers
  • Again, pay attention to pointers. I had forgotten to do Marshal.StructureToPtr in my P/Invoke code which wasn't necessary in the C++ version since we had the actual pointer and not a copy of its data.

I have surely missed some things, but hopefully these tips will be of some help to people who are demoralized by the amount of code that needs to be ported, especially in a short period of time :)

After porting is done, use VS/ReSharper to refactor and clean up the code. Not only is it nice for readability, which is my top priority when writing code, but it also forces you to interact with the code and possibly find bugs that you otherwise would have missed.

Oh and one final FYI that could save you headaches: If you create a C++/CLI wrapper that exposes the native C++ pointer, and need to use that pointer in an external C++/CLI assembly, you MUST make the native type public by using #pragma make_public or else you'll get linker errors:

// put this at the top of the wrapper class, after includes
#pragma make_public(SomeNamespace::NativeCppClass)

If you find a bug in the C++/CLI code, keep it. You want to port the code, not fix the code, so keep things in scope!

For those wondering, we got maybe around 10 regressions after the port. Half were mistakes because I was already on autopilot mode and didn't pay attention to what I was doing.

Happy porting!

怀里藏娇 2024-07-11 03:01:42

如果 C++ 代码是纯托管的,则只能将托管 C++ 代码(和 C++/CLI 代码)转换为 C#。 如果不是,即源代码中包含本机代码,.NET Reflector 等工具将无法为您翻译代码。

如果您确实混合了本机 C++ 代码,那么我建议尝试将本机代码移至单独的 DLL 中,通过易于识别的存根函数替换对 DLL 函数的调用,将项目编译为纯 .NET 库,然后使用.NET 反射器反编译为 C# 代码。 然后,您可以通过对本机 DLL 的 p-invoke 调用来替换对存根函数的调用。

祝你好运! 我对你有感觉!

You can only translate Managed C++ code (and C++/CLI code) to C# if the C++ code is pure managed. If it is not -- i.e. if there is native code included in the sources -- tools like .NET Reflector won't be able to translate the code for you.

If you do have native C++ code mixed in, then I'd recommend trying to move the native code into a separate DLL, replace your calls to DLL functions by easily identifiable stub functions, compile your project as a pure .NET library, then use .NET reflector to de-compile into C# code. Then you can replace the calls to the stub functions by p-invoke calls to your native DLL.

Good luck! I feel for you!

我一直都在从未离去 2024-07-11 03:01:42

此类项目通常使用 c++/cli 完成,因为 C# 对于该任务来说并不是一个真正优雅的选择。 例如,如果您必须与某些本机 C++ 库交互,或者在低级 C 中执行非常高性能的操作。因此,只需确保选择 c++/cli 的人在进行切换之前没有充分的理由这样做。

话虽如此,我非常怀疑是否有某种东西可以满足您的要求,原因很简单,并非所有 C++/cli 代码都可以翻译为 C#(反之亦然)。

Such projects are often done in c++/cli because C# isn't really an elegant option for the task. e.g. if you have to interface with some native C++ libraries, or do very high performance stuff in low level C. So just make sure whoever chose c++/cli didn't have a good reason to do it before doing the switch.

Having said that, I'm highly skeptical there's something that does what you ask, for the simple reason that not all C++/cli code is translatable to C# (and probably vice versa too).

浅浅淡淡 2024-07-11 03:01:42

早在 2004 年左右,微软就有了一个可以将托管 C++ 转换为 C++/CLI 的工具……有点像。 我们在几个项目上运行了它,但说实话,清理项目所需的工作量并不少于首先手动进行转换的工作量。 我认为该工具从未公开发布过(也许是因为这个原因)。

我不知道您使用的是哪个版本的 Visual Studio,但我们管理的 C++ 代码无法使用 /clr:oldSyntax 开关与 Visual Studio 2005/2008 进行编译,而且我们仍然有一个遗留的 VS 2003。

我不知道有什么方法可以有效地从 C++ 转换到 C# ...您可以尝试通过反射器来回转换它:)

Back ~2004 Microsoft did have a tool that would convert managed C++ to C++/CLI ... sort of. We ran it on a couple of projects, but to be honest the amount of work left cleaning up the project was no less than the amount of work it would have been to do the conversion by hand in the first place. I don't think the tool ever made it out into a public release though (maybe for this reason).

I don't know which version of Visual Studio you are using, but we have managed C++ code that will not compile with Visual Studio 2005/2008 using the /clr:oldSyntax switch and we still have a relic VS 2003 around for it.

I don't know of any way of going from C++ to C# in a useful way ... you could try round tripping it through reflector :)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文