1. 程式人生 > >在Visual Studio平臺上的應用libiconv庫進行字符集轉換的範例

在Visual Studio平臺上的應用libiconv庫進行字符集轉換的範例

有網友發郵件說我的文章:《Windows 10 正確編譯 iconv 的方法》對細節的說明太少,希望內容更詳細一些,所以我寫一篇圖文並茂的博文進一步詳細說明如何把linux平臺的字元符轉換庫libiconv遷移到Linux平臺以及Visual Studio 2012如何使用。這篇文章也說明了Visual C++時如何使用MinGW編譯的動態庫。

前言

先說說怎麼把Linux系統平臺的libiconv庫遷移到Windows平臺上來。一般情況下VC工程預設32位開發環境,引用的庫必須是32位的。比較好的工具平臺有CYGWIN和MSYS。CYGWIN在Windows平臺上模擬一個Linux子系統,相當於Linux程式和Windows平臺中間的翻譯。MSYS2在編譯階段把Linux執行時與Windows執行時一一轉換對映,相當於預編譯。基於工作原理的角度認為,MSYS2的效率高於CYGWIN。在64位系統上執行的MSYS2預設編譯出64位的庫。

配置MSYS2

雖然MSYS2在64位系統上支援啟動i686編譯環境,但libtool對32位的相容性確實不好。libiconv大量應用libtool進行工程設定,所以新建一個32位的Windows虛擬機器是一個比較好的選擇。下圖是一新建好的一個Windows 10 32位系統。


msys2的官方網站是:http://www.msys2.org/。下載msys2-i686-20161025.exe安裝包。它的安裝過程沒有特別的安裝選項,安裝成功後它的安裝檔案如下所示:

這裡需要一個適用於程式碼編輯的文字編輯器,這裡選擇Notepad++。MSYS2把MSYS2的預設安裝目錄c:\msys32稱為檔案系統的根目錄,把各個分割槽對映到根檔案系統下,分割槽的碟符是檔案系統的第一級資料夾名字。下面分別編輯/etc/pacman.d/下的3個檔案:mrrorlist.mingw32、mingwlist.mingw64和mirrorlist.msys,映象源使用清華大學的映象源。清華大學的映象源的URL是:

https://mirrors.tuna.tsinghua.edu.cn/msys2/。改完以後執行執行命令:

pacman -Syu
第一次執行這個命令會有一個特殊的英文提示。提示內容的大意是由於pacman自身的更新,建議使用者以強行結束當前控制檯程序代替執行exit命令。按照提示關閉控制檯程序即可。隨後重新開啟mingw32應用程式,再次執行上面的命令列,提示更新完畢,如下圖所示:


配置開發環境

首先是是編譯環境GCC。執行命令:

pacman -Syu gcc
pacman -Syu mingw-w64-i686-ffmpeg mingw-w64-i686-libc++

安裝ffmpeg的原因是ffmpeg的依賴項非常多,包含了大部分常用的開發工具和SDK。安裝libc++的原因是隻有它才能啟動GCC 7.2。安裝GCC只能獲取到GCC 6.3。下面安裝一些自動構建工具:

pacman -Syu make automake autoconf autogen libtool gettext
然後安裝常用工具:
pacman -Syu tar zip zlib zlib-devel
編譯環境安裝成功後如下圖所示:

libiconv執行configure之前

wget https://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.15.tar.gz  
然後解壓:
tar -xvf libiconv-1.15.tar.gz 
進入解壓後的目錄:
cd libiconv-1.15 
我實踐發現,1.15版本有configure呼叫的兩個指令碼都存在2處錯誤,錯誤的位置是相同的。這個錯誤也有編譯器版本太新,跟舊版本不相容的原因。首先開啟buld-aux目錄下的ltmain.sh檔案,搜尋process.h,按照如下圖所示,在5527行後面加入一行:
# include <process.h>

然後註釋掉5536行的程式碼,如下圖所示:


搜尋_P_WAIT,定位到5906行程式碼,把

  rval = (int) _spawnv (_P_WAIT, lt_argv_zero, (const char * const *) newargz);
改成:
  rval = (int) spawnv (_P_WAIT, lt_argv_zero, (const char * const *) newargz);
如下圖所示:


然後開啟位於libcharset/build-aux/ltmain.sh指令碼檔案,修改方法與過程與上面的ltmain.sh的修改相同。最後要修改srclib\lstat.c檔案。這個程式碼檔案編譯器版本升級而出現語法錯誤,要做的修改是把第36行註釋掉了。如下圖所示:


配置、編譯和安裝

轉到MSYS2控制檯,執行:

./configure --prefix=/usr/local
成功如下圖所示:


執行make命令編譯,成功如下圖所示:


執行make install,成功如下圖所示:


生成的動態庫位於/usr/local/bin下面,如下圖所示:


把/usr/local/bin和/usr/loca/lib兩個目錄下的所有檔案複製到Visual Studio 2012新建的test_iconv工程目錄下新建的一個iconv/x86資料夾下。這個工程是一個普通的Win32 控制檯工程。如下圖所示:


測試工程

MSYS2生成的a檔案也是coff格式的,Visual Studio 2012能夠像lib檔案一樣識別它。把/usr/local/include下面的Iconv.h檔案複製到工程目錄下,設定連結選項,它剛才複製檔案存放的位置加入到工程的連結屬性中一個叫做附加的庫目錄的值裡面。我寫了一個測試程式碼,它的功能是實現UCS-2LE字符集與UTF-8字符集之間的互相轉換,並使用notepad++驗證。程式碼如下:

#include <iostream>
#include <iconv.h>
#include <Windows.h>
#include <atlbase.h>
#include <atlstr.h>

void convert_to_unicode()
{
	FILE* pfile=fopen("a.txt","r");
	fseek(pfile,0,SEEK_END);
	size_t ifrom=(size_t) ftell(pfile);
	size_t ifrom_old=ifrom;
	char * pfrom=(char*)calloc(ifrom+1,1);
	char * pfrom_old=pfrom;
	rewind(pfile);
	long loc=ftell(pfile);
	fread(pfrom,1,ifrom,pfile);
	fclose(pfile);
	pfile=NULL;
	const char* pcan= iconv_canonicalize("UTF-8");
	libiconv_t cd=libiconv_open("UCS-2LE",pcan);
	//int discard=1;
	size_t ito=ifrom*2;
	size_t ito_old=ito;
	char * pto=(char*)calloc(ito+2,1);
	char * pto_old=pto;
	wchar_t* pwto=(wchar_t*)pto;
	errno_t ir=(errno_t)libiconv(cd,&pfrom,&ifrom,&pto,&ito);
	if(!ir)
	{
		pfile=fopen("a.txt","w");
		fseek(pfile,0,SEEK_SET);
		rewind(pfile);
		char* psign="\xFF\xFE";
		fwrite(psign,1,2,pfile);
		fwrite((void*)pto_old,1,wcslen(pwto)*2,pfile);
		fflush(pfile);
		fclose(pfile);
		printf("success.\n");
	}
	else
	{
		printf("fault.\n");
	}
	free(pfrom_old);
	free(pto_old);
	libiconv_close(cd);
}

bool ConvertCStringToUTF8(const CString strfrom,std::string& utf8)
{
	bool br=false;
	wchar_t* pwfrom=(wchar_t*)calloc(strfrom.GetLength()+2,2);
	wcscpy(pwfrom,strfrom);
	char* pfrom=(char*)pwfrom;
	char* pfrom_old=pfrom;
	size_t ifrom=strfrom.GetLength()*2;
	size_t ifrom_old=ifrom;
	size_t ito=ifrom;
	size_t ito_old=ito;
	char* pto=(char*)calloc(ito+2,1);
	char* pto_old=pto;
	libiconv_t cd=libiconv_open("UTF-8","UCS-2LE");
	errno_t ir=libiconv(cd,&pfrom,&ifrom,&pto,&ito);
	if(!ir)
	{
		br=true;
		utf8=pto_old;
		printf("success.\n");
	}
	else
	{
		printf("fault.\n");
	}
	free(pwfrom);
	free(pto_old);
	libiconv_close(cd);
	return br;
}

bool ConvertUTF8ToCString(const char* utf8,CString& str)
{
	bool br=false;
	size_t ifrom=strlen(utf8);
	size_t ifrom_old=ifrom;
	char* pfrom=(char*)calloc(ifrom+1,1);
	char* pfrom_old=pfrom;
	strcpy(pfrom,utf8);
	size_t ito=ifrom*2;
	size_t ito_old=ito;
	char* pto=(char*)calloc(ito+2,1);
	char* pto_old=pto;
	wchar_t* pwto=(wchar_t*)pto;
	libiconv_t cd=libiconv_open("UCS-2LE","UTF-8");
	errno_t ir=(errno_t)libiconv(cd,&pfrom,&ifrom,&pto,&ito);
	if(!ir)
	{
		br=true;
		str=pwto;
		printf("success.\n");
	}
	else
	{
		printf("fault.\n");
	}
	free(pfrom_old);
	free(pto_old);
	libiconv_close(cd);
	return br;
}

void main()
{
	//convert_to_unicode();
	std::string utf8;
	CString ucs2le;

	ConvertUTF8ToCString("UTF8תUNICODE",ucs2le);
	ConvertCStringToUTF8(L"UNICODEתUTF8",utf8);
	system("pause");
}

iconv函式有兩個坑。
  • 第一個坑是它會修改輸入引數的值的指標,但它把轉換結果存在指標被修改之間指向在記憶體所在的地址,所以關鍵是在轉換之前把輸出緩衝區的指標存放一個副本。
  • 第二個坑是它的返回值是size_t型別,但它的返回值的含義並非轉換了多少個字元,返回值的實際型別是errno_t。總體來說,-1表示出錯,0表示成功。

編譯成功後把libiconv的動態庫複製到Visual Studio 2012的輸出目錄,然後啟動除錯,成功截圖所下所示: