您也使用托管C++吗?

您也使用托管C++吗?

  转向.NET后,手头上往往仍有旧的模块要重用。也许这些模块是Delphi写的,也许是C/C++写的,或者是其它编程语言……为了能把它们移植到.NET下,或者是在.NET中调用,To be or not to be, that is a question。
  在这里,我笔记了几个在工作中遇到的几个场景。不过,这里不包括完全使用C#来重写原来用C++编写的程序这种变态的需求。当你被要求做这种事的时候,请三思而后行……这简直是种非人的折磨。

您也使用托管C++吗?  如沐枫林

  场景一:在.NET中调用WindowsAPI或DLL。

  这是比较普遍的需求。一般来说,简单的函数调用,大可直接用C#/VB.NET,经过DllImport属性包装出函数来调用。如:

您也使用托管C++吗?
[DllImport(

KERNEL32.DLL

, EntryPoint
=

MoveFileW

,  SetLastError
=
true
,
您也使用托管C++吗?CharSet

=
CharSet.Unicode, ExactSpelling
=
true
,
您也使用托管C++吗?CallingConvention

=
CallingConvention.StdCall)]
您也使用托管C++吗?

public
 
static
 
extern
 
bool
 MoveFile(String src, String dst);您也使用托管C++吗?

  由于WindowsAPI用到的人实在是多,因此有一个专门的wiki站点,收集这方面的资料:http://www.pinvoke.net/,对于常用的函数甚至有完整的应用例子和帮助。当然,如果你有相应的资料和例子,你也可以贡献你的力量,给其它人帮助。

   场景二:用托管C++包装现有的DLL,供C#调用

  当函数的参数或返回值比较复杂,或函数比较多的时候,这种方法对与人来说,实在是一个折磨。常常这些接口和定义就要用掉几千行的代码,而且还不能保证是正确的。这些错误往往在运行时才能显现出来,甚至有些错误会引起内存泄漏,或其它更为隐蔽的错误。
  在这种情况下,使用C++/Managed代码来包装,就成了最合理的选择。因为托管C++代码可以直接引用原有的头文件,直接调用非托管函数,而不需要声明。这样,既减少了工作量,又避免引入错误。缺点是,这种方法会增加一个DLL。要注意的是托管字符串和非托管字符串是有区别的,并需要转换(特别要注意的Unicode字符串和多字节字符串的转换)。

  仍以MoveFile为例吧,这样比较简单:

您也使用托管C++吗?
#include 
<
windows.h
>

您也使用托管C++吗?#include 

<
vcclr.h
>

您也使用托管C++吗?
您也使用托管C++吗?

using
 
namespace
 System;
您也使用托管C++吗?
您也使用托管C++吗?

namespace
 wrapper
ExpandedBlockStart.gifContractedBlock.gif

您也使用托管C++吗?
{

ExpandedSubBlockStart.gifContractedSubBlock.gif    
public ref class ApiWrapper 您也使用托管C++吗?{

您也使用托管C++吗?    
public:
您也使用托管C++吗?        
bool static MoveFile(String ^ lpExistingFileName, String ^ lpNewFileName )
ExpandedSubBlockStart.gifContractedSubBlock.gif        
您也使用托管C++吗?{

您也使用托管C++吗?            pin_ptr
<const wchar_t> src = PtrToStringChars(lpExistingFileName);
您也使用托管C++吗?            pin_ptr
<const wchar_t> dst = PtrToStringChars(lpNewFileName);
您也使用托管C++吗?            
return ::MoveFile(src, dst);
您也使用托管C++吗?        }

您也使用托管C++吗?    }
;
您也使用托管C++吗?}

  然后在C#中,引用上面代码生成的DLL文件,就可以直接调用了:

您也使用托管C++吗?
wrapper.ApiWrapper.MoveFile(
@”
c:\debug.log


@”
c:\debug.txt

);

  假如原有的代码是基于COM的,那么太好了,VisualStudio等IDE会自动生成一个用于包装的dll,供你调用。当然因特殊需要而手工编码的是另一回事。

  
场景三:现有C++原代码,包装后供C#调用。

  C++的原代码,实际上可以直接编译成托管代码。MFC也好ATL也好……这样看起来在.NET中最强大的编程语言就是C++了:它不仅可以编写托管程序,甚至可以将标准C++的代码也编译成托管程序!其实VC++最强大的地方不止如此,它还在于能够编写混合了托管和非托管的代码的程序!!!这样最大的好处不仅可以将关键代码直接编译成非托管的代码,还可以避免被反编译。

  

  假设现有C++代码如下:

ExpandedBlockStart.gif
ContractedBlock.gif
class
 UnmanagedClass 
您也使用托管C++吗?
{

您也使用托管C++吗?
public:
ExpandedSubBlockStart.gifContractedSubBlock.gif    LPCWSTR GetPropertyA() 
您也使用托管C++吗?return L”Hello!”; }
ExpandedSubBlockStart.gifContractedSubBlock.gif    
void MethodB( LPCWSTR ) 您也使用托管C++吗?{}
您也使用托管C++吗?}

;

我们只要再增加一个包装类到工程文件中:

您也使用托管C++吗?
namespace
 wrapper
ExpandedBlockStart.gifContractedBlock.gif

您也使用托管C++吗?
{

ExpandedSubBlockStart.gifContractedSubBlock.gif    
public ref class ManagedClass 您也使用托管C++吗?{

您也使用托管C++吗?    
public:
您也使用托管C++吗?        
// Allocate the native object on the C++ Heap via a constructor
ExpandedSubBlockStart.gifContractedSubBlock.gif
        ManagedClass() : m_Impl( new UnmanagedClass ) 您也使用托管C++吗?{}
您也使用托管C++吗?
您也使用托管C++吗?        
// Deallocate the native object on a destructor
ExpandedSubBlockStart.gifContractedSubBlock.gif
        ~ManagedClass() 您也使用托管C++吗?{

您也使用托管C++吗?            delete m_Impl;
您也使用托管C++吗?        }

您也使用托管C++吗?
您也使用托管C++吗?    
protected:
您也使用托管C++吗?        
// Deallocate the native object on the finalizer just in case no destructor is called
ExpandedSubBlockStart.gifContractedSubBlock.gif
        !ManagedClass() 您也使用托管C++吗?{

您也使用托管C++吗?            delete m_Impl;
您也使用托管C++吗?        }

您也使用托管C++吗?
您也使用托管C++吗?    
public:
ExpandedSubBlockStart.gifContractedSubBlock.gif        property String 
^  get_PropertyA 您也使用托管C++吗?{

ExpandedSubBlockStart.gifContractedSubBlock.gif            String 
^ get() 您也使用托管C++吗?{

您也使用托管C++吗?                
return gcnew String( m_Impl->GetPropertyA());
您也使用托管C++吗?            }

您也使用托管C++吗?        }

您也使用托管C++吗?
ExpandedSubBlockStart.gifContractedSubBlock.gif        
void MethodB( String ^ theString ) 您也使用托管C++吗?{

您也使用托管C++吗?            pin_ptr
<const WCHAR> str = PtrToStringChars(theString);
您也使用托管C++吗?            m_Impl
->MethodB(str);
您也使用托管C++吗?        }

您也使用托管C++吗?
您也使用托管C++吗?    
private:
您也使用托管C++吗?        UnmanagedClass 
* m_Impl;
您也使用托管C++吗?    }
;
您也使用托管C++吗?}

  然后,改变编译选项为“使用公共语言扩展 /clr”就可以了。这样,我们把代码编译成DLL文件就可以供.NET其它语言调用了。

  最后,C#中可以象如下的代码一样调用C++类了:

您也使用托管C++吗?
ManagedClass mc 
=
 
new
 ManagedClass();
您也使用托管C++吗?mc.MethoB(


Hello

);
您也使用托管C++吗?

string
 s 
=
 mc.get_PropertyA;

  
场景四:如何在托管C++代码中混合托管和非托管代码

  很简单,只要从#pragma unmanaged编译指示开始的程序,一率编译成非托管代码;要想恢复成托管代码,只要使用#pragma managed就可以了。如:

您也使用托管C++吗?
#pragma unmanaged
您也使用托管C++吗?
您也使用托管C++吗?#include 

<
iostream
>

您也使用托管C++吗?

using
 
namespace
 std;
您也使用托管C++吗?
您也使用托管C++吗?template

<
typename T
>

ExpandedBlockStart.gifContractedBlock.gif

void
 f(T t)
您也使用托管C++吗?
{

您也使用托管C++吗?    cout 
<< t << endl;
您也使用托管C++吗?}


您也使用托管C++吗?
您也使用托管C++吗?#pragma managed
您也使用托管C++吗?
您也使用托管C++吗?

using
 
namespace
 System;
您也使用托管C++吗?
ExpandedBlockStart.gifContractedBlock.gif

void
 m(String 
^
 s)
您也使用托管C++吗?
{

您也使用托管C++吗?    Console::WriteLine(s);
您也使用托管C++吗?}


您也使用托管C++吗?
ExpandedBlockStart.gifContractedBlock.gif

void
 main()
您也使用托管C++吗?
{

您也使用托管C++吗?    f(
Hello);
您也使用托管C++吗?    m(
World);
您也使用托管C++吗?}

  生成exe文件后,用反编译程序查看 f 函数:

您也使用托管C++吗?
[PreserveSig, MethodImpl(MethodImplOptions.Unmanaged, MethodCodeType
=
MethodCodeType.Native), SuppressUnmanagedCodeSecurity]
您也使用托管C++吗?

public
 
static
 
unsafe
 
void
 modopt(CallConvCdecl) f
<
char
 
const
 
*>
(
sbyte
 modopt(IsSignUnspecifiedByte) modopt(IsConst)
*
);

  看不到源码,而方法属性标记为Unmanaged。

  如果没有加上#pragma unmanaged,反编译得到的 f 函数为:

您也使用托管C++吗?
internal
 
static
 
unsafe
 
void
 modopt(CallConvCdecl) f
<
char
 
const
 
*>
(
sbyte
 modopt(IsSignUnspecifiedByte) modopt(IsConst)
*
 t)
ExpandedBlockStart.gifContractedBlock.gif

您也使用托管C++吗?
{

您也使用托管C++吗?      std.basic_ostream
<char,std::char_traits<char> >.<<(std.operator<<<struct std::char_traits<char> >(*((basic_ostream<char,std::char_traits<char> >* modopt(IsImplicitlyDereferenced)*&__imp_std.cout), t), (basic_ostream<char,std::char_traits<char> >* modopt(IsImplicitlyDereferenced) modopt(CallConvCdecl) *(basic_ostream<char,std::char_traits<char> >* modopt(IsImplicitlyDereferenced))) __unep@?endl@std@@$$FYAAAV?$basic_ostream@DU?$char_traits@D@std@@@1@AAV21@@Z);
您也使用托管C++吗?}

  其中的函数内容一目了然。如果你的函数没有调用operator等不好理解的类库,那么反编译出来的代码简直和源码没差别。 

  
开心一刻:我只会C++不懂.NET不懂C#,怎么编写.NET程序?

  很简单,你照样用你的C++写你的程序,然后测试没有错误后,将编译选项改为/clr,好了,Rebuild,你的程序现在是.NET了。

  
恶搞:“我想问一下,在能将现有的C++代码直接进行封装,被C#进行调用,而不是去调用DLL,也就是不生成DLL,就在C#下能直接调用VC的工程源文件不?”

  我想,提问的人是不是指,现有c++源码,但不想费劲去转换成C#源码,但又想能与C#一起编译。

  于是我就给了一个极其变态的方法,不过,个人是不建议使用这种变态的方法啊。方法如下:

  1 先将C++源码,改用CLR编译选项,编译成.NET的Assembly(DLL文件)。

  2 然后用reflector等反编译软件,反编译成C#代码,并导出(reflector有专门的导出插件)。

  3 将导出的C#代码,添加上新写的C#代码一起编译。

  

  这种方法生成的代码很是恐怖,强烈建议不要把C++源码就这么丢了,否则后果自负。

  
场景五:不想要DLL,能不能直接把C++源代码与C#源代码一起编译成一个单独的Assembly呢?

  当然是可以的。具体参见:让C++代码与C#代码一起生成一个单一的Assembly

——-
部份例子来自MSDN.

分类:
C++,
.NET
0
0
(请您对文章做出评价)

« 上一篇:
创建一个不显示窗口的程序(2006-03-30修订)


» 下一篇:
汇编语言的Hello World

posted @ 2006-03-31 16:44
沐枫 阅读(9137)
评论(38)
编辑
收藏

您也使用托管C++吗?

1562145
 
回复 
引用 
查看   

#1楼
[
楼主]2006-03-31 20:25 |
沐枫 
     

终于发现博客园的一处好风景:
http://xinyi.cnblogs.com/
心仪的澳洲蜜月之旅


 
回复 
引用   

#2楼
222.212.126.* 2006-04-01 08:33 |
ff[未注册用户]

为什么要把C++转到C#,你说对了,这是变态的需求

 
回复 
引用   

#3楼
219.239.201.* 2006-04-03 10:14 |
111111[未注册用户]

二和三有分别么? 都是包装dll 然后调用

 
回复 
引用 
查看   

#4楼
[
楼主]2006-04-03 12:26 |
沐枫 
     

2和3有区别。
2是将原有的dll,用c++另外包一个dll,然后给.net其它语言去用。(这样就有了两个dll)
3是将原有的原代码,再添上c++/cli的包装代码,一起编译一个dll,然后给.net其它语言去用。(这样就只有一个dll)


 
回复 
引用   

#5楼
219.239.201.* 2006-04-04 16:33 |
111111[未注册用户]

在第二种情况里, 其实是调用包装后的dll,然后包装的dll再调用原有的dll,对吧

 
回复 
引用 
查看   

#6楼
[
楼主]2006-04-04 17:06 |
沐枫 
     

是的。因为这个dll可能没有源码,也可能是其它非.net支持的语言编写的。

 
回复 
引用   

#7楼
61.187.54.* 2006-04-12 11:12 |
Frain[未注册用户]

我想问一下:
在能将现有的C++代码直接进行封装,被C#进行调用,而不是去调用DLL,也就是不生成DLL,就在C#下能直接调用VC的工程源文件不?


 
回复 
引用 
查看   

#8楼
[
楼主]2006-04-12 11:25 |
沐枫 
     

@Frain
没看明白。
猜测一下,是不是指,现有c++源码,想转换成C#源码,一起编译是吗?
那就是用C#重写C++源码,这是很头痛的一件事。

不过,还有一个偷懒的方法。但是个人更不建议使用这种方法(极其变态)
1 先将C++源码,改用CLR编译选项,编译成.NET的Assembly(DLL文件)。
2 然后用reflector等反编译软件,反编译成C#代码,并导出。
3 将导出的C#代码,添加上新写的C#代码一起编译。
4 搞定。
这种方法生成的代码很是恐怖,强烈建议不要把C++源码就这么丢了,否则。。。

 
回复 
引用   

#9楼
61.187.54.* 2006-04-12 11:56 |
Frain[未注册用户]

不是这个意思,是这样的,我现在有VC的源码,是VC6写的,但现在工程项目改用C#来开发了,但用C#来重写原来的代码工作量很大,时间不构,因而我们考虑用C#来封装原来VC6代码的接口,让用户感知我们是用C#开发的,同时又不希望用户知道,我们就想只生成一个DLL,这个DLL是用C#代码写的接口,而具体实现全是以前的VC6代码。我们不想有两个DLL。
其实另外一个问题就是一个工程中能同时编译C#和C++不?
谢谢如沐枫林


 
回复 
引用   

#10楼
61.187.54.* 2006-04-12 12:37 |
Frain[未注册用户]

谢谢你提的解决办法,我的QQ是:7805242,很想跟你就这个问题深入交谈。

 
回复 
引用 
查看   

#11楼
[
楼主]2006-04-12 13:37 |
沐枫 
     

曾经有不少人提出一个方法,具体贴子不知道在哪。
那个方法是:
将非托管的dll文件当作资源的一部份,夹带到项目中,共同编译。
然后在运行时释放出此dll,并调用之。

释放时,最简单的方法就是释放成一个dll文件,可以藏在执行目录或临目录甚至系统目录下。并可选择在程序结束时删除(有点小难度)。
另一个方法,是释放到内存中并装载,然后进行调用(有较大的难度)。

我没有这么做过,需要你自已查找资料和试验。如果试验有成果希望共享一下。

 
回复 
引用   

#12楼
61.187.54.* 2006-04-12 14:43 |
Frain[未注册用户]

真是谢谢你,
这样的,一般VC6的代码还是比较容易的转换到VC.net上,如果都是.net平台了,这样对C#来调用VC.net的源代码这个问题上就容易些吗?还有就是,我们最终是想让客户只看到一个以C#封装接口的DLL,可以将原来的VC6代码进行升级成VC.net。

你看这样的难度会小点不?

 
回复 
引用 
查看   

#13楼
[
楼主]2006-04-12 16:17 |
沐枫 
     

@Frain
理论上来说,一个工程中可以同时编译C#和C++甚至其它的语言,然后共同链接成一个Assembly。
但是用VisualStudio的集成环境很不容易。
建议直接使用命令行编译。


 
回复 
引用 
查看   

#14楼
[
楼主]2006-04-12 17:55 |
沐枫 
     

@Frain

希望这个随笔可以给你帮助:

 
回复 
引用   

#15楼
60.191.25.* 2006-07-15 19:39 |
liupf[未注册用户]

我有一个C#写的dlll文件,想在想在MFC ++工程中进行调用,要怎么做?

 
回复 
引用 
查看   

#16楼
[
楼主]2006-07-20 09:08 |
沐枫 
     

@liupf
我稍微查了一下,在Visual Studio2005中,比较简单,有两种方法可用:
1 通过增加/clr编译选项,VC++可以生成混合托管和非托管的程序.在程序中可以直接调用C#写的dll文件.注意使用#pragma managed和#pragma unmanaged.
2 将C#写的dll注册为COM组件,则可以在VC++等其它本地代码中调用该COM组件.

当然,还有一个方法,就是直接调用mscoree的API,自已初始化和加载托管代码,但是很麻烦,VC++2005都做的不错了,一般情况下不需要这么做.

 
回复 
引用   

#17楼
218.79.109.* 2006-08-31 12:13 |
Oscar.Ken[未注册用户]

可以使用SWIG。像OGRE.net就是用它进行转换的。

 
回复 
引用   

#18楼
84.103.24.* 2006-10-04 17:28 |
lize[未注册用户]

怎么在c#调用 VC的窗口程序, 想把窗口程序编绎成dll, 然后在c#中调用
不知道lz有这个经验吗?


 
回复 
引用 
查看   

#19楼
[
楼主]2006-10-08 11:37 |
沐枫 
     

@lize
这应该不是问题啊。
主要是看你的用途。一般来说,你在dll中提供几个操作窗口的函数,然后供c#调用就可以了。


 
回复 
引用   

#20楼
217.194.34.* 2006-11-09 15:50 |
test[匿名][未注册用户]

请问,能在C++中使用托管的类库而编译成非托管的exe吗?

 
回复 
引用 
查看   

#21楼
[
楼主]2006-11-11 19:56 |
沐枫 
     

这是不可以的。
因为,既使exe程序是非托管的,但你所使用的托管类库仍然要启动 .NET 执行机制。


 
回复 
引用   

#22楼
220.248.120.* 2007-01-02 13:56 |
Lake[匿名][未注册用户]

我需要在C++.Net中把一个.Net窗口设为一个Win32(或者MFC)窗口的子窗口,可行么?怎么做?

 
回复 
引用   

#23楼
220.249.120.* 2007-01-11 11:52 |
sander[未注册用户]

看了你的托管C++,但编译总是有问题,能给我一个完整的例子吗

 
回复 
引用   

#24楼
58.246.11.* 2007-01-12 13:49 |
aaa[匿名][未注册用户]

我已经生成了C#的DLL,然后注册为COM组件时报错,你能不能写下注册的 命令行呢?然后我想在BCB中调用生成的 COM组件 ,又要怎么做呢?


 
回复 
引用 
查看   

#25楼
[
楼主]2007-01-29 09:51 |
沐枫 
     

@aaa[匿名]
你用的是regasm吗?

@sander
抱歉,工作调动,很久没来。
不知道你要的是哪个例子?

@Lake[匿名]
理论上可行的。具体看一下Win32API中的窗口机制。

 
回复 
引用   

#26楼
202.95.81.* 2007-05-17 09:01 |
Fact[未注册用户]

请问第三种方法对现有的C++代码会有什么影响吗?

如果我要做一个Dll既要被Native的程序调用,也要被.Net的程序调用, 那用第三种方法加上了Wrapper, 会不会导致对.Net的依赖,或者Native的程序就无法调用了?

 
回复 
引用 
查看   

#27楼
[
楼主]2007-05-22 15:34 |
沐枫 
     

@Fact
你把Wrapper做成独立的一个dll就可以了。
这样,.net用的时候,加上这个dll,native用的时候,不需要这个dll.


 
回复 
引用   

#28楼
222.66.162.* 2007-05-23 22:16 |
秒大刀[未注册用户]

我遇到过将一个无法维护的工程完全用C#重写的需求

 
回复 
引用   

#29楼
202.95.81.* 2007-05-24 12:57 |
Fact[未注册用户]

我又来了, 我已经成功用第三种方法做了封装,但是用C#程序调wrap之后的dll时出现了下面的exception:

The specified module could not be found. (Exception from HRESULT: 0x8007007E)

我在网上搜了一下, 有说是C++编译器和C#编译器对manifest的处理不同的,有的说把unmanaged的dll也要拷过去才行…
究竟哪种才是可行的呢? 如果还需要unmanaged的dll的话, 岂不是还是要依赖2个dll….

 
回复 
引用 
查看   

#30楼
[
楼主]2007-05-24 16:52 |
沐枫 
     

第三种方法,是在原有源代码中增加新的wrapper代码,并编译成managed的dll供C#调用。这时候是不需要unmanaged的dll。需要dll的是第二种方法。

之所以出现这个问题,可能性比较多。最有可能的情况是,你的wrapper dll使用了第三方库,或是别的什么dll,在你运行的环境中找不到,或没有设置好路径。解决方法就是使用 depends.exe来查看wrapper dll文件,看看他依赖于哪些dll,这些dll你的运行环境如果没有,就需要复制一份过来。
(Depends.exe 在 \Program Files\Microsoft Visual Studio 8\Common7\Tools\Bin 目录下。)

 
回复 
引用   

#31楼
202.95.81.* 2007-05-25 07:06 |
Fact[未注册用户]

我用depends看了, 它依赖于unmanaged的dll,是不是我编译的时候什么没有配置好?

 
回复 
引用   

#32楼
202.95.81.* 2007-05-25 13:27 |
Fact[未注册用户]

另外, 我在.Net的程序里调用managed wrapper, 不能debug到unmanaged dll中去, 把pdb放过去也不行… :(

 
回复 
引用 
查看   

#33楼
[
楼主]2007-06-13 11:15 |
沐枫 
     

@Fact
你想要debug到unmanaged dll中,就要把unmanaged dll作为调试项目。

wrapper依赖于unmanaged dll,那肯定是你编译没配置好。首先可以确定的是,你必须只有一个vcproj,把wrapper代码和原来的代码放到同一个vcproj中,编译出一个dll。你还要排除掉unmanaged的lib(因为不需要,需要了就必然要unmanged的dll)。

 
回复 
引用   

#34楼
60.167.135.* 2007-07-18 17:29 |
ww[未注册用户]

请教一下:如果想用c#调用c++的Mfcdialog应该用那种方法比较好??

 
回复 
引用 
查看   

#35楼
[
楼主]2007-07-19 19:29 |
沐枫 
     

哪个能满足你的要求,哪个就是好的。

 
回复 
引用   

#36楼
124.237.98.* 2007-12-17 16:21 |
我的浏览[未注册用户]

想问下为什么要将c++项目转成c#????

 
回复 
引用   

#37楼
222.212.144.*
2009-06-19 00:51 |
ZKO[未注册用户]

不是为什么维护以前的C++代码,而是C#有些实现不了的功能用C++写,但C#又想实现,却来个变态的互操作

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/110665.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号