将 C++/CLI 转换为 C#
我有一个使用 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
.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
我不确定这是否有效,但请尝试使用 .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.
不幸的是,它必须手动完成,但如果代码主要是 C++/CLI(不是本机 C++),那么实际上可以很快完成。 我在不到几个月的时间里成功地将大约 250,000 行 C++/CLI 代码移植到 C# 中,而我什至根本不太了解 C++。
如果保留 Git 历史记录很重要,您可能需要将 cpp 文件
git mv
转换为 cs 文件,提交,然后开始移植。 这样做的原因是,如果你重命名后修改太多,Git 会认为你的文件是新的。这是我移植大量代码时的方法(这样就不会永远花费时间):
Ctrl+H
替换:^
为空::
到.
->
到.
nullptr
到null
对于每个
到foreach
gcnew
到new
L"
至"
L"cool"
应变为"cool"
,而不是"coo"
< /里>ClassName::
等前缀清空,以便MyClass::MyMethod
变为MyMethod
移植,因为我必须删除 getter 和 setter 之前和之后的所有内容。我本来可以为它编写一个正则表达式,但没有这样做。
一旦移植完成,逐行检查更改、阅读代码并进行比较是非常重要的。 C++/CLI 代码并修复可能的错误。
这种方法的一个问题是您可能会在变量声明中引入错误,因为在 C++/CLI 中您可以通过两种方式声明变量:
MyType^ 变量;
<- nullMyType 变量;< /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#方法。接口可以有实现! 在这种情况下,您可能需要重新考虑一下架构。 一种选择是将实现拉入抽象类并从中派生
Convert
调用替换强制转换时要小心Convert.ToInt32
四舍五入到最接近的 int,但转换总是向下舍入,因此在这种情况下我们不应该使用转换器。Convert
类。C++/CLI 中的变量可以在本地范围内重新声明,但在 C# 中会出现命名冲突。 这样的代码如果移植不仔细,很容易导致难以发现的错误。
e
,但也有一个 try-catch,例如catch (Exception e)
,这意味着有 2 个e
变量。上面的代码在C#中是非法的,因为存在命名冲突。 了解代码在 C++ 中的具体工作原理,并使用完全相同的逻辑移植它,并且显然使用不同的变量名称。
在 C++/CLI 中,可以只编写
throw;
来创建通用 C++ 异常SEHException
。 只需将其替换为适当的异常即可。移植使用引用
%
符号的代码时要小心,这通常意味着您必须在中使用ref
或out
关键字C#。*
和&
引用。 您可能需要编写额外的代码来写回更改,而在 C++ 中您可以只修改指针指向的数据。可以在 C++/CLI 中调用空对象实例的方法。 是的,认真的。 所以在函数内部你可以这样做
If (this == null) { return; }
.检查并确保旧项目文件
vcxproj
中的所有内容均已正确移植。 您是否错过了任何嵌入式资源?移植像
#ifdef
这样的指令时要小心,“if not”(#ifndef
) 看起来非常相似,但可能会带来灾难性的后果。C++/CLI 类在添加析构函数时自动实现
IDisposable
,因此在 C# 中,您需要实现该接口或重写 Dispose 方法(如果基类中可用)。其他提示:
Marshal.StructureToPtr
,这在 C++ 版本中是不必要的,因为我们拥有实际的指针而不是其数据的副本。我肯定错过了一些东西,但希望这些技巧对那些因需要移植的代码量而士气低落的人有一些帮助,尤其是在很短的时间内:)
移植完成后,使用 VS/ReSharper重构和清理代码。 它不仅提高了可读性(这是我编写代码时的首要任务),而且还迫使您与代码进行交互,并可能发现否则会错过的错误。
哦,最后一个仅供参考,可以让您省去麻烦:如果您创建一个公开本机 C++ 指针的 C++/CLI 包装器,并且需要在外部 C++/CLI 程序集中使用该指针,则必须使用 < 来公开本机类型code>#pragma make_public 否则您将收到链接器错误:
如果您在 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):
Ctrl+H
:^
to empty::
to.
->
to.
nullptr
tonull
for each
toforeach
gcnew
tonew
L"
to"
L"cool"
should become"cool"
, not"coo"
ClassName::
to empty, so thatMyClass::MyMethod
becomesMyMethod
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;
<- nullMyType variable;
<- calls default constructorIn 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:
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 dobase.DoStuff
instead.C++/CLI allows such statements:
if (foo)
, but in C# this has to be explicit. In the case of integers, it would beif (foo != 0)
or for objectsif (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 toas
cast in C#, the rest can be direct casts ((int) something
).gcnew
can be done without parentheses. In C# you must have them withnew
.Pay attention to
virtual override
keywords in the header files, you can easily forget to mark the C# methods withoverride
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.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.
e
, but also has a try-catch likecatch (Exception e)
which means there are 2e
variables.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++ exceptionSEHException
. 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 useref
orout
keywords in C#.*
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; }
.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:
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: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!
如果 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!
此类项目通常使用 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).
早在 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 :)