windows programming 中的 main()(经典)

学过 c 语言的都知道,main() 是用户代码的入口函数(当然这里要排除一些修改入口函数的手段),在 unix/linux 下依然是这样。可是到了 windows 下情况就搞得很复杂,特别是 windows NT 时代以后, NT 架构从内核上完全支持 Unicode 码,这是一种 16 位的宽字符集,可以表达更多的字符,这当然是件好事。可是由于 kernel 里使用 Unicode 来表达字符,而 user 层的代码往往只使用 ANSI 字符, 这样就产生一些问题。

内核或程序员需要将 ANSI 转化为 Unicode 进行处理,windows 下的 Win32 API 大多数字符处理函数都分为 ANSI 字符和 Wide 字符版本。程序员需要区分和使用相应的版本,我所知道的:在一个程序代码里混用两种字符集, 或多或少会产生些问题。如果在用户代码中使用 ANSI 字符集,那么 kernel 会转化为 Unicode 字符进行处理。

那么,现在我想表达的是选择什么样的入口函数,决定你使用什么样的字符集。

1. windows 下各种各样的用户代码入口函数

用户代码的入口函数分 console 和 GUI 两大类,这两类又有 ANSI 和 UNICODE 版本之分:

  • console 下 ANSI 版本的 main() 以及对应的 UNICODE 版本的 wmain()
  • GUI 下 ANSI 版本的 WinMain() 以及对应的 UNICODE 版本的 wWinMain()

可是还没完,实际情况会让人更加懊恼和迷惑,Microsoft 的 Visual studio C/C++ Project Wizard 会为用户生成的入口函数是:

  • console 下是 _tmain(),而不是 main() 或 wmain()
  • GUI 下是 _tWinMain(),而不是 WinMain() 或 wWinMain()

_tmain 和 _tWinMain 只不过是一个宏,根据 _UNICODE 宏的定义而选择相应的定义。

1.1 _tmain 和 _tWinMain 宏的定义

在 VC 的 C 运行时库头文件 tchar.h 和 VC 标准库头文件 tchar.h 里对 _tmain 进行定义:

#ifdef _UNICODE


#ifdef __cplusplus
}   /* ... extern "C" */
#endif  /* __cplusplus */

/* ++++++++++++++++++++ UNICODE ++++++++++++++++++++ */

#include 

#ifdef __cplusplus
extern "C" {
#endif  /* __cplusplus */

#ifndef _WCTYPE_T_DEFINED
typedef unsigned short wint_t;
typedef unsigned short wctype_t;
#define _WCTYPE_T_DEFINED
#endif  /* _WCTYPE_T_DEFINED */

#ifndef __TCHAR_DEFINED
typedef wchar_t     _TCHAR;
typedef wchar_t     _TSCHAR;
typedef wchar_t     _TUCHAR;
typedef wchar_t     _TXCHAR;
typedef wint_t      _TINT;
#define __TCHAR_DEFINED
#endif  /* __TCHAR_DEFINED */

#ifndef _TCHAR_DEFINED
#if !__STDC__
typedef wchar_t     TCHAR;
#endif  /* !__STDC__ */
#define _TCHAR_DEFINED
#endif  /* _TCHAR_DEFINED */

#define _TEOF       WEOF

#define __T(x)      L ## x


/* Program */

#define _tmain      wmain
#define _tWinMain   wWinMain

#define _tenviron   _wenviron
#define __targv     __wargv

当定义了用户代码使用 UNICODE 字符集(定义了 _UNICODE 宏),那么就链接到 wmain() 或 wWinMain(),否则链接到 main() 或 WinMain()

#else   /* ndef _UNICODE */

/* ++++++++++++++++++++ SBCS and MBCS ++++++++++++++++++++ */

#ifdef  __cplusplus
}   /* ... extern "C" */
#endif

#include 

#ifdef  __cplusplus
extern "C" {
#endif


#define _TEOF       EOF

#define __T(x)      x


/* Program */

#define _tmain      main
#define _tWinMain   WinMain

1.2 _tmain() 和 _tWinMain() 的扩展形式

下面是 _tmain() 的常见定义形式:

int _tmain(int argc, _TCHAR* argv[])
{
}


int _tmain(int argc, TCHAR* argv[])
{
}


int _tmain(int argc, LPTSTR argv[])
{
}

它们实际上都是同一个定义,当定义了 UNICODE 宏时,会扩展为:

int wmain(int argc, wchar_t *argv[])
{
}

否则,使用 ANSI 形式:

int main(int argc, char *argv[])
{
}

而 _tWinMain() 的处理手法与 _tmain() 是一样的。

2. 混用时的问题

当明确使用 ANSI C 写程序时,应该使用 main() 形式,正如下面的代码所示:

#include "stdafx.h"
#include  < errno.h>

#define BUF_SIZE         256

int main(int argc, char* argv[])
{
         FILE *inFile, *outFile;
         char rec[BUF_SIZE];
         size_t bytesIn, bytesOut;

         if (argc != 3)
         {
                  printf("Usage: cp file1 file2\n");
                  return 1;
         }

         /* open file for read */
         inFile = fopen(argv[1], "rb");
         if (inFile == NULL)
         {
                  perror(argv[1]);
                  return 2;
         }

         /* open file for write */
         outFile = fopen(argv[2], "wb");
         if (outFile == NULL)
         {
                  perror(argv[2]);
                  return 3;
         }

         /* copy file */
         while ((bytesIn = fread(rec, 1, BUF_SIZE, inFile)) > 0)
         {
                  bytesOut = fwrite(rec, 1, BUF_SIZE, outFile);
                  if (bytesIn != bytesOut)
                  {
                           perror("Fatal write error.");
                           return 4;
                  }
         }

         fclose(inFile);
         fclose(outFile);
         
         return 0;
}

这个程序是典型在 windows 下使用 ANSI C 进行编程的例子,使用了标准 C 库的文件操纵函数进行文件复制,这个程序是正确的。可是,如果你混用字符集的话,那就会出错了,像下面:

int main(int argc, TCHAR *argv[])                           /* 参数使用了 TCHAR * 类型 */
{
         ... ...
}

这里明确编译器使用 ANSI 字符集,可是其中一个参数却使用了 TCHAR * 类型,在没有定义 UNICODE 宏的前提下,这个函数还是可以工作的,但是 Windows NT 以后都定义了 UNICODE 宏,因此这个程序是不正确的。argv[] 数组参数读入的是 Wide 宽字符串,代码中却使用了 char 方式。 导致无法打开正确的文件。

另外,像下面的定义也是不正确的:

int wmain(int argc, char *argv[])                           /* 参数使用了 char * 类型 */
{
         ... ...
}

上面这个定义明确使用了 UNICODE 字符集,而其中一个参数却使用了 ANSI 字符集,显然也是不正确的。

当明确需要使用 ANSI C 编程时,应该使用 ANSI 字符集的定义形式,否则应该使用 _tmain() 形式,让编译器自己选择链接合适的版本。

3. 程序的入口

在 VC 的运行时库 crt0.c 文件里,定义了 4 个 C 运行时库启动例程:

  • mainCRTStartup()
  • wmainCRTStartup()
  • WinMainCRTStartup()
  • wWinMainCRTStartup()

它们都是由一个宏指引出来的:

#ifdef WPRFLAG
#define _tmainCRTStartup    wWinMainCRTStartup
#else  /* WPRFLAG */
#define _tmainCRTStartup    WinMainCRTStartup
#endif  /* WPRFLAG */

#else  /* _WINMAIN_ */

#ifdef WPRFLAG
#define _tmainCRTStartup    wmainCRTStartup
#else  /* WPRFLAG */
#define _tmainCRTStartup    mainCRTStartup
#endif  /* WPRFLAG */

#endif  /* _WINMAIN_ */

#ifdef WPRFLAG

编译器会根据用户代码的入口函数而做出相应的选择:

  • 当用户入口函数是 main() 时,链接到 mainCRTStartup()
  • 当用户入口函数是 wmain() 时,链接到 wmainCRTStartup()
  • 当用户入口函数是 WinMain() 时,链接到 WinMainCRTStartup()
  • 当用户入口函数是 wWinMain() 时,链接到 wWinMainCRTStartup()

在_tmainCRTStartup() 里又调用 __tmainCRTStartup() 最终调用用户代码的入口函数:

_tmainCRTStartup() --> __tmainCRTStartup() --> _tWinMain() 或 _tmain()

以 helloworld.exe 程序为例,如果 helloworld.exe 是 GUI 的 UNICODE 程序,那么调用流程是:

helloworld!wWinMainCRTStartup()                ==> _tmainCRTStartup() 被替换为 wWinMainCRTStartup()
               |
               |
               V
helloworld!__tmainCRTStartup()                   
               |
               |
               V
helloworld!wWinMain(HINSTANCE__ *, HINSTANCE__ *, wchar_t *, int)    ==> 调用用户代码入口函数 _tWinMain() 替换为 wWinMain()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值