VC++ 调用 C#生成DLL的两种方法
如果想在VC++的非托管代码中调用已经用VC#生成的托管的DLL,从两个方向进行调整可以产生两种办法:(visual studio 2008)(下面方法都是对于同一平台下,即C#跟VC++都是在windows平台下)
一、对VC++的环境中进行修改使其支持托管代码:
vc++调用端增加公共语言运行时【clr】的支持以执行C#的程序:解决方案-》Properties(右键)-》Configuration Properties(展开左树)->General(打开子节点)->Common Language Runtime support(选中选项)->【Common Language Runtime support(/clr)】(选中)
OK,现在就可以引入托管的动态连接库来用了,不过在调用时还是得注意语法(new->gcnew,....),例如下:
1.新建一个C#的动态连接库(在 模板 ,下单击类库):
1)、使用强命名工具(Strong Name Utility)产生密钥对:
sn -k MyKeyFile.snk
2)、在AssemblyInfo.cs文件中用AssemblyKeyFile属性指向它:
即在项目的文件AssemblyInfo.cs中将[assembly: ComVisible(false)]用以下内容替换:
[assembly: ComVisible(true)]
[assembly: AssemblyDelaySign(false)]
[assembly: AssemblyKeyFile("MyKeyFile.snk")] //指向刚生成的文件(可用无汉字的绝对路径)
3)、重新编译,产生的程序集就是经过签名后的程序集了
3.把生成的库文件加入全局程序集缓存(Global Assembly Cache, .NET [Compact]Framework支持一个工具,通常位于:C/Wndows/Assembly下)以便可以从任何 COM 客户端激活它,可以使用工具GACUtil.exe,指定/i命令开关将一个程序集安装到GAC中,同样可以使用/u命令开关将一个程序集从GAC中卸载。注意:安装的程序集必须是强命名程序集:
GACUTIL /i SmartDeviceDLL.dll //可用SmartDeviceDLL.dll的绝对路径,Wince平台直接在命令窗口中用CGACUTIL(Compact)
4.用下面的命令为COM注册刚才的程序集,生成COM库文件(程序集注册工具读取程序集中的元数据,并将所需的项添加到注册表中,注册表允许 COM 客户程序以透明方式创建 .NET Framework 类。类一经注册,任何 COM 客户程序都可以使用它,就好像该类是一个 COM 类。类仅在安装程序集时注册一次。程序集中的类实例直到被实际注册时,才能从 COM 中创建)
//下面命令注册 SmartDeviceDLL.dll 中包含的所有公共类,并生成和注册类型库 SmartDeviceDLL.tlb,该类型库包含 SmartDeviceDLL.dll 中定义的所有公共类型的定义
REGASM SmartDeviceDLL.dll /tlb:SmartDeviceDLL.tlb
//或者可以选中:解决方案->Properties(右键)->Build->【Register for COM interop】(Wince平台的DLL此选项不可选)
5.创建非托管的VC++调用程序(此处用Win32 Console Project为例):
您也使用托管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属性包装出函数来调用。如:
[DllImport("KERNEL32.DLL", EntryPoint="MoveFileW", SetLastError=true,
CharSet=CharSet.Unicode, ExactSpelling=true,
CallingConvention=CallingConvention.StdCall)]
public static extern bool MoveFile(String src, String dst);
由于WindowsAPI用到的人实在是多,因此有一个专门的wiki站点,收集这方面的资料:http://www.pinvoke.net/,对于常用的函数甚至有完整的应用例子和帮助。当然,如果你有相应的资料和例子,你也可以贡献你的力量,给其它人帮助。
场景二:用托管C++包装现有的DLL,供C#调用
当函数的参数或返回值比较复杂,或函数比较多的时候,这种方法对与人来说,实在是一个折磨。常常这些接口和定义就要用掉几千行的代码,而且还不能保证是正确的。这些错误往往在运行时才能显现出来,甚至有些错误会引起内存泄漏,或其它更为隐蔽的错误。
在这种情况下,使用C++/Managed代码来包装,就成了最合理的选择。因为托管C++代码可以直接引用原有的头文件,直接调用非托管函数,而不需要声明。这样,既减少了工作量,又避免引入错误。缺点是,这种方法会增加一个DLL。要注意的是托管字符串和非托管字符串是有区别的,并需要转换(特别要注意的Unicode字符串和多字节字符串的转换)。
仍以MoveFile为例吧,这样比较简单:
wrapper.ApiWrapper.MoveFile(@"c:\debug.log", @"c:\debug.txt");
假如原有的代码是基于COM的,那么太好了,VisualStudio等IDE会自动生成一个用于包装的dll,供你调用。当然因特殊需要而手工编码的是另一回事。
场景三:现有C++原代码,包装后供C#调用。
C++的原代码,实际上可以直接编译成托管代码。MFC也好ATL也好……这样看起来在.NET中最强大的编程语言就是C++了:它不仅可以编写托管程序,甚至可以将标准C++的代码也编译成托管程序!其实VC++最强大的地方不止如此,它还在于能够编写混合了托管和非托管的代码的程序!!!这样最大的好处不仅可以将关键代码直接编译成非托管的代码,还可以避免被反编译。
假设现有C++代码如下:
最后,C#中可以象如下的代码一样调用C++类了:
ManagedClass mc = new ManagedClass();
mc.MethoB("Hello");
string s = mc.get_PropertyA;
场景四:如何在托管C++代码中混合托管和非托管代码
很简单,只要从#pragma unmanaged编译指示开始的程序,一率编译成非托管代码;要想恢复成托管代码,只要使用#pragma managed就可以了。如:
生成exe文件后,用反编译程序查看 f 函数:
[PreserveSig, MethodImpl(MethodImplOptions.Unmanaged, MethodCodeType=MethodCodeType.Native), SuppressUnmanagedCodeSecurity]
public static unsafe void modopt(CallConvCdecl) f<char const *>(sbyte modopt(IsSignUnspecifiedByte) modopt(IsConst)*);
看不到源码,而方法属性标记为Unmanaged。
如果没有加上#pragma unmanaged,反编译得到的 f 函数为:
internal static unsafe void modopt(CallConvCdecl) f<char const *>(sbyte modopt(IsSignUnspecifiedByte) modopt(IsConst)* t)
{
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);
}
其中的函数内容一目了然。如果你的函数没有调用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++源码就这么丢了,否则后果自负。
现在,如何将这些源码编译成一个exe文件呢?
用集成环境是很难的,最方便的,还是创建一个makefile或MSBuild来完成。
由于这个例子很简单,我用命令行来创建这个exe文件:
cl c.cpp wrapper.cpp /c /clr
生成 c.obj 和 wrapper.obj
csc /addmodule:wrapper.obj /target:module /out:cs.netmodule cs.cs
生成 cs.netmodule
link c.obj wrapper.obj cs.netmodule /LTCG /Subsystem:Console /entry:test.Program.Main /out:o.exe
生成 o.exe
运行 o.exe 显示结果:121
------
C++与C#混合生成.NET程序
参考资料:
1. .netmodule Files as Linker Input http://msdn2.microsoft.com/en-us/library/k669k83h(VS.80).aspx
2. 选择 .netmodule 输入文件的格式 http://msdn2.microsoft.com/zh-CN/library/0zyh2sf2(VS.80).aspx
后记:(2006-04-19)
今天特意上网查了一下,发现已经有这个介绍了。大家参考一下。
原文地址:http://blog.joycode.com/junfeng/archive/2005/05/25/51838.aspx?Pending=true
-------
转载地址:http://blog.sina.com.cn/s/blog_6d3edc7e010136j3.html
http://www.cnblogs.com/ly4cn/archive/2006/04/12/373494.html
如果想在VC++的非托管代码中调用已经用VC#生成的托管的DLL,从两个方向进行调整可以产生两种办法:(visual studio 2008)(下面方法都是对于同一平台下,即C#跟VC++都是在windows平台下)
一、对VC++的环境中进行修改使其支持托管代码:
vc++调用端增加公共语言运行时【clr】的支持以执行C#的程序:解决方案-》Properties(右键)-》Configuration Properties(展开左树)->General(打开子节点)->Common Language Runtime support(选中选项)->【Common Language Runtime support(/clr)】(选中)
OK,现在就可以引入托管的动态连接库来用了,不过在调用时还是得注意语法(new->gcnew,....),例如下:
#include "stdafx.h"
#using "SmartDeviceDLL.dll"
using namespace SmartDeviceDLL;
int _tmain(int argc, _TCHAR* argv[])
{
printf("1111111111111/n");
SmartDeviceDLL::ICalculator ^pICalc=gcnew SmartDeviceDLL::Class1();
long lResult =0;
lResult=pICalc->Add(5,10);
wprintf(L"the result is %d/n",lResult);
printf("222222222222222222/n");
char c;
scanf("%c",&c);
return 0;
}
二、C#生成DLL端编译成COM接口,供VC++以托管格式调用(命令的运行都是在visual studio command prompt (命令窗口)中)
1.新建一个C#的动态连接库(在 模板 ,下单击类库):
using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
namespace SmartDeviceDLL
{
public interface ICalculator
{
int Add(int Number1, int Number2);
}
public class Class1: ICalculator
{
public int Add(int Number1, int Number2)
{
return Number1 * Number2;
}
public static int TestMethod(String s)
{
Console.WriteLine("Managed assembly: [0]", s);
return s.Length;
}
}
}
2.为程序集创建一个强命名的类库,并在AssemblyInfo.cs文件中用AssemblyKeyFile属性指向它:
1)、使用强命名工具(Strong Name Utility)产生密钥对:
sn -k MyKeyFile.snk
2)、在AssemblyInfo.cs文件中用AssemblyKeyFile属性指向它:
即在项目的文件AssemblyInfo.cs中将[assembly: ComVisible(false)]用以下内容替换:
[assembly: ComVisible(true)]
[assembly: AssemblyDelaySign(false)]
[assembly: AssemblyKeyFile("MyKeyFile.snk")] //指向刚生成的文件(可用无汉字的绝对路径)
3)、重新编译,产生的程序集就是经过签名后的程序集了
3.把生成的库文件加入全局程序集缓存(Global Assembly Cache, .NET [Compact]Framework支持一个工具,通常位于:C/Wndows/Assembly下)以便可以从任何 COM 客户端激活它,可以使用工具GACUtil.exe,指定/i命令开关将一个程序集安装到GAC中,同样可以使用/u命令开关将一个程序集从GAC中卸载。注意:安装的程序集必须是强命名程序集:
GACUTIL /i SmartDeviceDLL.dll //可用SmartDeviceDLL.dll的绝对路径,Wince平台直接在命令窗口中用CGACUTIL(Compact)
4.用下面的命令为COM注册刚才的程序集,生成COM库文件(程序集注册工具读取程序集中的元数据,并将所需的项添加到注册表中,注册表允许 COM 客户程序以透明方式创建 .NET Framework 类。类一经注册,任何 COM 客户程序都可以使用它,就好像该类是一个 COM 类。类仅在安装程序集时注册一次。程序集中的类实例直到被实际注册时,才能从 COM 中创建)
//下面命令注册 SmartDeviceDLL.dll 中包含的所有公共类,并生成和注册类型库 SmartDeviceDLL.tlb,该类型库包含 SmartDeviceDLL.dll 中定义的所有公共类型的定义
REGASM SmartDeviceDLL.dll /tlb:SmartDeviceDLL.tlb
//或者可以选中:解决方案->Properties(右键)->Build->【Register for COM interop】(Wince平台的DLL此选项不可选)
5.创建非托管的VC++调用程序(此处用Win32 Console Project为例):
#include "stdafx.h"
#import "SmartDeviceDLL.tlb" named_guids raw_interfaces_only
using namespace SmartDeviceDLL;
int _tmain(int argc, _TCHAR* argv[])
{
printf("1111111111111/n");
//初始化COM以及产生智能指针
HRESULT hr=CoInitializeEx(NULL,COINIT_MULTITHREADED);
if(hr!=S_OK)
printf("hr failed/n");
else
printf("hr ok/n");
printf("222222222222222222/n");
SmartDeviceDLL::ICalculatorPtr pICalc;
printf("2.1/n");
HRESULT hRes=pICalc.CreateInstance(__uuidof(Class1),NULL,CLSCTX_ALL);
//HRESULT hRes=pICalc.CreateInstance(SmartDeviceDLL::CLSID_Class1);
printf("2.2/n");
if(hRes==S_OK)
{
printf("hRes ok/n");
long lResult =0;
pICalc->Add(5,10, &lResult);
wprintf(L"the result is %d/n",lResult);
}
else
printf("hRes failure/n");
printf("333333333333/n");
CoUninitialize();
printf("4444444444444444444/n");
char c;
scanf("%c",&c);
return 0;
}
==============================================================================================
您也使用托管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属性包装出函数来调用。如:
[DllImport("KERNEL32.DLL", EntryPoint="MoveFileW", SetLastError=true,
CharSet=CharSet.Unicode, ExactSpelling=true,
CallingConvention=CallingConvention.StdCall)]
public static extern bool MoveFile(String src, String dst);
由于WindowsAPI用到的人实在是多,因此有一个专门的wiki站点,收集这方面的资料:http://www.pinvoke.net/,对于常用的函数甚至有完整的应用例子和帮助。当然,如果你有相应的资料和例子,你也可以贡献你的力量,给其它人帮助。
场景二:用托管C++包装现有的DLL,供C#调用
当函数的参数或返回值比较复杂,或函数比较多的时候,这种方法对与人来说,实在是一个折磨。常常这些接口和定义就要用掉几千行的代码,而且还不能保证是正确的。这些错误往往在运行时才能显现出来,甚至有些错误会引起内存泄漏,或其它更为隐蔽的错误。
在这种情况下,使用C++/Managed代码来包装,就成了最合理的选择。因为托管C++代码可以直接引用原有的头文件,直接调用非托管函数,而不需要声明。这样,既减少了工作量,又避免引入错误。缺点是,这种方法会增加一个DLL。要注意的是托管字符串和非托管字符串是有区别的,并需要转换(特别要注意的Unicode字符串和多字节字符串的转换)。
仍以MoveFile为例吧,这样比较简单:
#include <windows.h>
#include <vcclr.h>
using namespace System;
namespace wrapper
{
public ref class ApiWrapper
{
public:
bool static MoveFile(String ^ lpExistingFileName, String ^ lpNewFileName )
{
pin_ptr<const wchar_t> src = PtrToStringChars(lpExistingFileName);
pin_ptr<const wchar_t> dst = PtrToStringChars(lpNewFileName);
return ::MoveFile(src, dst);
}
};
}
然后在C#中,引用上面代码生成的DLL文件,就可以直接调用了:
wrapper.ApiWrapper.MoveFile(@"c:\debug.log", @"c:\debug.txt");
假如原有的代码是基于COM的,那么太好了,VisualStudio等IDE会自动生成一个用于包装的dll,供你调用。当然因特殊需要而手工编码的是另一回事。
场景三:现有C++原代码,包装后供C#调用。
C++的原代码,实际上可以直接编译成托管代码。MFC也好ATL也好……这样看起来在.NET中最强大的编程语言就是C++了:它不仅可以编写托管程序,甚至可以将标准C++的代码也编译成托管程序!其实VC++最强大的地方不止如此,它还在于能够编写混合了托管和非托管的代码的程序!!!这样最大的好处不仅可以将关键代码直接编译成非托管的代码,还可以避免被反编译。
假设现有C++代码如下:
class UnmanagedClass
{
public:
LPCWSTR GetPropertyA() { return L"Hello!"; }
void MethodB( LPCWSTR ) {}
};
我们只要再增加一个包装类到工程文件中:
namespace wrapper
{
public ref class ManagedClass
{
public:
// Allocate the native object on the C++ Heap via a constructor
ManagedClass() : m_Impl( new UnmanagedClass ) {}
// Deallocate the native object on a destructor
~ManagedClass()
{
delete m_Impl;
}
protected:
// Deallocate the native object on the finalizer just in case no destructor is called
!ManagedClass()
{
delete m_Impl;
}
public:
property String ^ get_PropertyA
{
String ^ get()
{
return gcnew String( m_Impl->GetPropertyA());
}
}
void MethodB( String ^ theString )
{
pin_ptr<const WCHAR> str = PtrToStringChars(theString);
m_Impl->MethodB(str);
}
private:
UnmanagedClass * m_Impl;
};
}
然后,改变编译选项为“使用公共语言扩展 /clr”就可以了。这样,我们把代码编译成DLL文件就可以供.NET其它语言调用了。
最后,C#中可以象如下的代码一样调用C++类了:
ManagedClass mc = new ManagedClass();
mc.MethoB("Hello");
string s = mc.get_PropertyA;
场景四:如何在托管C++代码中混合托管和非托管代码
很简单,只要从#pragma unmanaged编译指示开始的程序,一率编译成非托管代码;要想恢复成托管代码,只要使用#pragma managed就可以了。如:
#pragma unmanaged
#include <iostream>
using namespace std;
template<typename T>
void f(T t)
{
cout << t << endl;
}
#pragma managed
using namespace System;
void m(String ^ s){
Console::WriteLine(s);
}
void main()
{
f("Hello");
m("World");
}
生成exe文件后,用反编译程序查看 f 函数:
[PreserveSig, MethodImpl(MethodImplOptions.Unmanaged, MethodCodeType=MethodCodeType.Native), SuppressUnmanagedCodeSecurity]
public static unsafe void modopt(CallConvCdecl) f<char const *>(sbyte modopt(IsSignUnspecifiedByte) modopt(IsConst)*);
看不到源码,而方法属性标记为Unmanaged。
如果没有加上#pragma unmanaged,反编译得到的 f 函数为:
internal static unsafe void modopt(CallConvCdecl) f<char const *>(sbyte modopt(IsSignUnspecifiedByte) modopt(IsConst)* t)
{
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);
}
其中的函数内容一目了然。如果你的函数没有调用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
让C++代码与C#代码一起生成一个单一的Assembly
本文提供了一个方法,让C++源码和C#源码一起编译链接成一个单一的Assembly。
由于M$的C++/CLI提供了IJW方法,允许将旧有C++代码也编译成托管代码,因此这篇小文可以用极小的工作量来彻底解决所有C++遗留代码移植到.NET的问题。
这个方法,再加上前次的小文《您也使用托管C++吗?》,就可以把C++遗留代码移植到.NET的所有方法一网打尽了。
好了,言归正传。
假设一个很简单的C++程序,它只有一个函数:
//c.h
#pragma once
int sqr(int n);
//c.cpp
#include "c.h"
int sqr(int n)
{
return n*n;
}
为了能让它与C#共同工作,必须为它加个.NET的包装:
//wrapper.cpp
#include "c.h"
namespace wrapper
{
public ref class calc
{
public:
static int sqr(int n)
{
return ::sqr(n);
}
};
}
现在可以用C#去调用它了:
//cs.cs
using System;
namespace test
{
public class Program
{
public static void Main()
{
Console.WriteLine(wrapper.calc.sqr(11));
}
}
}
现在,如何将这些源码编译成一个exe文件呢?
用集成环境是很难的,最方便的,还是创建一个makefile或MSBuild来完成。
由于这个例子很简单,我用命令行来创建这个exe文件:
cl c.cpp wrapper.cpp /c /clr
生成 c.obj 和 wrapper.obj
csc /addmodule:wrapper.obj /target:module /out:cs.netmodule cs.cs
生成 cs.netmodule
link c.obj wrapper.obj cs.netmodule /LTCG /Subsystem:Console /entry:test.Program.Main /out:o.exe
生成 o.exe
运行 o.exe 显示结果:121
------
C++与C#混合生成.NET程序
参考资料:
1. .netmodule Files as Linker Input http://msdn2.microsoft.com/en-us/library/k669k83h(VS.80).aspx
2. 选择 .netmodule 输入文件的格式 http://msdn2.microsoft.com/zh-CN/library/0zyh2sf2(VS.80).aspx
后记:(2006-04-19)
今天特意上网查了一下,发现已经有这个介绍了。大家参考一下。
原文地址:http://blog.joycode.com/junfeng/archive/2005/05/25/51838.aspx?Pending=true
-------
部份例子来自MSDN.