让fopen打开在不同的代码页下创建的文件

本文探讨英文Win2000下应用程序操作含中文文件名文件出错的问题。原因是文件在中文Win2000创建后迁移,代码页不同致UNICODE转ANSI信息丢失。利用Win2000用ANSI保存短文件名的特性,通过API获取短文件名,经转换后可让fopen正常工作,并给出示例代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

让fopen打开在不同的代码页下创建的文件


问题:
    英文Win2000下,应用程序操作一个文件名中带中文字符的文件时出错。

分析:
    文件是在中文Win2000下创建好之后,再拷贝到英文Win2000的,初步认为这涉及到代码页的问题!
    应用程序使用了一个第三方的工具包,提供的接口需要一个ANSI编码的文件名,而不是UNICODE的。
    进一步的分析发现,工具包是用fopen()来打开文件的。
    于是,问题变成了如何让fopen在不同的代码页之间也能正常工作?
   
    要解决问题,必须先弄清楚问题产生的具体原因。
   
    查看文档,Win2000的文件系统在保存文件时,实际记录了文件的两个名字:长文件名和短文件名。而长文件名是按UNICODE保存的,短文件名则是按ANSI保存的!
    显然上面的操作都是基于长文件名的。
    那么问题可以这样来理解:

    中文Win2000的代码页是936,在这个代码页下的字符的ANSI编码和UNICODE编码是一一对应的。
    文件的长文件名是UNICODE,而fopen()需要ANSI作为文件名参数,所以如果你想使用fopen(),必然需要UNICODE和ANSI之间的转换,而这个转换可能是你来做也可能是系统来做。比如你使用GetOpenFileNameA()来得到文件名,那么转换就不用你关心了;如果你非要用GetOpenFileNameW()的话,那么你的代码的下一行可能就是WideCharToMultiByte()了。总之,fopen的使用不会有什么问题。

    在文件迁移到英文Win2000后,问题就出现了。

    英文Win2000的代码页是437。一个中文字符的UNICODE映射到437是没有任何意义的,这称为unmappable character。一般情况下,unmappable character的转换结果会是一个'?'。所以转换之后,文件名中原有的信息就丢失了。这正是英文Win2000下fopen不能打开中文文件的原因。

解决:

    前面提到了,Win2000的文件系统是用ANSI编码来保存短文件名的。我们可以利用它来解决问题。

    怎么得到短文件名呢?有一个API是做这事的:

    DWORD GetShortPathName(LPCTSTR lpszLongPath, LPTSTR lpszShortPath, DWORD cchBuffer );

    GetShortPathName()需要一个长文件名(其实是PATH)作为参数,这就意味着我们只能使用GetShortPathNameW(),那么我们得到的短文件名也是UNICODE。那这不是存在和长文件名一样的问题吗???

    注意到一个事实:一个ANSI字符串,在不同的代码页有不同的含义。但是,在任意一个代码页下,将ANSI转为UNICODE,然后再从UNICODE转回ANSI,这个字符串保持不变,不会丢失任何信息!!!
   
    所以,我们可以放心地把得到的短文件名用WideCharToMultiByte()转为ANSI,再交给fopen使用。

      

示例代码:

 FILE * file = NULL;

 wchar_t wsShortName[1000] = {0,};

 OPENFILENAMEW ofn;
 wchar_t szFile[MAX_PATH];

 // Initialize OPENFILENAME
 ZeroMemory(&ofn, sizeof(ofn));
 ofn.lStructSize = sizeof(ofn);
 ofn.hwndOwner = NULL;
 ofn.lpstrFile = szFile;
 ofn.lpstrFile[0] = '/0';
 ofn.nMaxFile = sizeof(szFile);
 ofn.lpstrFilter = L"All/0*.*/0Text/0*.TXT/0";
 ofn.nFilterIndex = 1;
 ofn.lpstrFileTitle = NULL;
 ofn.nMaxFileTitle = 0;
 ofn.lpstrInitialDir = NULL;
 ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;


 if (GetOpenFileNameW(&ofn))
 {
  GetShortPathNameW(ofn.lpstrFile, wsShortName, 1000);
  char sShortName[1000] = {0};
  int length =WideCharToMultiByte(CP_ACP, 0, wsShortName, -1, sShortName, 1000, NULL, NULL);
  file = fopen(sShortName, "rb");
 }

 fclose(file);

分析:
    文件是在中文Win2000下创建好之后,再拷贝到英文Win2000的,初步认为这涉及到代码页的问题!
    应用程序使用了一个第三方的工具包,提供的接口需要一个ANSI编码的文件名,而不是UNICODE的。
    进一步的分析发现,工具包是用fopen()来打开文件的。
    于是,问题变成了如何让fopen在不同的代码页之间也能正常工作?
   
    要解决问题,必须先弄清楚问题产生的具体原因。
   
    查看文档,Win2000的文件系统在保存文件时,实际记录了文件的两个名字:长文件名和短文件名。而长文件名是按UNICODE保存的,短文件名则是按ANSI保存的!
    显然上面的操作都是基于长文件名的。
    那么问题可以这样来理解:

    中文Win2000的代码页是936,在这个代码页下的字符的ANSI编码和UNICODE编码是一一对应的。
    文件的长文件名是UNICODE,而fopen()需要ANSI作为文件名参数,所以如果你想使用fopen(),必然需要UNICODE和ANSI之间的转换,而这个转换可能是你来做也可能是系统来做。比如你使用GetOpenFileNameA()来得到文件名,那么转换就不用你关心了;如果你非要用GetOpenFileNameW()的话,那么你的代码的下一行可能就是WideCharToMultiByte()了。总之,fopen的使用不会有什么问题。

    在文件迁移到英文Win2000后,问题就出现了。

    英文Win2000的代码页是437。一个中文字符的UNICODE映射到437是没有任何意义的,这称为unmappable character。一般情况下,unmappable character的转换结果会是一个'?'。所以转换之后,文件名中原有的信息就丢失了。这正是英文Win2000下fopen不能打开中文文件的原因。

解决:

    前面提到了,Win2000的文件系统是用ANSI编码来保存短文件名的。我们可以利用它来解决问题。

    怎么得到短文件名呢?有一个API是做这事的:

    DWORD GetShortPathName(LPCTSTR lpszLongPath, LPTSTR lpszShortPath, DWORD cchBuffer );

    GetShortPathName()需要一个长文件名(其实是PATH)作为参数,这就意味着我们只能使用GetShortPathNameW(),那么我们得到的短文件名也是UNICODE。那这不是存在和长文件名一样的问题吗???

    注意到一个事实:一个ANSI字符串,在不同的代码页有不同的含义。但是,在任意一个代码页下,将ANSI转为UNICODE,然后再从UNICODE转回ANSI,这个字符串保持不变,不会丢失任何信息!!!
   
    所以,我们可以放心地把得到的短文件名用WideCharToMultiByte()转为ANSI,再交给fopen使用。

      

示例代码:

 FILE * file = NULL;

 wchar_t wsShortName[1000] = {0,};

 OPENFILENAMEW ofn;
 wchar_t szFile[MAX_PATH];

 // Initialize OPENFILENAME
 ZeroMemory(&ofn, sizeof(ofn));
 ofn.lStructSize = sizeof(ofn);
 ofn.hwndOwner = NULL;
 ofn.lpstrFile = szFile;
 ofn.lpstrFile[0] = '/0';
 ofn.nMaxFile = sizeof(szFile);
 ofn.lpstrFilter = L"All/0*.*/0Text/0*.TXT/0";
 ofn.nFilterIndex = 1;
 ofn.lpstrFileTitle = NULL;
 ofn.nMaxFileTitle = 0;
 ofn.lpstrInitialDir = NULL;
 ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;


 if (GetOpenFileNameW(&ofn))
 {
  GetShortPathNameW(ofn.lpstrFile, wsShortName, 1000);
  char sShortName[1000] = {0};
  int length =WideCharToMultiByte(CP_ACP, 0, wsShortName, -1, sShortName, 1000, NULL, NULL);
  file = fopen(sShortName, "rb");
 }

 fclose(file);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值