思想决定高度

永远的思想者

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  17 随笔 :: 0 文章 :: 0 评论 :: 0 引用

2011年2月24日 #

When you compile an application, you can specify that it should run on a Windows 64-bit operating system either as a native application or under WOW64. WOW64 is a compatibility environment that enables a 32-bit application to run on a 64-bit system. WOW64 is included in the system.

All applications that are built on the .NET Framework versions 1.0 or 1.1 are treated as 32-bit applications and are always executed under WOW64 on the 32-bit common language runtime (CLR) on a 64-bit operating system. In addition, 32-bit-specific applications that are built on the .NET Framework version 3.5 would run under WOW64 on 64-bit systems.

Visual Studio installs the 32-bit version of the CLR on an x86 computer, and both the 32-bit version and the appropriate 64-bit version of the CLR on a 64-bit Windows computer. (Because Visual Studio 2008 is a 32-bit application, when it is installed on a 64-bit system, it runs under WOW64.)

Note Note:

Because of the design of x86 emulation and the WOW64 subsystem for the Itanium processor family, applications are restricted to execution on one processor. These factors reduce the performance and scalability of 32-bit .NET Framework applications that run on Itanium-based systems. We recommend that applications that use the .NET Framework 1.1 be used for interactive, client applications and not for applications that demand high performance or scalability, for example, high-load ASP.NET applications. If you can, use the , which includes native 64-bit support for Itanium-based systems, for increased performance and scalability.

When you run a 64-bit managed application on a 64-bit Windows operating system, you can create an object of no more than 2 gigabytes (GB).

Many assemblies run identically on both the 32-bit CLR and the 64-bit CLR. However, some programs may behave differently, depending on the CLR, for one or more of the following reasons:

  • Structs that contain members that change size depending on the platform, such as any pointer type.

  • Pointer arithmetic that includes constant sizes.

  • Incorrect platform invoke or COM declarations that use Int32 for handles instead of IntPtr.

  • Casting IntPtr to Int32.

For more information about how to port a 32-bit application to run on the 64-bit CLR, see Migrating 32-bit Managed Code to 64-bit on the MSDN website.

For general information about 64-bit programming, see the following documents:

By default, when you use the .NET Framework 2.0 to build an application on either a 32-bit or a 64-bit computer, the application will run on a 64-bit computer as a native application (that is, not under WOW64). The following table lists documents that teach how to use Visual Studio compilers to create 64-bit applications that will run as native, under WOW64, or both.

Compiler

Compiler Option

Visual Basic

/platform (Visual Basic)

Visual C#

/platform (Specify Output Platform) (C# Compiler Options)

Visual C++

You can create platform-agnostic, Microsoft intermediate language (MSIL) applications by using /clr:safe. For more information, see /clr (Common Language Runtime Compilation).

Visual C++ includes a separate compiler for each 64-bit operating system. For more information about how to use Visual C++ to create native applications that run on a 64-bit Windows operating system, see 64-Bit Programming with Visual C++.

JScript

/platform (Visual Studio - JScript)

To determine whether an .exe file or .dll file is meant to run only on a specific platform or under WOW64, use corflags.exe at the command prompt. You can also use corflags.exe to change the platform status of an .exe file or .dll file. For more information, see CorFlags Conversion Tool (CorFlags.exe). The CLR header (or COM+ Runtime header) of a Visual Studio assembly has the major runtime version number set to 2 and the minor runtime version number set to 5 (0 in Visual Studio .NET 2003 assemblies). Applications that have the minor runtime version set to 0 are treated as legacy applications and are always executed under WOW64.

To programmatically query an .exe or .dll to see whether it is meant to run only on a specific platform or under WOW64, use the GetPEKind method.

posted @ 2011-02-24 22:20 绿化树 阅读(30) 评论(0) 编辑

WoW64 (Windows 32-bit On Windows 64-bit) is a subsystem of the Windows operating system that is capable of running 32-bit applications and is included on all 64-bit versions of Windows — including Windows 2000 Limited Edition, Windows XP Professional x64 Edition, IA-64 and x64 versions of Windows Server 2003, as well as 64-bit versions of Windows Vista, Windows Server 2008, and Windows 7. In Windows Server 2008 R2 Server Core, it is an optional component. WoW64 is designed to take care of many of the differences between 32-bit Windows and 64-bit Windows, particularly involving structural changes to Windows itself.

Translation libraries

The Wow64 subsystem is a lightweight compatibility layer that has similar interfaces on all 64-bit versions of Windows. Its primary purpose is to create a 32-bit environment that provides the interfaces required to allow 32-bit Windows applications to run unmodified in the 64-bit system. Technically, WoW64 is implemented using three dynamic-link libraries (DLLs): Wow64.dll, which is the core interface to the Windows NT kernel that translates between 32-bit and 64-bit calls, including pointer and call stack manipulations; Wow64win.dll, which provides the appropriate entry points for 32-bit applications; and Wow64cpu.dll, which takes care of switching the processor from 32-bit to 64-bit mode.

Architectures

Despite its outwardly similar appearance on all versions of 64-bit Windows, WoW64's implementation varies depending on the target processor architecture. For example, the version of 64-bit Windows developed for the Intel Itanium 2 processor (known at Microsoft as IA-64 architecture) uses Wow64win.dll to set up the emulation of x86 instructions within the Itanium 2's unique instruction set. This emulation is a much more computationally-expensive task than the Wow64win.dll's functions on the x86-64 architecture (alias "Intel 64" in the Intel terminology, or "AMD64" in the original AMD implementation of this 64-bit mode, also used in the architecture type name of Windows installers), which switches the processor hardware from its 64-bit mode to compatibility mode when it becomes necessary to execute a 32-bit thread, and then handles the switch back to 64-bit mode.

Registry and file system

The WoW64 subsystem also handles other key aspects of running 32-bit applications. It's involved in managing the interaction of 32-bit applications with the Windows components such as the Registry, which has distinct keys for 64-bit and 32-bit applications. For example HKEY_LOCAL_MACHINE\Software\Wow6432Node is the 32-bit equivalent of HKEY_LOCAL_MACHINE\Software (although 32-bit applications are not aware of this redirection). Some Registry keys are mapped from 64-bit to their 32-bit equivalents, while others have their contents mirrored, depending on the edition of Windows.

The operating system uses the %SystemRoot%\system32 directory for its 64-bit library and executable files. This is done for backward compatibility reasons, as many legacy applications are hardcoded to use that path. When executing 32-bit applications, WoW64 transparently redirects 32-bit DLLs to %SystemRoot%\SysWOW64, which contains 32-bit libraries and executables. 32-bit applications are generally not aware that they are running on a 64-bit operating system. 32-bit applications can access %SystemRoot%\System32 through the pseudo directory %SystemRoot%\sysnative.

There are two "Program Files" directories, both visible to both 32-bit and 64-bit applications. The directory that stores the 32 bit files is called Program Files (x86) to differentiate between the two, while the 64 bit maintains the traditional "Program Files" name without any additional qualifier.

 Incompatible applications

32-bit applications that include only 32-bit kernel-mode device drivers, or that plug into the process space of components that are implemented purely as 64-bit processes (e.g. Windows Explorer) cannot be executed on a 64-bit platform. Service applications are supported. The SysWOW64 folder located in the Windows folder on the OS drive contains several applications to support 32-bit applications (e.g. cmd.exe, useful to register 32bit windows services, odbcad32.exe, to register ODBC connections for 32-bit applications).

Internet Explorer is implemented as both a 32-bit and a 64-bit application because of the large number of 32-bit ActiveX components on the internet that would not be able to plug into the 64-bit version. The 32-bit version is used by default and the 64-bit version cannot be set to be the default browser.

A bug in the translation layer of the x64 version of WoW64[1][2] also renders all 32-bit applications that rely on the Windows API function GetThreadContext incompatible. Such applications include application debuggers, call stack tracers (e.g. IDEs displaying call stack) and applications that use garbage collection (GC) engines. One of the more widely used but affected[3] GC engines is the Boehm GC. It is also used as the default Garbage Collector of the equally popular Mono. While Mono has introduced a new (but optional) GC as of October 2010 called SGen-GC, it performs stack scanning in the same manner as Boehm GC, thus also making it incompatible under Wow64. No fix is planned and no workarounds have been provided by Microsoft as of 15th November 2010.

posted @ 2011-02-24 22:19 绿化树 阅读(121) 评论(0) 编辑

人们通常认为,Exchange Server 上必须安装 MAPI 客户端(例如 Microsoft Outlook 或 Exchange 客户端)才能创建 MAPI 配置文件;这种想法是错误的。因为此原因而安装 MAPI 客户端是不必要的,而且就 Outlook 而言,不建议您在生产服务器上安装它。只要安装有 MAPI 子系统(Exchange 中已经包含此子系统),就可以通过其他各种方法来创建 MAPI 配置文件。

您尝试其中任一种方法之前,您需要确保服务器上的 Mapisvc.inf 文件中生成了适当的项目。 有关如何修改该文件的其他信息,请单击下面的文章编号,以查看 Microsoft 知识库中相应的文章:
294470  (http://support.microsoft.com/kb/294470/EN-US/ ) HOWTO:Add Entries For Exchange Services to Mapisvc.inf

1. 使用 NewProf.exe

此实用工具随 Microsoft Outlook 一起提供。您可以在 MSDN Library 中的“Creating a Profile with NEWPROF”(使用 NEWPROF 创建配置文件)主题下找到它的使用说明。 有关其他信息,请单击下面的文章编号,以查看 Microsoft 知识库中相应的文章:
145905  (http://support.microsoft.com/kb/145905/EN-US/ ) XCLN:Newprof.exe Command-Line Options
148664  (http://support.microsoft.com/kb/148664/EN-US/ ) XCLN:Description of the Profile Descriptor File

2. 使用 ProfMan2 示例

该示例使用 MAPI IProfAdmin 接口(参见下面的方法 5 中的说明)。您可以在以下文章中找到此示例:
228736  (http://support.microsoft.com/kb/228736/EN-US/ ) 示例:Profman2.exe - MAPI 配置文件管理器 v2.0

3. 使用 MAPILogonEx

当调用 MAPILogonEx 而没有指定配置文件并且设置了 MAPI_LOGON_UI 标志时,如果计算机上没有任何配置文件,MAPI 会显示配置文件创建向导。如果有现有的配置文件,MAPI 则显示选择配置文件对话框。单击新建以启动配置文件创建向导。

示例代码
// CreateProfileWithMAPILogonEx function: This takes advantage of the 
// profile prompt dialog's "New" button.
bool CreateProfileWithMAPILogonEx()
{
    HRESULT         hRes = S_OK;        // Return code from MAPI calls.
    LPMAPISESSION   lpSession = NULL;   // MAPI Session pointer.

    // Initialize MAPI.
    if (FAILED(hRes = MAPIInitialize(NULL)))
    {
        cout<<"Error initializing MAPI. hRes = 0x"<<hex<<hRes<<dec<<endl;
        return FALSE;
    }

    // Instruct user to click the "New" button.
    cout<<"When the \"Choose Profile\" dialog appears, click the \"New\" button"
        <<"to configure a new profile."<<endl;

    // Call MAPILogonEx to display the profile chooser dialog.
    if (FAILED(hRes = MAPILogonEx(NULL,
                                  NULL,
                                  NULL,
                                  MAPI_LOGON_UI,
                                  &lpSession)))
    {
        cout<<"Error logging on. hRes = 0x"<<hex<<hRes<<dec<<endl;
        return FALSE;
    }

    // Log off the session.
    if (FAILED(hRes = lpSession->Logoff(0,0,0)))
    {
        cout<<"Error logging off. hRes = 0x"<<hex<<hRes<<dec<<endl;
    }

    // Release the session.
    lpSession->Release();

    // Uninitialize MAPI.
    MAPIUninitialize();

    // Return true to indicate success.
    return TRUE;
}
				

4. 使用 LAUNCHWIZARDENTRY 函数

该函数直接调用配置文件创建向导。

示例代码
// CreateProfileWithLAUNCHWIZARD function: This uses the LAUNCHWIZARDENTRY API
// to display the profile configuration UI.
bool CreateProfileWithLAUNCHWIZARD()
{
    HRESULT     hRes = S_OK;                    // Return code from MAPI calls.
    TCHAR       szProfName[80] = {0};           // String to hold profile name.
    LPTSTR      szServices[] = {"MSEMS", NULL}; // String to hold message service names.

    // Call LaunchWizard to add the MSEMS service.
    if (FAILED(hRes = LaunchWizard(NULL,
                                   NULL,
                                   (LPCTSTR *)szServices,
                                   80,
                                   szProfName)))
    {
        cout<<"Error launching wizard. hRes = 0x"<<hex<<hRes<<dec<<endl;
        return FALSE;
    }

    // Return true indicating success.
    return TRUE;
}
				

5. 使用 MAPI IProfAdmin 接口

使用此 MAPI 接口,您可以在无用户干预的情况下以编程方式创建配置文件。

示例代码
// CreateProfileWithIProfAdmin function: This uses the MAPI IProfAdmin to 
// programmatically create a profile. No UI is displayed.
bool CreateProfileWithIProfAdmin()
{
    HRESULT         hRes = S_OK;            // Result from MAPI calls.
    LPPROFADMIN     lpProfAdmin = NULL;     // Profile Admin object.
    LPSERVICEADMIN  lpSvcAdmin = NULL;      // Service Admin object.
    LPMAPITABLE     lpMsgSvcTable = NULL;   // Table to hold services.
    LPSRowSet       lpSvcRows = NULL;       // Rowset to hold results of table query.
    SPropValue      rgval[2];               // Property structure to hold values we want to set.
    SRestriction    sres;                   // Restriction structure.
    SPropValue      SvcProps;               // Property structure for restriction.
    char            szProfile[80] = {0};    // String for profile name.
    char            szMailbox[80] = {0};    // String for mailbox name.
    char            szServer[80] = {0};     // String for server name.

    // This indicates columns we want returned from HrQueryAllRows.
    enum {iSvcName, iSvcUID, cptaSvc};
    SizedSPropTagArray(cptaSvc,sptCols) = { cptaSvc, PR_SERVICE_NAME, PR_SERVICE_UID };

    // Get configuration info from user.
    cout<<"Enter name for profile: ";
    cin>>szProfile;
    cout<<"Enter Exchange mailbox name: ";
    cin>>szMailbox;
    cout<<"Enter Exchange server name: ";
    cin>>szServer;

    // Initialize MAPI.

    if (FAILED(hRes = MAPIInitialize(NULL)))
    {
        cout<<"Error initializing MAPI.";
        goto error;
    }

    // Get an IProfAdmin interface.

    if (FAILED(hRes = MAPIAdminProfiles(0,              // Flags.
                                        &lpProfAdmin))) // Pointer to new IProfAdmin.
    {
        cout<<"Error getting IProfAdmin interface.";
        goto error;
    }

    // Create a new profile.

    if (FAILED(hRes = lpProfAdmin->CreateProfile(szProfile,     // Name of new profile.
                                                 NULL,          // Password for profile.
                                                 NULL,          // Handle to parent window.
                                                 NULL)))        // Flags.
    {
        cout<<"Error creating profile.";
        goto error;
    }

    // Get an IMsgServiceAdmin interface off of the IProfAdmin interface.

    if (FAILED(hRes = lpProfAdmin->AdminServices(szProfile,     // Profile that we want to modify.
                                                 NULL,          // Password for that profile.
                                                 NULL,          // Handle to parent window.
                                                 0,             // Flags.
                                                 &lpSvcAdmin))) // Pointer to new IMsgServiceAdmin.
    {
        cout<<"Error getting IMsgServiceAdmin interface.";
        goto error;
    }

    // Create the new message service for Exchange.

    if (FAILED(hRes = lpSvcAdmin->CreateMsgService("MSEMS",     // Name of service from MAPISVC.INF.
                                                   NULL,        // Display name of service.
                                                   NULL,        // Handle to parent window.
                                                   NULL)))      // Flags.
    {
        cout<<"Error creating Exchange message service.";
        goto error;
    }
        
    // We now need to get the entry id for the new service.
    // This can be done by getting the message service table
    // and getting the entry that corresponds to the new service.

    if (FAILED(hRes = lpSvcAdmin->GetMsgServiceTable(0,                 // Flags.
                                                     &lpMsgSvcTable)))  // Pointer to table.
    {
        cout<<"Error getting Message Service Table.";
        goto error;
    }

    // Set up restriction to query table.

    sres.rt = RES_CONTENT;
    sres.res.resContent.ulFuzzyLevel = FL_FULLSTRING;
    sres.res.resContent.ulPropTag = PR_SERVICE_NAME;
    sres.res.resContent.lpProp = &SvcProps;

    SvcProps.ulPropTag = PR_SERVICE_NAME;
    SvcProps.Value.lpszA = "MSEMS";

    // Query the table to get the entry for the newly created message service.

    if (FAILED(hRes = HrQueryAllRows(lpMsgSvcTable,
                                     (LPSPropTagArray)&sptCols,
                                     &sres,
                                     NULL,
                                     0,
                                     &lpSvcRows)))
    {
        cout<<"Error querying table for new message service.";
        goto error;
    }

    // Setup a SPropValue array for the properties you need to configure.

    // First, the server name.
    ZeroMemory(&rgval[1], sizeof(SPropValue) );
    rgval[1].ulPropTag = PR_PROFILE_UNRESOLVED_SERVER;
    rgval[1].Value.lpszA = szServer;

    // Next, the mailbox name.
    ZeroMemory(&rgval[0], sizeof(SPropValue) );
    rgval[0].ulPropTag = PR_PROFILE_UNRESOLVED_NAME; 
    rgval[0].Value.lpszA = szMailbox;

    // Configure the message service with the above properties.

        if (FAILED(hRes = lpSvcAdmin->ConfigureMsgService(
        (LPMAPIUID)lpSvcRows->aRow->lpProps[iSvcUID].Value.bin.lpb, // Entry ID of service to configure.
        NULL,                                                       // Handle to parent window.
        0,                                                          // Flags.
        2,                                                          // Number of properties we are setting.
        rgval)))                                                    // Pointer to SPropValue array.
    {
        cout<<"Error configuring message service.";
        goto error;
    }

    goto cleanup;

error:
    cout<<" hRes = 0x"<<hex<<hRes<<dec<<endl;
    return FALSE;

cleanup:
    // Clean up.
    if (lpSvcRows) FreeProws(lpSvcRows);
    if (lpMsgSvcTable) lpMsgSvcTable->Release();
    if (lpSvcAdmin) lpSvcAdmin->Release();
    if (lpProfAdmin) lpProfAdmin->Release();

    MAPIUninitialize();
    return TRUE;

}
				
示例
从 Microsoft 下载中心可以下载以下文件:
Profiler.exe (http://download.microsoft.com/download/exchplatinumbeta/sample/1.0/nt45/en-us/profiler.exe)  

Profiler.exe 是一个简单的 Microsoft Visual C++ 示例,它演示了上面的方法 3 到方法 5。Profiler.exe 文件包含下列文件:

文件名大小
Profiler.dsw 1 KB
Profiler.dsp 5 KB
Profiler.cpp 12 KB

posted @ 2011-02-24 21:33 绿化树 阅读(231) 评论(0) 编辑

Hi,

I have my projects in VS 2008 SP1 and is using .NET 3.5 SP1. Till now we are developing application in Any CPU platform setting for all the projects and supported only 32-bit platforms. Now we are going to support 64-bit OS platforms as well.

Q-1. What difference it makes by building solution for Any CPU, x86 and x64 platform?

Q-2. Will it make any difference if I build my application using Any CPU platform setting and run on x64 platform?

Q-3. What are the advantages of creating different builds for x86 and x64? Any performance gain? anything considerable change?

Q-4. I have my VC++ dll library which is imported in one of my C# project as well. What changes are required in that VC++ dll to make it running on both x86 and x64?

Appreciate your inputs.


For Q1-Q3:

AnyCPU will compile your assembly to run on any platform. DLLs and EXEs compiled with AnyCPU option will behave with sutle difference. On a 64-bit Windows operating system, EXEs compiled with this option will execute on the 64 bit CLR, while DLLs compiled with this option will execute on the same CLR as the process into which it is being loaded. So, if a 32bit EXE load a DLL that is compiled with this option, the process is 32bit so the DLL will run on the 32bit CLR.

x86 option compiles your assembly to be run by the 32-bit, x86-compatible common language runtime. On 32bit platform, the EXEs will run as 32bit process. On 64bit platform, the EXEs will run under WOW64 mode.

x64 option compiles your assembly to be run by the 64-bit common language runtime on a computer that supports the AMD64 or EM64T instruction set.

Also please take a look at: How to: Optimize an Application for a Specific CPU Type

For Q4:

Please take a look at : How do exes/dlls generated with the /platform:x switch interact?

posted @ 2011-02-24 21:25 绿化树 阅读(289) 评论(0) 编辑

64 位势不可挡

从某种意义上讲,处理器的发展直接影响着整个IT行业的发展水平,电脑核心从16位到32的转变对IT行业产生的巨大影响就是一个强有力证明。从386、486直到奔腾系列的CPU都是32位,大多数情况32位计算已经能满足现阶段人们的需要。然而随着互联网等技术的发展,对内存的需求越来越大,比如大型服务器,每秒钟处理的数据量就超过2GB,32位就难以满足需求了。

32位的风云十年

在信息技术领域,“位数”是衡量计算机性能的重要标准之一。位数在很大程度上决定着计算机的内存最大容量、文件的最大长度、数据在计算机内部的传输速度、处理速度和精度等性能指标。在近十年的时间里,X86-32位平台由于其性价比高,获得广泛应用。

按照以前的习惯,我们把基于80X86的CPU架构称作X86结构,这是Intel公司在1978年发布其第一块16位的CPU(5MHz 8086)时开发的,1981年IBM推出的第一台PC中使用的也是X86指令,同时电脑中为提高浮点数据处理能力而加的X87芯片系列数学协处理器则使用X87指令,后来将X86指令集和X87指令集统称为X86指令集。为了保证计算机能够继续运行以往开发的各类应用程序以保护和继续软件资源,后续的CPU(包括大多数第三方处理器厂商)继续使用X86指令集,采用这种指令集的CPU系统被称作X86架构。

64位异军突起

64位计算技术为这一问题的解决提供了契机,64位技术可以突破这两大限制,不仅使得处理器的计算能力有了更加广阔的发展空间,而且其所能支持的内存寻址能力更是达到了180亿GB,将能够彻底解决32位计算系统所遇到的瓶颈现象。但人们很快又发现,目前,已有的64位计算平台主要是基于RISC(Reduced Instruction Set Computing,精简指令集计算机)架构的高端应用,涉及厂商包括IBM、HP和SUN。其服务器高昂的价格使得64位计算长期被局限在电信、金融等少数应用领域,不能形成大规模的应用环境。享受64位计算的方便快捷成了高端应用的特权,其高昂的价格普通网络用户根本无法接受。在这种情况下,普通网络用户陷入了一个进退两难的尴尬境地……

2003年4月AMD公司推出首款64位处理器,这是一款采用X86兼容架构的64位CPU,它最大的特点就是在支持64位数据寻址的同时,向下兼容32位数据寻址,妥善解决了CPU从32位到64位的过渡和兼容问题,从而掀起了桌面处理器从32位向64位过渡的技术革命。2004年3月,Intel也发布了其首款64位Xeon处理器,它采用EM64T(Intel Extended Memory 64 Technology)技术,同时支持32位和64位运算,在运行64位程序时采用64位工作方式,而在处理32位运算时依然是IA32(即X86)工作结构,这实际上也可以认为是X86-64架构。

从现在开始,本文所提到的64位技术,都是指兼容32位计算的X86-64技术或EM64T技术。

64位技术综述

在AMD强大的宣传攻势下,64位技术和64位处理器已经成了众人皆知的时髦名词,那么究竟什么是64位技术呢?64位处理器与32位处理器相比,具体有哪些优点?同样是64位/32位兼容处理器,Intel和AMD的产品在技术上又有怎样的区别呢?

64位技术的定义

64位技术是相对于32位技术而言的,这个位数指的是CPU GPRs(General-Purpose Registers,通用寄存器)的数据宽度为64位。此外,64位指令集就是运行64位数据的指令,64位处理器则表示处理器一次就可以运行64位的数据(如图所示)。

由图中可以看出,64位处理器的指令宽度并没有改变,只是增大了通用寄存器和数据通道的宽度,我们可以简单的理解为:64位处理器的定义是拥有数据宽度为64位的寄存器,并且可以一次传输、运算64位的数据。

当然上面的理解并不全面。通常,在通用寄存器内储存两种数据:整数数据Integer和地址数据Address,它们都由ALU进行运算。除了上面两种数据外,现在的处理器通常还支持浮点数据和多媒体数据,并且都有各自专用的寄存器和执行单元。下面以Intel的32位和64位x86处理器作简单对比,说明上述四种数据在处理器内的存储运算情况。

由附表中可以看出64位处理器和32位处理器相比,浮点数据和多媒体数据的专用寄存器数据宽度并没有改变,64位处理器所改变的主要是整数运算和内存寻址的宽度。

需要补充说明的是X86处理器在进行双精度浮点运算时采用了80位的浮点寄存器,实际上浮点运算是64位,但是当处理器将数据转换为内部数据时,为了提高运算的精度就采用了80位的格式。单精度浮点运算仍然使用64位浮点寄存器。

64位处理器的优点

光看数字,64比32整整大了一倍,那么64位处理器在应用上有什么优点呢?总的来看,64位计算主要有以下优点:

1. 可以进行更大范围的整数运算

一个32位整数可以表示2的32次方也就是4GB的数值,而一个64位整数,即2的64次方也就是1800万TB,可以看做是无限大。64位整数数据的应用程序在64位的硬件上进行运算可以大幅提高计算性能,在同一周期内可以处理更多的数据,从而大大减少运算时间,也使得某些超大数运算得以更好的解决,这使得气象模拟、环境模拟等大型科学运算从中受益匪浅。

2. 可以支持更大的内存

另一个优点便是64位处理器可以支持64位的内存寻址。同样的原理,内存地址也是整数,ALU(算术逻辑运算器)和寄存器既然能够存储更多的整数,那同样也能够容下更多的内存地址,打破了32位下4GB的限制。实际上64位处理器究竟需要多大的物理和虚拟内存寻址完全取决于不同处理器的需求。

但我们需要注意的一点是:64位处理器的性能≠两倍于32位处理器的性能。我们不能因为数字上的变化,而简单的认为64位处理器的性能是32位处理器性能的整整两倍。实际上在32位应用环境下,32位处理器的性能在很多时候反倒会更强。挫折湮没了激情

电脑核心从32位到64位的转变正在悄然进行中,比起行业应用的如火如荼,普通消费者却迟迟不能提起兴趣,就连专家也表示64位取代32位的时代还为时尚早。最先把64位处理器引入寻常百姓家的AMD正在全力以赴地推广64位计算技术在全行业的应用,然而这场革命起初因为缺少Intel这个主角似乎有些进行不下去了,因为消费者在观望,他们担心过早地选择64位会为它的早产付出高额代价,但同时,他们更加担心会成为时代更替的牺牲者。现如今,AMD的主动 “推”和Intel的“附和”,让消费者陷入了两难境地。智者开始考虑这样一个问题,现在到底该不该迈进64位计算技术的新纪元呢?

专家认为,64位计算技术迟迟不能普及的根本原因是桌面级应用对64位技术的依赖并不是非常迫切。他进一步说,在国内,我们更需要的是一定价格下的高性能,而不是盲目追求没有必要的应用扩展。

10年前,计算机历史上曾发生过一次重大变革——多媒体技术唤起了人们对32位电脑的渴望,那些想让电脑屏幕鲜艳起来、让电脑奏出美妙的音乐、让电脑能与人交流的人发现,自己破旧的16位286电脑再也无法满足最基本的需求了,于是业内孕育并上演了32位电脑“夺权篡位”的一幕。也许那一幕连最初开发MS-DOS的比尔?盖茨都没有想到,否则他也不会认为电脑只要有640KB内存就够了。

10年后,人们还没有充分享受到32位处理器的高性能,因为32位处理器理论上可以支持高大4GB的内存,也因为32位会带来较小的延迟,更高的处理器频率。实际上,目前推动64位技术应用的并不是桌面机应用,行业级应用才是64位这个锋利的武器的用武之地。

一位资深的硬件评测专家断言,如果说32位取代16位确实是由应用和需求在驱动,那么64位取代32位更像是厂商炒作的噱头。

Windows XP X64为何姗姗来迟

大家都知道,微软64位操作系统发布时间的一再延后,让AMD错过了推广、销售64位产品,并全面超越Intel的大好机会,也是在过去两年中64位一直未能普及的根本原因,那么这背后到底是什么东西在驱使呢?笔者看来,至少有三个因素决定了这个事实。

Intel在作怪?“Wintel联盟”是很容易想到的一个原因,不过我们很难说Intel在其中发挥了多大的作用。

驱动跟不上?驱动是任何一个操作系统的组成部分,适当的驱动优化和移植需要很长时间才能完成。微软的64位Windows XP可以同时运行32位和64位应用程序,驱动则必须是原生64位编写的。看起来,这个理由倒也合情合理。

微软追求完美?由于64位Windows迟迟没有,微软也看到了不少客户为了早日用上X86-64位技术而转向了Linux平台,这肯定是他们不愿意看到的。不过为了保证新操作系统的完美,微软确实花了很长时间。



64位与32位的工作示意图



64位万事俱备 只欠东风

两年时间过去了,64位平台终于迎来了快速发展的黄金时期。虽然Intel和AMD的桌面64位CPU都能支持32位代码,但这未免有些大材小用。因此,要真正进入64位的广阔天地,必须使用“纯”64位软件,即完全使用64位扩展指令集。除了部分新开发的软件外,大多数32位软件都要进行移植或升级才行。由于64位CPU只是扩展了32位指令,并不像32位CPU那样对16位指令进行了全新改造,还增加了新的工作模式,因此 32位向64位的移植将较为容易,比16位向32位过渡时简单得多。

总结:我们要认清64位处理器的优势和发展趋势,但也不可迷信64位。
posted @ 2011-02-24 21:18 绿化树 阅读(1492) 评论(0) 编辑

I received a question about this recently, so i figured i'd elaborate here with a little example...

 

Let's assume we have the following three dlls:

   anycpu.dll      -- compiled "any cpu"

   x86.dll           -- compiled "x86"

   x64.dll           -- compiled "x64"

 

And the following three exes:

   anycpu.exe     -- compiled "any cpu"

   x86.exe          -- compiled "x86"

   x64.exe          -- compiled "x64"

 

What happens if you try to use these exes and dlls together? We have to consider two possible scenarios, running on a 32-bit machine and running on a 64-bit machine...

 

On a 32-bit x86 machine:

anycpu.exe -- runs as a 32-bit process, can load anycpu.dll and x86.dll, will get BadImageFormatException if it tries to load x64.dll

x86.exe -- runs as a 32-bit process, can load anycpu.dll and x86.dll, will get BadImageFormatException if it tries to load x64.dll

x64.exe -- will get BadImageFormatException when it tries to run

 

On a 64-bit x64 machine:

anycpu.exe -- runs as a 64-bit process, can load anycpu.dll and x64.dll, will get BadImageFormatException if it tries to load x86.dll

x86.exe -- runs as a 32-bit process, can load anycpu.dll and x86.dll, will get BadImageFormatException if it tries to load x64.dll

x64.exe -- runs as a 64-bit process, can load anycpu.dll and x64.dll, will get BadImageFormatException if it tries to load x86.dll

posted @ 2011-02-24 21:11 绿化树 阅读(13) 评论(0) 编辑


/platform (Specify Output Platform) (C# Compiler Options) 

Specifies which version of the common language runtime (CLR) can run the assembly.
/platform:string

Parameters

string

x86, Itanium, x64, or anycpu (default).

  • x86 compiles your assembly to be run by the 32-bit, x86-compatible common language runtime.

  • Itanium compiles your assembly to be run by the 64-bit common language runtime on a computer with an Itanium processor.

  • x64 compiles your assembly to be run by the 64-bit common language runtime on a computer that supports the AMD64 or EM64T instruction set.

  • anycpu (default) compiles your assembly to run on any platform.

On a 64-bit Windows operating system:

  • Assemblies compiled with /platform:x86 will execute on the 32 bit CLR running under WOW64.

  • Executables compiled with the /platform:anycpu will execute on the 64 bit CLR.

  • DLLs compiled with the /platform:anycpu will execute on the same CLR as the process into which it is being loaded.

For more information about developing an application to run on a Windows 64-bit operating system, see 64-bit Applications.

To set this compiler option in the Visual Studio development environment

  1. Open the Properties page for the project. For details, see How to: Set Project Properties (C#, J#).

  2. Click the Build property page.

  3. Modify the Platform target property.

Note   /platform is not available in the development environment in Visual C# Express.

For information on how to set this compiler option programmatically, see PlatformTarget.

The following example shows how to use the /platform option to specify that the application should only be run by the 64-bit CLR on a 64-bit Windows operating system for Itanium.

csc /platform:Itanium myItanium.cs
posted @ 2011-02-24 21:08 绿化树 阅读(110) 评论(0) 编辑

2010年3月11日 #

1        预备知识—程序的内存分配
一个由c/C++编译的程序占用的内存分为以下几个部分

u       1、栈区(stack)—   由编译器自动分配释放 (由编译器在需要的时候分配,在不需要的时候自动清除) 。存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

 

u       2、堆区(heap) —   一般由程序员分配释放(由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete)(c中用malloc,c++中用new), 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。

u       自由存储区,就是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来结束自己的生命的。

 

u       3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。在C++里面没有这个区分了,他们共同占用同一块内存区。 - 程序结束后有系统释放

 

u       4、文字常量区  —常量字符串就是放在这里的,不允许修改。 程序结束后由系统释放

 

u       5、程序代码区—存放函数体的二进制代码。 

 

 

l        更改堆栈大小的设置:

在VC6下面,默认的栈空间大小是1M(好像是,记不清楚了)。当然,我们可以修改:

    打开工程,依次操作菜单如下:Project->Setting->Link,在Category 中选中Output,然后在Reserve中设定堆栈的最大值和commit。

注意:reserve最小值为4Byte;commit是保留在虚拟内存的页文件里面,它设置的较大会使栈开辟较大的值,可能增加内存的开销和启动时间。

 

1.1          例子程序
这是一个前辈写的,非常详细

//main.cpp

int a = 0; 全局初始化区

char *p1; 全局未初始化区

main()

{

int b; 栈

char s[] = "abc"; 栈

char *p2; 栈

char *p3 = "123456"; 123456\0在常量区,p3在栈上。

static int c =0; 全局(静态)初始化区

p1 = (char *)malloc(10);

p2 = (char *)malloc(20);

分配得来得10和20字节的区域就在堆区。

strcpy(p1, "123456"); 123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。

}

 

2        堆和栈的理论知识
2.1      申请方式
stack:

由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间

heap:

需要程序员自己申请,并指明大小,在c中malloc函数

如p1 = (char *)malloc(10);

在C++中用new运算符

如p2 = (char *)malloc(10);

但是注意p1、p2本身是在栈中的。

2.2      申请后系统的响应

只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。

首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,

会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。

 

2.3      申请大小的限制

在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。

堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

 

2.4      申请效率的比较:
栈由系统自动分配,速度较快。但程序员是无法控制的。

堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.

另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活。

 

2.5      堆和栈中的存储内容
栈: 在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。

当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。

堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。

 

2.6      存取效率的比较
char s1[] = "aaaaaaaaaaaaaaa";

char *s2 = "bbbbbbbbbbbbbbbbb";

aaaaaaaaaaa是在运行时刻赋值的;

而bbbbbbbbbbb是在编译时就确定的;

但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。

比如:

#include

void main()

{

char a = 1;

char c[] = "1234567890";

char *p ="1234567890";

a = c[1];

a = p[1];

return;

}

对应的汇编代码

10: a = c[1];

00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]

0040106A 88 4D FC mov byte ptr [ebp-4],cl

11: a = p[1];

0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]

00401070 8A 42 01 mov al,byte ptr [edx+1]

00401073 88 45 FC mov byte ptr [ebp-4],al

第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到edx中,在根据edx读取字符,显然慢了。

2.7      小结:
堆和栈的区别可以用如下的比喻来看出:

使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。

使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/douzixinxin/archive/2006/02/23/606942.aspx

posted @ 2010-03-11 16:22 绿化树 阅读(221) 评论(0) 编辑

  • 摘要:本文介绍C# 4.0的4个新特性,包括:通过委托成员来实现接口、匿名返回类型、接口相似性(ducktyping)、安全的null延迟赋值操作符。

1. 通过委托成员来实现接口
在C# 4.0中可以通过委托某个成员的实现来实现一个接口,例如下面的代码:
//faux code public class Foo : IList { private List _Collection implements IList; public Foo() { _Collection = new List(); } }
被封闭的成员可以用以委托实现一个或多个接口,多个接口用逗号分隔。这么做可以少掉很多冗余的代码,就像上面的示例,不再需要在封闭类(Foo类)上写一大堆方法来将接口实现交给成员变量,接口的实现会直接映射到受委托的接口实现(_Collection成员变量)。这个功能同样增强了对minxins的支持。
这个就是“委托模式”了,wikipedia上对此模式的解释如下:
委托模式是指一个对象对外表现某种行为,但事实上只是将实现此行为的任务将会给一个相关的成员的技术,这种技术反转了责任。委托模式是加强组合 (聚合)、minxins及aspects的一种基本模式。
再进一步,在委托实现接口之余,我们也应当可以自由地重写某些方法如下:
public class Foo : IList { private List _Collection { get; set; } implements IList; public Foo() { _Collection = new List(); } //This would override the delegated implementation // for nice mixin functionality and easy decorator pattern implementation public int IList.Add(string value) { if (!_Collection.Contains(value)) _Collection.Add(value); } }
2. 匿名返回类型
我希望见到在C#中匿名类型可以拥有像普通的类声明一样的地位。(当前)匿名类型只能用于局部变量,不能作为方法的返回值。但是如果一个强类型的LINQ查询的返回类型可以作为方法的返回类型一定很好,比如下面的代码:
//faux code public var GetProductInfos() { var productInfos = from p in products select new { p.ProductName, p.Category, Price = p.UnitPrice }; return productInfos; }
3. Some Duck-typing or Structural Subtyping support
如果一个类中的某一个方法/属性的签名和某个接口一样,并且这个类没有实现此接口,那么这个类就将隐式地实现这个接口。只有这个类实现了接口规定的所有方法/属性的时候才被认为隐式地实现了此接口。
如果这东西走起来像鸭子,晃起来像鸭子,那么这就是鸭子!(James Riley)
那么这个和Structural Subtyping有什么区别?我承认structural subtyping更适合C#的静态样式,所以这是个'static duck typing',或者如wikipedia所述:
Duck typing与structural typing的区别仅在于类型中被访问的部分在运行期才做兼容性确认。
我们将通过一个用例来说明这种方法有什么好处:
在.NET框架中,一部分控件实现了一个叫ReadOnly的属性,比如TextBox, DataGrid, NumericUpDown
现在我们建一个叫IReadOnlyRestricable的接口
public interface IReadOnlyRestricable { bool ReadOnly { get; set; } }
然后我们要遍历所有的控件,找出有ReadOnly属性的控件并把此属性设为true(译者注:这些控件本身没有实现IReadOnlyRestricable),在ducktyping下我们可以把控件通过类型转换为IReadOnlyRestricable,就像下面代码一样,这样我们就不需要通过反射去定位ReadOnly属性了: foreach (Control c in f.Controls) { //would like to have implicit cast to IReadOnlyRestrictable    // if interface contract is in class we are checking against IReadOnlyRestricable editable = c as IReadOnlyRestricable; if (editable != null) editable.ReadOnly = true; }
在我看来ducktyping的最大好处是可以为你不需要访问的类库定义一些接口,这可以尽可能地减少相互依赖,你可以查看Phil Haacks more extensive post on duck typing这文章来看看为什么作者相信这对C#有好处。
Krzysztof Cwalina认为,很显然的,C#的foreach关键字已经使用了duck typing。
4. 安全的null延迟赋值操作符
我很想看到一种安全地访问一个值为null的对象的属性的表达式,表达式可能形如Object.Property.Property.Value
比如我要访问Customer?.FirstName,但是Customer是null,此时Customer?.FirstName会返回null而不是抛出个NullReferenceException
再看看下面的代码,你就会知道怎么用了:
//FAUX CODE //this would throw a null reference exception    // as usual if either Customer or Order was null int orderNumber = Customer.Order.OrderNumber; //this would not compile since it would require a nullable return type int orderNumber = Customer.Order?.OrderNumber; //this would return null if a Customer was null or if Order was null int? orderNumber = Customer?.Order?.OrderNumber; if (orderNumber.HasValue) //... do something with it //instead of having to do if ((Customer != null) && (Customer.Order != null)) int a = Customer.Order.OrderNumber
posted @ 2010-03-11 14:59 绿化树 阅读(205) 评论(0) 编辑

  • 摘要:本文将谈到Visual C# 2010中的新特性—协变和逆变。随着Visual Studio 2010 Beta1的发布,更多的新功能期待大家的试用。

1. 协变和逆变
开发时经常与到以下的问题,首先看代码:
定义一个水果类和继承了该类的苹果类:

public class Fruit
{
public string Name { get; set; }
}
public class Apple : Fruit
{
}

有一个方法接收一个元素类型为Fruit的泛型集合,如下所示:

static void Output(List fruits)
{
foreach (Fruit f in fruits)
Console.WriteLine(f.Name);
}

由于Apple类继承自Fruit,所以很自然的认为以下代码“应该”能够正常运行:

static void Main(string[] args)
{
List apples = new List();
Output(apples);
Console.ReadLine();
}

但实际上在.NET Framework 4.0以前的版本中这段代码不能通过编译。还有另外一种相似的情况,在Windows窗体应用程序中鼠标点击事件和键盘按键事件拥有不同类型的事件参数MouseEventArgs和KeyPressEventArgs,这两个类均继承自EventArgs,如果希望在这两件事件触发时执行相同的操作,期望编写以下“通用”的事件处理程序附加到两个事件上是行不通的:

private void Form1_UserAction(object sender, EventArgs e)
{
}

只能须创建两个单独的事件处理程序来执行操作。
Visual C# 2010 中引入的协变和逆变解决了类似于这样的问题。
在泛型接口和委托中协变(covariance)可以使用泛型参数所定义类型的继承类型,逆变(contravariance)用于使用更一般的类型。一个泛型接口或委托的泛型参数被声明为协变或逆变时该接口或委托称为变体。在.NET Framework 4和Visual Studio 2010中,C#和Visual Basic均支持变体泛型接口和委托,并且允许泛型参数的隐式转换,而且这两种语言都允许创建自定义变体接口和委托。变体只支持引用类型,值类型不支持变体。
使用协变,第一个问题可以解决,这些代码在Visual Studio 2010中能够正确编译并运行。使用逆变可以解决第二个问题,这时事件处理程序使用了“更一般”的类型(该事件的委托允许使用更一般的类型)。
2. 接口中的变体
在.NET Framework 4中对一些已存在的泛型接口引入了变体支持,这支持实现了这些接口的类的隐式转换。这些接口是:

IEnumberable
IEnumerator
IQueryable
IGrouping
IComparer
IEqualityComparer
IComparable

开发人员还可以在泛型类型参数上使用in和out关键字以声明变体泛型接口。
2.1 使用out关键字声明协变泛型参数,例如以下代码:

interface IFileCollection
{
}

但是该变体类型T必须遵守以下规则:
1. 该类型不能作为方法参数而只能作为返回类型。

interface IFileCollection
{
T IndexOf(int i);
}

2. 第一个规则有一个特殊情况是当方法参数是逆变泛型委托时可以将该类型作为该委托的泛型类型参数。

interface IFileCollection
{
void Delete(Action file);
}

3. 该类型不能作为接口方法中泛型类型的约束,例如以下代码是错误的

interface IFileCollection
{
void Display where R : T;
}

2.2. 使用in关键字声明逆变泛型参数。逆变类型仅能用于方法的参数和泛型类型约束而不能作为返回类型。

interface IOperator
{
void Increace(T value);
void Double() where R : T;
}

2.3. 可以在一个接口中同时使用out和in定义协变和逆变,但仍需遵守相应规则。
2.4. 实现变体接口时语法与普通接口语法一致,但实现了变体接口的类不在是变体的。如果某个接口继承自变体接口,根据需要使用in或out来指定子接口是否仍然为变体类型。如果某个接口同时继承了变体接口和非变体接口,那么该接口为非变体类型,并且不能从逆变接口继承为协变接口。
3. 委托中的变体
.NET Framework 4 中为某些已存在的泛型委托引入变体支持,这些支持在使用委托类型匹配方法签名时提供了很大的灵活性,这些委托是:
System命名空间下的Action委托,例如Action和Action
System命名空间下的Func委托,例如Func和Func
Predicate委托
Comparison委托
EventHandler委托(正是由于该委托的存在解决了我们的第2个问题)
Converter委托。
同样可以使用out和in关键字定义协变和逆变泛型参数,仍然需要遵守在接口中定义时相应的规则。定义完成之后使用原来的委托访问语法实例化和调用委托即可
4. 总结
Visual C# 2010中新提供了协变和逆变的新特性,一个泛型接口或委托的泛型参数被声明为协变或逆变时该接口或委托称为变体,这为我们解决类似于开篇中的两类问题带来了便利。.NET Framework 4中已为现有的一些接口和委托增加了变体支持,并且开发人员可以使用in和out关键字定义自己的变体接口和委托,但在定义时需要遵守相应的规则。

posted @ 2010-03-11 14:56 绿化树 阅读(156) 评论(0) 编辑

51CTO.com曾经报道过讲解C# 4.0中的动态类型和动态编程的文章,也曾对C# 4.0动态编程技巧做过一些演示。但依然有读者不太了解dynamic到底有那些作用。本文摘选博主“胡里胡涂”的一篇文章,针对作者提出的两个问题对dynamic进行理解。
◆写程序时少了智能感知
◆运行程序时速度变慢(反射)
看完New features in CSharp 4.docx才恍然明白,趁着没事,把东西整理一下,希望能对暂时还不明白dynamic有什么用的朋友提供一些参考。当然,由于本人才疏学浅,不保证所有的观点都是正确的,希望大家用辩证的眼光来看这篇文章,如有错误之处,请大伙批评指正。
言归正传,dynamic关键字用于声明一个动态对象,然后通过该动态对象去调用方法或读写属性。
在使用C# 2.0或3.0的时候,如果一个对象需要在运行时才能确定,并且没有接口和基类方面的信息,那我们一般使用反射技术来调用这个未知对像的方法或属性,而C# 4.0提供的dynamic可以帮我们简化这些工作。假设我们的程序会在运行时取得一个不确定类型的对象,但这个对象一定会有个Print()方法,我们需要调用这个方法打印出一些信息,那么在C# 4.0下面,我们可以用下面的两句代码来实现这个需求。
dynamic unknowObj = GetDymanicObject();unknowObj.Print();  //调用动态对象的某个方法
这种解决方式比起用反射调用Print方法,应该简洁很多吧?程序员要做的就是别把方法名Print()打错,VS2010是不会为dynamic对象提供智能提示的,因为VS不知道运行时这个unknowObj会是什么东东……
到这里,应该有不少的朋友可以从这个例子上看出,当程序编译到unknowObj.Print()的时候,VS会帮我们生成反射的代码,用反射的方式去调用Print这个方法,实质上就是帮我们自动反射了。
如果能理解这一点,那也就不难理解C#为啥要搞dynamic这个既没智能感知,运行又慢的怪物出来了。
按New features in CSharp 4的说法,dymanic主要应用于下面的场景:
1、自动反射
2、COM组件互操作
3、混合编程,例如IronRuby和IronPython
4、处理Html DOM对象
如果有处理过上面这些工作的朋友们,应该不难理解了吧。
具体的内容,还请大伙自己看看New features in CSharp 4,里面说得比较详细。

【编辑推荐】

  1. 浅谈C# 4.0中的动态类型和动态编程
  2. C# 4.0中的动态类型与编程技巧
posted @ 2010-03-11 14:52 绿化树 阅读(193) 评论(0) 编辑

摘要:本文讲解了通过使用LINQ来改善代码的一些技巧,文中的每一个技巧都让代码写起来更加简单,可读性更强。

原文地址:http://igoro.com/archive/7-tricks-to-simplify-your-programs-with-linq/

 

自从学习LINQ以来,我发现了很多使用LINQ来改善代码的方式。每一个技巧都让代码写起来更简单,可读性更强。

这里总结了这些技巧。我会介绍如何使用LINQ来:

  • 初始化数组
  • 在一个循环中遍历多个数组
  • 生成随机序列
  • 生成字符串
  • 转换序列或集合
  • 把值转换为长度为1的序列
  • 遍历序列的所有子集

如果你在LINQ方面有心得也欢迎在评论中一起分享。

1. 初始化数组

通常,我们需要把数组的值初始化为相同的值或递增的序列值,或者可能是一个步进不为1的递增/递减序列。有了LINQ,我们可以在数组的初始化器中完成所有工作,不再需要循环!

在如下的示例代码中,第一行代码初始化了一个长度为10的数组,所有元素都是-1,第二行代码初始化b为0、1、2到9,第三行代码初始化c为100、110、120到190.

int[] a = Enumerable.Repeat(-1, 10).ToArray();

int[] b = Enumerable.Range(0, 10).ToArray();

int[] c = Enumerable.Range(0, 10).Select(i => 100 + 10 * i).ToArray();

    要提醒一下:如果你初始化一个很大的数组,最好不考虑这种优雅的方式而是使用传统的方式来替代。LINQ的这种解决方案会动态产生数组,因此垃圾数组需要在运行时被回收。也就是说,我总是会在小数组或测试调试代码的情况下使用这种技巧。

2. 在一个循环中遍历多个数组

    有个朋友问我一个C#的问题:有没有办法在一个循环中遍历多个集合?他的代码差不多是这样:

foreach (var x in array1) {

    DoSomething(x);

}

 

foreach (var x in array2) {

    DoSomething(x);

}

    这样的话,循环主体会很大,而且他也不希望这样重复的代码。但是,他又不希望创建一个数组来保存array1和array2的所有元素。

    LINQ提供了一种优雅的解决方案:Concat操作。我们可以使用单个循环来重写上面的代码,如下:

foreach (var x in array1.Concat(array2)) {

    DoSomething(x);

}

注意,由于LINQ在枚举器级别进行操作,他不会产生新的数组来保存array1和array2的元素。因此,除了优雅之外,这个方案还很高效。

3. 生成随机序列

这是一个生成N长度随机序列的简单技巧:

Random rand = new Random();

var randomSeq = Enumerable.Repeat(0, N).Select(i => rand.Next());

    有了LINQ的延迟特性,序列不会实现进行计算并保存到数组中,而是在迭代randomSeq的时候按需生成随机数。

4. 生成字符串

    LINQ同样也是生成各种类型字符串的好工具。对于测试或调试,生成字符串时很有用的。假设我们需要生成一个N长度的字符串,按照“ABCABCABC”的方式。使用LINQ,解决方案非常优雅:

string str = new string(

    Enumerable.Range(0, N)

    .Select(i => (char)(‘A’ + i % 3))

    .ToArray());

    Petar Petrov给出了另外一种有趣的方式使用LINQ来生成字符串:

string values = string.Join(string.Empty, Enumerable.Repeat(pattern, N).ToArray());

5. 转换序列或集合

    在C#或VB中我们不能实现把序列从T类型转换为U类型,即使T从U类继承。因此,即使把List<string>转换为List<object>也很难实现。(要解释为什么,请看Bick Byer的帖子)。但是如果要把IEnumerable<T>转换为IEnumerable<U>的话,LINQ有一个简单而有效的解决方案:

IEnumerable<string> strEnumerable = …;

IEnumerable<object> objEnumerable = strEnumerable.Cast<object>();

    如果我们需要转换List<T>为List<U>,LINQ也提供了解决方案,但是它会进行列表的复制:

List<string> strList = …;

List<object> objList = new List<object>(strList.Cast<object>());

    Chris Cavanagh建议另外一种解决方式:

var objList = strList.Cast<object>().ToList();

6. 把值转换为长度为1的序列

    当我们需要把单个值转化为一个长度为1的序列时,会怎么做?我们可以创建一个长度为1的数组,但是我还是喜欢LINQ的Repeat操作:

IEnumerable<int> seq = Enumerable.Repeat(myValue, 1);

7. 遍历序列的所有子集

    有的时候,遍历数组的所有子集很有用。子集和问题布尔可满足性问题以及背包问题都可以通过遍历某个序列的所有子集来简单解决。

    有了LINQ,我们可以如下声场所有arr数组的子集:

T[] arr = ...;

var subsets = from m in Enumerable.Range(0, 1 << arr.Length)

              select

                  from i in Enumerable.Range(0, arr.Length)

                  where (m & (1 << i)) != 0

                  select arr[i];

    注意,如果子集的个数超过了int,上面的代码就不能工作。因此,仅当你知道arr的长度不超过30的时候才去使用这个方式。如果arr长度超过30,你应该不会是想去遍历所有的子集,因为可能这会耗费几分钟或更长的时间。

评论和总结

    希望这些技巧对你有用,这些示例代码都使用C#实现,但是你可以很容易得改变为其它.NET语言。然而,LINQ对于支持扩展方法、lambda表达式和类型推断的语言更方便,比如C#和VB。这里的每一段代码都可行,但是我不能保证什么,请在使用前仔细检查。

posted @ 2010-03-11 14:49 绿化树 阅读(326) 评论(0) 编辑

1.总是用属性 (Property) 来代替可访问的数据成员
2.在  readonly 和 const 之间,优先使用 readonly
3.在 as 和 强制类型转换之间,优先使用 as 操作符
4.使用条件属性 (Conditional Attributes) 来代替条件编译语句 #if
5.总是为自定义类重载 ToString 方法
6.区别值类型和引用类型
7.使用不可变的值类型(Immutable Atomic Value Types)
8.在值类型中,确保0是一个合法的数据
9.理解 ReferenceEquals, static Equals, instance Equals 和 比较运算符(==)之间的关系
10.理解 GetHashCode方法的缺陷
11.在编写循环时,优先使用 foreach.
12.在定义变量的时候就将其初始化
13.使用静态构造函数来初始化静态成员变量
14.用多个构造函数时,利用构造函数链
15.使用using和try/finally来处理资源的释放
16.尽量避免产生资源垃圾
17.尽量避免使用装箱(boxing)和拆箱(unboxing)
18.实现类的 Dispose 方法
19.在接口和继承(Inheritance)之间,优先使用接口(interface)
20.区分接口和重载(overrides)
21.用委托(delegate)来实现回调(callback)
22.用事件(event)来定义外部接口
23.避免返回类内部成员的引用
24.使用元数据来控制程序
25.优先使用可序列化(serilizable)类型
26.对需要排序的对象实现IComparable和IComparer接口
27.避免使用 ICloneable接口
28.避免使用类型转换操作符
29.只有当基类加入了与派生类中现有的函数名称相同的函数时,才需要使用 new 操作符
30.尽量使用 CLS-Compliant
31.尽量编写短少,简单的函数
32.尽量编写比较小的程序集(assembly)
33.限定类型的可见性(visibility)
34.编写大粒度的 web API
35.在使用事件时,优先继承基类事件,而不是重新创建一个事件
36.多使用 framework 的运行时调试 (DEBUG, TRACE, EVENTLOG等)
37.使用.net标准的配置机制
38.使用并且在类中支持.net的数据绑定功能 (Data Binding)
39.使用.net的验证机制 (Validation)
40.根据你的需求选择正确的集合类(Collection)
41.在自定义结构中使用 DataSet
42.利用属性(Attributes)
43.不要过度使用反射(Reflection)
44.创建完整的,应用程序特定的异常
45.尽可能多的考虑程序可能出现的异常,并作出处理
46.尽可能少的使用 Interop
47.尽量使用安全代码 (safe code)
48.多多学习、使用外部工具和资源
49.准备使用 C# 4.0
50.学习 ECMA 标准
posted @ 2010-03-11 14:47 绿化树 阅读(123) 评论(0) 编辑

Forrester的高级分析师Dave West认为,测试驱动开发(TDD)就像是“圣杯”,但是“如果能达到这个目标,付出再多的辛苦也是值得的。”
    在企业向敏捷转型的各种实践中,TDD通常是最艰难的一个。
    这对美国犹他州Midvale的IBM软件组来说是确确实实的体验。他们从2007年就开始向敏捷转型。IBM开发转型部门副总裁Sue Mckinney认为,测试驱动开发前景非常诱人,但是“在这个过程中我们的付出可能也是最多的。”
    Mckinney说:“虽然我们做了大量的自动单元测试和功能测试,集成测试也做得越来越好,但是这毕竟是一种意识问题。开发人员喜欢编写代码,他们一点儿也不喜欢做测试。”
    她又补充道,但这是他们必须经历的一段旅程,“我们通过兼并获得一些优秀的设计师,他们在TDD方面有很不错的经验,值得我们效仿。我们请他们将经验和大家分享。这将是我们2009年的重点工作——让我们的团队学习TDD。”
    TDD根源于极限编程(XP),通常指开发人员在编写代码之前创建用于对代码进行自动单元测试的实践。Kirk Knoernschild是Burton集团的一位分析师。他列举了这个过程中所面临的挑战:“如果单元太大就很难写出高质量的测试。并且,虽然许多组织都说他们的开发人员在写单元测试,但是实际上他们也不知道自己的代码覆盖率有多高。还有许多组织仍然会把测试阶段推迟到软件周期的后期,这无疑会使问题变得更严重。”
    旧代码也会产生问题。据AMS Services的首席架构师Chris Kinsman说,虽然AMS已经提高了对代码的单元测试和集成测试的力度,但是他们并没有做太多TDD方面的工作。“我们现在有大量的非TDD代码。旧代码几乎没有测试驱动的。”Chris表示,“部分团队做过TDD方面的短期努力,但那不等于在全部团队都采用了这个实践。”
    他认为,AMS不一定会把TDD作为目标,“我担心我们的代码基础可能不支持这么做。在走这一步之前,我们还要先解决一些架构上的问题。我并不是从理论上反对这么做,我只是不想把我们当前的工作弄得更辛苦而已。”
    Knoernschild认为,测试驱动开发所面临的另一难题是它给人一种会减缓开发团队速度的感觉。复式记账法就是一个很好的类比。如果会计不使用复式记账法,那么他的工作会快很多。但是这样的结果就是一旦出错他无法(轻松地)使用制约系统进行检查。测试也是如此。一旦出现了问题,你会发现你缺少相应的制约系统来检查并确定问题的根源所在。
    虽然TDD确实会减缓工作速度,但是长远看来,“随着代码的增长,它实际上是会提高速度的。”
    Forrester的分析师West认为,这是一种文化上的转变,“以前的软件工程师在拿不定主意的时候会写许多文件,但是这些文件都没有说到痛处。因为这时候没有让你战胜这个问题的环境,缺少的是一种氛围。”
    West还说道,开发人员在写测试之前可能需要先研究一些东西,“并不是说你不喜欢这种方式,而是在你写出一部分编码、了解到一个大概之前,你无法真正地明白问题,也就无法写出测试来。而在敏捷所提倡的探索性过程中,你可以更好地了解问题所在,相应地也就会更习惯于这种做法。”
    West认为,测试资源按部门划分的方法也是一个障碍,“某些供应商要为此负责,因为他们鼓励卓越中心的做法——即专门负责QA的部门。这使得很难从早期就开始QA方面的工作,所以你只能在没有QA的情况做敏捷工作。你确实会做单元测试,但是你把它和其它工作分开了,因为它们属于不同的部门。”
    网络内容管理公司Vignette已经开始接受TDD,但也只是刚开始。其工程部主任Subu Subramanian说,“如果团队考虑了需求描述和验收标准,他们会把这些详细地写到测试里,这样他们在进行设计和开发的时候就会同时考虑测试。”他表示,Vignette的最终目标是把测试带入到开发周期的早期阶段。
    虽然这不是敏捷的一部分,却可以为测试驱动开发带来意想不到的效果。他说,“虽然TDD通常会和敏捷开发联系在一起,也是敏捷实践的一种,但是即使你没有把全部精力都投入到敏捷当中,TDD的价值也是显而易见的。从你写下第一行代码的时候,你就可以把测试带入开发周期。你可以在编写代码的同时生成相应的测试集。”
    他还补充说,“如何执行TDD取决于你的企业结构。你必须根据实际情况做决定。比如可以是一种两人工作的模式,一人写测试,一人写代码。”
    不管TDD将来如何发展,各企业都需要“尽可能地在早期阶段把尽量多的精力放到质量与测试当中”。

posted @ 2010-03-11 14:43 绿化树 阅读(89) 评论(0) 编辑

MIME的编码介绍(由网上资料和实践经验整合)

一、MIME: Multipurpose Internet Mail Extensions

英国帝国大学计算机在线字典FOLDOC对MIME的解释为:“多部分(multi-part)、多媒体电子邮件和WWW超文本的一种编码标准,用于传送诸如图形、声音和传真等非文本数据。MIME定义于RFC1341,用MIMENCODE的方法将二进制数据转换成为一种被称为BASE64的ASCII子集的字符的组合。”

Internet上有专门讨论MIME的新闻组: comp.mail.mime。该新闻组的FAQ可以从下面的网点获得:

http://www.cis.ohio-state.edu/hypertext/faq/usenet/mail/mime-faq/mime0/faq.html

MIMENCODE最早称为MMENCODE,提出用MIMENCODE代替UUENCODE,是因为UUENCODE使用了一些字符在一些邮件网关(特别是那些转换ASCII和EBCDIC码的网关)中造成传输障碍,(还有一些软件不能对所有 UUENCODE 的算法进行正确解码而导致邮件的阅读困难),因此 MIME 被设计用于替代UUENCODE,但是结果是这些协议共存。

在MIME出台之前,使用RFC 822只能发送基本的ASCII码文本信息,邮件内容如果要包括二进制文件、声音和动画等,实现起来非常困难。

   MIME提供了一种可以在邮件中附加多种不同编码文件的方法,弥补了原来的信息格式的不足。实际上不仅仅是邮件编码,现在MIME经成为HTTP协议标准的一个部分。

二、MIME编码方式简介

对邮件进行编码最初的原因是因为 Internet 上的很多网关不能正确传输8bit内码的字符,比如汉字等。编码的原理就是把8bit的内容转换成7bit的形式以能正确传输,在接收方收到之后,再将其还原成8bit的内容。

在MIME协议之前,邮件的编码曾经有过UUENCODE等编码方式 ,但是由于MIME协议算法简单,并且易于扩展,现在已经成为邮件编码方式的主流,不仅是用来传输8bit的字符,也可以用来传送二进制的文件,如邮件附件中的图像、音频等信息,而且扩展了很多基于MIME 的应用。从编码方式来说,MIME定义了两种编码方法Base64与QP(Quote-Printable)。

1.Base64编码

Base64是一种通用的方法,其原理很简单,就是把三个Byte的数据用4个Byte表示。在这四个Byte中,实际用到的都只有前面6bit,这样就不存在只能传输7bit的字符的问题了。Base64的缩写一般是“B”。

   Base64将输入的字符串或一段数据编码成只含有{'A'-'Z', 'a'-'z', '0'-'9', '+', '/'}这64个字符的串,'='用于填充。

   其编码的方法是,将输入数据流每次取6bit,用此6bit的值(0-63)作为索引去查表,输出相应字符。

这样,每3个字节将编码为4个字符(3×8 → 4×6);不满4个字符的以'='填充。

   有的场合,以“=?charset?B?xxxxxxxx?=”表示xxxxxxxx是Base64编码,且原文的字符集是charset。在段体内则直接编码,适当时机换行,MIME建议每行最多76个字符。

   Base64的算法很简单,它将字符流顺序放入一个24位的缓冲区,缺字符的地方补零。

   然后将缓冲区截断成为4个部分,高位在先,每个部分6位,用64个字符重新表示。如果输入只有一个或两个字节,那么输出将用等号“=”补足。这可以隔断附加的信息造成编码的混乱。

 

如何进行base64编码
   Base64 使用US-ASCII子集的65个字符, 每个字符用6位表示
   对于文本串,编码过程如下。例如"men":
   先转成US-ASCII值.

   "m"十进制 109
   "e"十进制 101
   "n"十进制 110
   二进制 :
   m 01101101
   e 01100101
   n 01101110

   三个8位连起来是24位
   011011010110010101101110

   然后分成4个6位
   011011 010110 010101 101110

   现在得到4个值,十进制为
   27 22 21 46

  对应的 Base64 字符是 : b W V u
  编码总是基于3个字符,从而产生4个Base64字符。

  如果只是2个字符的数据,使用特殊字符"="补齐Base64的4字。
  如,编码"me"
  01101101 01100101
  0110110101100101
  011011 010110 0101
  111111 (与,补足6位)
  011011 010110 010100
  b W U
  b W U = ("=" 补足4字符)
  于是 "bWU=" 就是"me"的Base64值.

  如果只是2个字符的数据,如编码 "m"
  01101101
  011011 01
  111111
  011011 010000
  b Q = =
于是 "bQ==" 就是"m"的Base64值.

 

2.QP编码

另一种方法是QP(Quote-Printable) 方法,通常缩写为“Q”方法,其原理是把一个8bit的字符用两个16进制数值表示,然后在前面加“=”。所以我们看到经过QP编码后的文件通常是这个样子:=B3=C2=BF=A1=C7=E5=A3= AC=C4=FA=BA=C3=A3=A1。

    Quoted -printable根据输入的字符串或字节范围进行编码,若是不需编码的字符,直接输出。若需要编码,则先输出'=',后面跟着以2个字符表示的十六进制字节值。有的场合,以“=?charset?Q?xxxxxxxx?=”表示xxxxxxxx是Quoted-printable编码,且原文的字符集是charset。在段体内则直接编码,适当时机换行,换行前额外输出一个'='。

三、MIME的头信息

邮件头

在邮件头中,有很多从RFC 822沿用的域名,MIME也增加了一些。常见的标准域名和含义如下:

域名 含义 添加者

   Received                     传输路径 各级邮件服务器

   Return-Path                  回复地址           目标邮件服务器

   Delivered-To                 发送地址           目标邮件服务器

   Reply-To                     回复地址 邮件的创建者

   From                         发件人地址 邮件的创建者

   To                           收件人地址       邮件的创建者

   Cc                           抄送地址           邮件的创建者

   Bcc                          暗送地址 邮件的创建者

   Date                         日期和时间 邮件的创建者

   Subject                      主题              邮件的创建者

   Message-ID                   消息ID            邮件的创建者

   MIME-Version                 MIME版本 邮件的创建者

   Content-Type                 内容的类型 邮件的创建者

   Content-Transfer-Encoding    内容的传输编码方式 邮件的创建者

非标准的、自定义域名都以X-开头,例如X-Mailer, X-MSMail-Priority等,通常在接收和发送邮件的是同一程序时才能理解它们的意义。

段头

在段头中,大致有如下一些域:

域名 含义

   Content-Type                     段体的类型

   Content-Transfer-Encoding        段体的传输编码方式

   Content-Disposition              段体的安排方式

   Content-ID                       段体的ID

   Content-Location                 段体的位置(路径)

   Content-Base                     段体的基位置

有的域除了值之外,还带有参数。值与参数、参数与参数之间以“;”分隔。参数名与参数值之间以“=”分隔。

1.MIME-Version

表示使用的MIME的版本号,一般是1.0;

如:

    MIME-Version: 1.0

2.Content-Type

   Content-Type定义了正文的类型,我们实际上是通过这个标识来知道正文内是什么类型的文件。比如:text/plain 表示的是无格式的文本正文,text/html 表示的 Html 文档,image/gif 表示的是 gif 格式的图片等等。Content-Type都是“主类型/子类型”的形式。主类型有text, image, audio, video, application, multipart, message等,分别表示文本、图片、音频、视频、应用、分段、消息等。每个主类型都可能有多个子类型,如text类型就包含plain, html, xml, css等子类型。以X-开头的主类型和子类型,同样表示自定义的类型,未向IANA正式注册,但大多已经约定成俗了。如application/x-zip-compressed是ZIP文件类型。在Windows中,注册表的“HKEY_CLASSES_ROOT\MIME\Database\Content Type”内列举了除multipart之外大部分已知的Content-Type。

关于参数的形式,RFC里有很多补充规定,有的允许带几个参数,较为常见的有:

主类型 参数名 含义

   text           charset        字符集

   image          name           名称

   application    name           名称

   multipart      boundary       边界

multipart类型

邮件中常用到的复合类型:multipart。

multipart类型表示正文是由多个部分组成的,后面的子类型说明的是这些部分之间的关系。

邮件中用到的三个类型有:

(1).multipart/alternative:表示正文由两个部分组成,可以选择其中的任意一个。主要作用是在征文同时有text格式和html格式时,可以在两个正文中选择一个来显示,支持 html 格式的邮件客户端软件一般会显示其 HTML 正文,而不支持的则会显示其Text正文;

(2).multipart/mixed:表示文档的多个部分是混合的,指正文与附件的关系。如果邮件的MIME类型是multipart/mixed,即表示邮件带有附件。

(3).multipart/related:表示文档的多个部分是相关的,一般用来描述 Html 正文与其相关的图片。

multipart类型,是MIME邮件的精髓。邮件体被分为多个段,每个段又包含段头和段体两部分,这两部分之间也以空行分隔。它们之间的层次关系可归纳为下图所示:

     +------------------------- multipart/mixed ----------------------------+

     |                                                                      |

     | +----------------- multipart/related ------------------+            |

     | |                                                      |            |

     | | +----- multipart/alternative ------+ +----------+ | +------+ |

     | | |                                  | | 内嵌资源 | | | 附件 | |

     | | | +------------+ +------------+ | +----------+ | +------+ |

     | | | | 纯文本正文 | | 超文本正文 | |                |             |

     | | | +------------+ +------------+ | +----------+ | +------+ |

     | | |                                  | | 内嵌资源 | | | 附件 | |

     | | +----------------------------------+ +----------+ | +------+ |

     | |                                                      |            |

     | +------------------------------------------------------+            |

     |                                                                      |

     +----------------------------------------------------------------------+

   可以看出,如果在邮件中要添加附件,必须定义multipart/mixed段;如果存在内嵌资源,至少要定义multipart/related段;如果纯文本与超文本共存,至少要定义multipart/alternative段。什么是“至少”?举个例子说,如果只有纯文本与超文本正文,那么在邮件头中将类型扩大化,定义为multipart/related,甚至multipart/mixed,都是允许的。

   multipart诸类型的共同特征是,在段头指定“boundary”参数字符串,段体内的每个子段以此串定界。所有的子段都以“--”+boundary行开始,父段则以“--”+boundary+“--”行结束。段与段之间也以空行分隔。在邮件体是multipart类型的情况下,邮件体的开始部分(第一个“--” +boundary行之前)可以有一些附加的文本行,相当于注释,解码时应忽略。段间也可以有一些附加的文本行,不会显示出来。

这些复合类型又是可以嵌套使用的,比如说一个带有附件的邮件,同时有html与text两种格式的正文,则邮件的结构是:

Content-Type: multipart/mixed

部分一:

Content Type : multipart/alternative:

Text 正文;

Html 格式的正文

部分二:

附件

邮件结束符;

由于复合类型由多个部分组成,因此,需要一个分隔符来分隔这多个部分,这就是上面的邮件源文件中的boundary所描述的,对于每一个Contect type :multipart/* 的内容,都会有这么一个说明,表示多个部分之间的分隔。

含有 MIME/BASE64编码的邮件,你查看它的源码时一般都含有:“This is a multi-part message in MIME format.”这样的句子。也可以被绝大多数的email程序进行解码,包括Netscape、MS Mail、Eudora等。这些程序可以正确识别邮件的正文,恢 MIME/BASE64 编码的部分为正确的文字或夹带的二进制文件。

3.Content-Transfer-Encoding

它表示了这个部分文档的编码方式。只有识别了这个说明,才能用正确的解码方式实现对其解码。

Content-Transfer-Encoding共有Base64, Quoted-printable, 7bit, 8bit, Binary等几种。

其中7bit是缺省的编码方式。电子邮件源码最初设计为全部是可打印的ASCII码的形式。

非ASCII码的文本或数据要编码成要求的格式。

Base64, Quoted-Printable是在非英语国家使用最广使的编码方式。

Binary方式只具有象征意义,而没有任何实用价值。

4.boundary

这个分隔符是正文中不可能出现的一串古字符的组合,在文档中,以"--"加上这个boundary 来表示一个部分的开始,在文档的结束,以"--"加boundary再在最后加上"--"来表示文档的结束。由于复合类型是可以嵌套使用的,因此,邮件中可能会多个boundary。

posted @ 2010-03-11 14:39 绿化树 阅读(344) 评论(0) 编辑

UNICODE:它是用两个字节表示一个字符的方法。比如字符'A'在ASCII下面是一个字符,可'A'在UNICODE下面是两个字符,高字符用0填充,而且汉字'程'在ASCII下面是两个字节,而在UNICODE下仍旧是两个字节。UNICODE的用处就是定长表示世界文字,据统计,用两个字节可以编码现存的所有文字而没有二义。   MBCS,它是多字节字符集,它是不定长表示世界文字的编码。MBCS表示英文字母时就和ASCII一样(这也是我们容易把MBCS和ASCII搞混的原因),但表示其他文字时就需要用多字节。   WINDOWS下面的程序设计可以支持MBCS和UNICODE两种编码的字符串,具体用那种就看你定义了MBCS宏还是UNICODE宏。MBCS宏对应的字符串指针是char*也就是LPSTR,UNICODE对应的指针是unsigned   short*也就是LPWSTR,为了写程序方便微软定义了类型LPTSTR,在MBCS下他就是char*,   在UNICODE下它是unsigned   char*,这样你就可以重定义一个宏进行不同字符集的转换了。 LPTSTR、LPCSTR、LPCTSTR、LPSTR的意义:

LPSTR:32bit指针 指向一个字符串,每个字符占1字节

LPCSTR:32-bit指针 指向一个常字符串,每个字符占1字节
LPCTSTR:32-bit指针 指向一个常字符串,每字符可能占1字节或2字节,取决于Unicode是否定义
LPTSTR:32-bit指针 每字符可能占1字节或2字节,取决于Unicode是否定义

Windows使用两种字符集ANSI和UNICODE,前者就是通常使用的单字节方式,但这种方式处理象中文这样的双字节字符不方便,容易出现半个汉字的情况。而后者是双字节方式,方便处理双字节字符。WindowsNT的所有与字符有关的函数都提供两种方式的版本,而Windows9x只支持ANSI方式。_T一般同字常数相关,如_T("Hello"。如果你编译一个程序为ANSI方式,_T实际不起任何作用。而如果编译一个程序为UNICODE方式,则编译器会把"Hello"字符串以UNICODE方式保存。_T和_L的区别在于,_L不管你是以什么方式编译,一律以UNICODE方式保存.

 Windows核心编程的第一章。

L是表示字符串资源为Unicode的。

比如
wchar_t Str[] = L"Hello World!";
这个就是双子节存储字符了。

_T是一个适配的宏~


#ifdef _UNICODE的时候
_T就是L
没有#ifdef _UNICODE的时候
_T就是ANSI的。

比如

LPTSTR lpStr = new TCHAR[32];
TCHAR* szBuf = _T("Hello");
以上两句使得无论是在UNICODE编译条件下都是正确编译的。

而且MS推荐你使用相匹配的字符串函数。
比如处理LPTSTR或者LPCTSTR 的时候,不要用strlen ,而是要用_tcslen

否则在UNICODE的编译条件下,strlen不能处理 wchar_t*的字符串。

T是非常有意思的一个符号(TCHAR、LPCTSTR、LPTSTR、_T()、_TEXT()...),它表示使用一种中间类型,既不明确表示使用 MBCS,也不明确表示使用 UNICODE。那到底使用哪种字符集?编译的时候才决定


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/douzixinxin/archive/2006/07/03/869937.aspx

posted @ 2010-03-11 12:55 绿化树 阅读(181) 评论(0) 编辑

 [原创文章,转载请保留或注明出处:http://www.regexlab.com/zh/encoding.htm]

级别:中级

摘要:本文介绍了字符与编码的发展过程,相关概念的正确理解。举例说明了一些实际应用中,编码的实现方法。然后,本文讲述了通常对字符与编码的几种误解,由于这些误解而导致乱码产生的原因,以及消除乱码的办法。本文的内容涵盖了“中文问题”,“乱码问题”。

掌握编码问题的关键是正确地理解相关概念,编码所涉及的技术其实是很简单的。因此,阅读本文时需要慢读多想,多思考。

引言

“字符与编码”是一个被经常讨论的话题。即使这样,时常出现的乱码仍然困扰着大家。虽然我们有很多的办法可以用来消除乱码,但我们并不一定理解这些办法的内在原理。而有的乱码产生的原因,实际上由于底层代码本身有问题所导致的。因此,不仅是初学者会对字符编码感到模糊,有的底层开发人员同样对字符编码缺乏准确的理解。

回页首

1. 编码问题的由来,相关概念的理解

1.1 字符与编码的发展

从计算机对多国语言的支持角度看,大致可以分为三个阶段:

  系统内码 说明 系统
阶段一 ASCII 计算机刚开始只支持英语,其它语言不能够在计算机上存储和显示。 英文 DOS
阶段二 ANSI编码
(本地化)
为使计算机支持更多语言,通常使用 0x80~0xFF 范围的 2 个字节来表示 1 个字符。比如:汉字 '中' 在中文操作系统中,使用 [0xD6,0xD0] 这两个字节存储。

不同的国家和地区制定了不同的标准,由此产生了 GB2312, BIG5, JIS 等各自的编码标准。这些使用 2 个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码。在简体中文系统下,ANSI 编码代表 GB2312 编码,在日文操作系统下,ANSI 编码代表 JIS 编码。

不同 ANSI 编码之间互不兼容,当信息在国际间交流时,无法将属于两种语言的文字,存储在同一段 ANSI 编码的文本中。
中文 DOS,中文 Windows 95/98,日文 Windows 95/98
阶段三 UNICODE
(国际化)
为了使国际间信息交流更加方便,国际组织制定了 UNICODE 字符集,为各种语言中的每一个字符设定了统一并且唯一的数字编号,以满足跨语言、跨平台进行文本转换、处理的要求。 Windows NT/2000/XP,Linux,Java

字符串在内存中的存放方法:

在 ASCII 阶段,单字节字符串使用一个字节存放一个字符(SBCS)。比如,"Bob123" 在内存中为:

42 6F 62 31 32 33 00
B o b 1 2 3 \0

在使用 ANSI 编码支持多种语言阶段,每个字符使用一个字节或多个字节来表示(MBCS),因此,这种方式存放的字符也被称作多字节字符。比如,"中文123" 在中文 Windows 95 内存中为7个字节,每个汉字占2个字节,每个英文和数字字符占1个字节:

D6 D0 CE C4 31 32 33 00
1 2 3 \0

在 UNICODE 被采用之后,计算机存放字符串时,改为存放每个字符在 UNICODE 字符集中的序号。目前计算机一般使用 2 个字节(16 位)来存放一个序号(DBCS),因此,这种方式存放的字符也被称作宽字节字符。比如,字符串 "中文123" 在 Windows 2000 下,内存中实际存放的是 5 个序号:

2D 4E 87 65 31 00 32 00 33 00 00 00      ← 在 x86 CPU 中,低字节在前
1 2 3 \0  

一共占 10 个字节。

回页首

1.2 字符,字节,字符串

理解编码的关键,是要把字符的概念和字节的概念理解准确。这两个概念容易混淆,我们在此做一下区分:

  概念描述 举例
字符 人们使用的记号,抽象意义上的一个符号。 '1', '中', 'a', '$', '¥', ……
字节 计算机中存储数据的单元,一个8位的二进制数,是一个很具体的存储空间。 0x01, 0x45, 0xFA, ……
ANSI
字符串
在内存中,如果“字符”是以 ANSI 编码形式存在的,一个字符可能使用一个字节或多个字节来表示,那么我们称这种字符串为 ANSI 字符串或者多字节字符串 "中文123"
(占7字节)
UNICODE
字符串
在内存中,如果“字符”是以在 UNICODE 中的序号存在的,那么我们称这种字符串为 UNICODE 字符串或者宽字节字符串 L"中文123"
(占10字节)

由于不同 ANSI 编码所规定的标准是不相同的,因此,对于一个给定的多字节字符串,我们必须知道它采用的是哪一种编码规则,才能够知道它包含了哪些“字符”。而对于 UNICODE 字符串来说,不管在什么环境下,它所代表的“字符”内容总是不变的。

回页首

1.3 字符集与编码

各个国家和地区所制定的不同 ANSI 编码标准中,都只规定了各自语言所需的“字符”。比如:汉字标准(GB2312)中没有规定韩国语字符怎样存储。这些 ANSI 编码标准所规定的内容包含两层含义:

  1. 使用哪些字符。也就是说哪些汉字,字母和符号会被收入标准中。所包含“字符”的集合就叫做“字符集”。
  2. 规定每个“字符”分别用一个字节还是多个字节存储,用哪些字节来存储,这个规定就叫做“编码”。

各个国家和地区在制定编码标准的时候,“字符的集合”和“编码”一般都是同时制定的。因此,平常我们所说的“字符集”,比如:GB2312, GBK, JIS 等,除了有“字符的集合”这层含义外,同时也包含了“编码”的含义。

UNICODE 字符集”包含了各种语言中使用到的所有“字符”。用来给 UNICODE 字符集编码的标准有很多种,比如:UTF-8, UTF-7, UTF-16, UnicodeLittle, UnicodeBig 等。

回页首

1.4 常用的编码简介

简单介绍一下常用的编码规则,为后边的章节做一个准备。在这里,我们根据编码规则的特点,把所有的编码分成三类:

分类 编码标准 说明
单字节字符编码 ISO-8859-1 最简单的编码规则,每一个字节直接作为一个 UNICODE 字符。比如,[0xD6, 0xD0] 这两个字节,通过 iso-8859-1 转化为字符串时,将直接得到 [0x00D6, 0x00D0] 两个 UNICODE 字符,即 "ÖÐ"。

反之,将 UNICODE 字符串通过 iso-8859-1 转化为字节串时,只能正常转化 0~255 范围的字符。
ANSI 编码 GB2312,
BIG5,
Shift_JIS,
ISO-8859-2 ……
把 UNICODE 字符串通过 ANSI 编码转化为“字节串”时,根据各自编码的规定,一个 UNICODE 字符可能转化成一个字节或多个字节。

反之,将字节串转化成字符串时,也可能多个字节转化成一个字符。比如,[0xD6, 0xD0] 这两个字节,通过 GB2312 转化为字符串时,将得到 [0x4E2D] 一个字符,即 '中' 字。

“ANSI 编码”的特点:
1. 这些“ANSI 编码标准”都只能处理各自语言范围之内的 UNICODE 字符。
2. “UNICODE 字符”与“转换出来的字节”之间的关系是人为规定的。
UNICODE 编码 UTF-8,
UTF-16, UnicodeBig ……
与“ANSI 编码”类似的,把字符串通过 UNICODE 编码转化成“字节串”时,一个 UNICODE 字符可能转化成一个字节或多个字节。

与“ANSI 编码”不同的是:
1. 这些“UNICODE 编码”能够处理所有的 UNICODE 字符。
2. “UNICODE 字符”与“转换出来的字节”之间是可以通过计算得到的。

我们实际上没有必要去深究每一种编码具体把某一个字符编码成了哪几个字节,我们只需要知道“编码”的概念就是把“字符”转化成“字节”就可以了。对于“UNICODE 编码”,由于它们是可以通过计算得到的,因此,在特殊的场合,我们可以去了解某一种“UNICODE 编码”是怎样的规则。

回页首

2. 字符与编码在程序中的实现

2.1 程序中的字符与字节

在 C++ 和 Java 中,用来代表“字符”和“字节”的数据类型,以及进行编码的方法:

类型或操作 C++ Java
字符 wchar_t char
字节 char byte
ANSI 字符串 char[] byte[]
UNICODE 字符串 wchar_t[] String
字节串→字符串 mbstowcs(), MultiByteToWideChar() string = new String(bytes, "encoding")
字符串→字节串 wcstombs(), WideCharToMultiByte() bytes = string.getBytes("encoding")

以上需要注意几点:

  1. Java 中的 char 代表一个“UNICODE 字符(宽字节字符)”,而 C++ 中的 char 代表一个字节。
  2. MultiByteToWideChar() 和 WideCharToMultiByte() 是 Windows API 函数。

回页首

2.2 C++ 中相关实现方法

声明一段字符串常量:

// ANSI 字符串,内容长度 7 字节
char
     sz[20] = "中文123";

// UNICODE 字符串,内容长度 5 个 wchar_t(10 字节)
wchar_t wsz[20] = L"\x4E2D\x6587\x0031\x0032\x0033";

UNICODE 字符串的 I/O 操作,字符与字节的转换操作:

// 运行时设定当前 ANSI 编码,VC 格式
setlocale(LC_ALL, ".936");

// GCC 中格式
setlocale(LC_ALL, "zh_CN.GBK");

// Visual C++ 中使用小写 %s,按照 setlocale 指定编码输出到文件
// GCC 中使用大写 %S

fwprintf(fp, L"%s\n", wsz);

// 把 UNICODE 字符串按照 setlocale 指定的编码转换成字节
wcstombs(sz, wsz, 20);
// 把字节串按照 setlocale 指定的编码转换成 UNICODE 字符串
mbstowcs(wsz, sz, 20);

在 Visual C++ 中,UNICODE 字符串常量有更简单的表示方法。如果源程序的编码与当前默认 ANSI 编码不符,则需要使用 #pragma setlocale,告诉编译器源程序使用的编码:

// 如果源程序的编码与当前默认 ANSI 编码不一致,
// 则需要此行,编译时用来指明当前源程序使用的编码

#pragma setlocale
(".936")

// UNICODE 字符串常量,内容长度 10 字节
wchar_t wsz[20] = L"中文123";

以上需要注意 #pragma setlocale 与 setlocale(LC_ALL, "") 的作用是不同的,#pragma setlocale 在编译时起作用,setlocale() 在运行时起作用。

回页首

2.3 Java 中相关实现方法

字符串类 String 中的内容是 UNICODE 字符串:

// Java 代码,直接写中文
String
string = "中文123";

// 得到长度为 5,因为是 5 个字符
System.out.println(string.length());

字符串 I/O 操作,字符与字节转换操作。在 Java 包 java.io.* 中,以“Stream”结尾的类一般是用来操作“字节串”的类,以“Reader”,“Writer”结尾的类一般是用来操作“字符串”的类。

// 字符串与字节串间相互转化

// 按照 GB2312 得到字节(得到多字节字符串)

byte
[] bytes = string.getBytes("GB2312");

// 从字节按照 GB2312 得到 UNICODE 字符串
string = new String(bytes, "GB2312");

// 要将 String 按照某种编码写入文本文件,有两种方法:

// 第一种办法:用 Stream 类写入已经按照指定编码转化好的字节串

OutputStream os = new FileOutputStream("1.txt");
os.write(bytes);
os.close();

// 第二种办法:构造指定编码的 Writer 来写入字符串
Writer ow = new OutputStreamWriter(new FileOutputStream("2.txt"), "GB2312");
ow.write(string);
ow.close();

/* 最后得到的 1.txt 和 2.txt 都是 7 个字节 */

如果 java 的源程序编码与当前默认 ANSI 编码不符,则在编译的时候,需要指明一下源程序的编码。比如:

E:\>javac -encoding BIG5 Hello.java

以上需要注意区分源程序的编码与 I/O 操作的编码,前者是在编译时起作用,后者是在运行时起作用。

回页首

3. 几种误解,以及乱码产生的原因和解决办法

3.1 容易产生的误解
  对编码的误解
误解一 在将“字节串”转化成“UNICODE 字符串”时,比如在读取文本文件时,或者通过网络传输文本时,容易将“字节串”简单地作为单字节字符串,采用每“一个字节”就是“一个字符”的方法进行转化。

而实际上,在非英文的环境中,应该将“字节串”作为 ANSI 字符串,采用适当的编码来得到 UNICODE 字符串,有可能“多个字节”才能得到“一个字符”。

通常,一直在英文环境下做开发的程序员们,容易有这种误解。
误解二 在 DOS,Windows 98 等非 UNICODE 环境下,字符串都是以 ANSI 编码的字节形式存在的。这种以字节形式存在的字符串,必须知道是哪种编码才能被正确地使用。这使我们形成了一个惯性思维:“字符串的编码”。

当 UNICODE 被支持后,Java 中的 String 是以字符的“序号”来存储的,不是以“某种编码的字节”来存储的,因此已经不存在“字符串的编码”这个概念了。只有在“字符串”与“字节串”转化时,或者,将一个“字节串”当成一个 ANSI 字符串时,才有编码的概念。

不少的人都有这个误解。

第一种误解,往往是导致乱码产生的原因。第二种误解,往往导致本来容易纠正的乱码问题变得更复杂。

在这里,我们可以看到,其中所讲的“误解一”,即采用每“一个字节”就是“一个字符”的转化方法,实际上也就等同于采用 iso-8859-1 进行转化。因此,我们常常使用 bytes = string.getBytes("iso-8859-1") 来进行逆向操作,得到原始的“字节串”。然后再使用正确的 ANSI 编码,比如 string = new String(bytes, "GB2312"),来得到正确的“UNICODE 字符串”。

回页首

3.2 非 UNICODE 程序在不同语言环境间移植时的乱码

非 UNICODE 程序中的字符串,都是以某种 ANSI 编码形式存在的。如果程序运行时的语言环境与开发时的语言环境不同,将会导致 ANSI 字符串的显示失败。

比如,在日文环境下开发的非 UNICODE 的日文程序界面,拿到中文环境下运行时,界面上将显示乱码。如果这个日文程序界面改为采用 UNICODE 来记录字符串,那么当在中文环境下运行时,界面上将可以显示正常的日文。

由于客观原因,有时候我们必须在中文操作系统下运行非 UNICODE 的日文软件,这时我们可以采用一些工具,比如,南极星,AppLocale 等,暂时的模拟不同的语言环境。

回页首

3.3 网页提交字符串

当页面中的表单提交字符串时,首先把字符串按照当前页面的编码,转化成字节串。然后再将每个字节转化成 "%XX" 的格式提交到 Web 服务器。比如,一个编码为 GB2312 的页面,提交 "中" 这个字符串时,提交给服务器的内容为 "%D6%D0"。

在服务器端,Web 服务器把收到的 "%D6%D0" 转化成 [0xD6, 0xD0] 两个字节,然后再根据 GB2312 编码规则得到 "中" 字。

在 Tomcat 服务器中,request.getParameter() 得到乱码时,常常是因为前面提到的“误解一”造成的。默认情况下,当提交 "%D6%D0" 给 Tomcat 服务器时,request.getParameter() 将返回 [0x00D6, 0x00D0] 两个 UNICODE 字符,而不是返回一个 "中" 字符。因此,我们需要使用 bytes = string.getBytes("iso-8859-1") 得到原始的字节串,再用 string = new String(bytes, "GB2312") 重新得到正确的字符串 "中"。

回页首

3.4 从数据库读取字符串

通过数据库客户端(比如 ODBC 或 JDBC)从数据库服务器中读取字符串时,客户端需要从服务器获知所使用的 ANSI 编码。当数据库服务器发送字节流给客户端时,客户端负责将字节流按照正确的编码转化成 UNICODE 字符串。

如果从数据库读取字符串时得到乱码,而数据库中存放的数据又是正确的,那么往往还是因为前面提到的“误解一”造成的。解决的办法还是通过 string = new String( string.getBytes("iso-8859-1"), "GB2312") 的方法,重新得到原始的字节串,再重新使用正确的编码转化成字符串。

回页首

3.5 电子邮件中的字符串

当一段 Text 或者 HTML 通过电子邮件传送时,发送的内容首先通过一种指定的字符编码转化成“字节串”,然后再把“字节串”通过一种指定的传输编码(Content-Transfer-Encoding)进行转化得到另一串“字节串”。比如,打开一封电子邮件源代码,可以看到类似的内容:

Content-Type: text/plain;
        charset="gb2312"
Content-Transfer-Encoding: base64

sbG+qcrQuqO17cf4yee74bGjz9W7+b3wudzA7dbQ0MQNCg0KvPKzxqO6uqO17cnnsaPW0NDEDQoNCg==

最常用的 Content-Transfer-Encoding 有 Base64 和 Quoted-Printable 两种。在对二进制文件或者中文文本进行转化时,Base64 得到的“字节串”比 Quoted-Printable 更短。在对英文文本进行转化时,Quoted-Printable 得到的“字节串”比 Base64 更短。

邮件的标题,用了一种更简短的格式来标注“字符编码”和“传输编码”。比如,标题内容为 "中",则在邮件源代码中表示为:

// 正确的标题格式
Subject: =?GB2312?B?1tA=?=

其中,

  • 第一个“=?”与“?”中间的部分指定了字符编码,在这个例子中指定的是 GB2312。
  • “?”与“?”中间的“B”代表 Base64。如果是“Q”则代表 Quoted-Printable。
  • 最后“?”与“?=”之间的部分,就是经过 GB2312 转化成字节串,再经过 Base64 转化后的标题内容。

如果“传输编码”改为 Quoted-Printable,同样,如果标题内容为 "中":

// 正确的标题格式
Subject: =?GB2312?Q?=D6=D0?=

如果阅读邮件时出现乱码,一般是因为“字符编码”或“传输编码”指定有误,或者是没有指定。比如,有的发邮件组件在发送邮件时,标题 "中":

// 错误的标题格式
Subject: =?ISO-8859-1?Q?=D6=D0?=

这样的表示,实际上是明确指明了标题为 [0x00D6, 0x00D0],即 "ÖÐ",而不是 "中"。

回页首

4. 几种错误理解的纠正

误解:“ISO-8859-1 是国际编码?”

非也。iso-8859-1 只是单字节字符集中最简单的一种,也就是“字节编号”与“UNICODE 字符编号”一致的那种编码规则。当我们要把一个“字节串”转化成“字符串”,而又不知道它是哪一种 ANSI 编码时,先暂时地把“每一个字节”作为“一个字符”进行转化,不会造成信息丢失。然后再使用 bytes = string.getBytes("iso-8859-1") 的方法可恢复到原始的字节串。

误解:“Java 中,怎样知道某个字符串的内码?”

Java 中,字符串类 java.lang.String 处理的是 UNICODE 字符串,不是 ANSI 字符串。我们只需要把字符串作为“抽象的符号的串”来看待。因此不存在字符串的内码的问题。

posted @ 2010-03-11 12:50 绿化树 阅读(123) 评论(0) 编辑