最近有個C++項目有這樣一個的需求:一些結(jié)構(gòu)體對象需要 序列化和反序列 操作。而我手頭上只有 Boost庫能干這事,但有個不太好的情況是其他同事的機(jī)器上并安裝Boost庫,因此我決定將這個對象序列化的操作封裝到 一個Dll庫中以提供給整個項目組使用。
問題在此時出現(xiàn)了,因?yàn)橐蛄谢慕Y(jié)構(gòu)體有若干個c標(biāo)準(zhǔn)庫有靜態(tài)和動態(tài)庫,而我希望提供一個統(tǒng)一的接口函數(shù)調(diào)用來序列化和反序列化對象,所以很自然想到了C++的模板技術(shù),于是很快就有了下面這個頭文件:
// libexport.h
#ifndef __LIBEXPORT_H__
#define __LIBEXPORT_H__
#include "structures.h"
#ifdef __DLL
#define DLLEXPORT __declspec(dllexport)
#else
#define DLLEXPORT
#endif
template
DLLEXPORT
bool Serialize( const T& object, char** pbuffer, int& buflen );
template
DLLEXPORT
bool Deserialize( const char* pbuffer, const int buflen, T& object );
#endif
這個頭文件很簡單,只有兩個函數(shù)模板接口,隨后在.cpp文件中實(shí)現(xiàn)這兩個函數(shù)模板c標(biāo)準(zhǔn)庫有靜態(tài)和動態(tài)庫,也成功地編譯鏈接這個DLL庫工程,但是在輸出目錄下只看到了 .Dll文件而沒有 發(fā)現(xiàn).lib文件,也就是說我的這個Dll庫中并未導(dǎo)出任何函數(shù)。如果將工程生成類型改為靜態(tài)庫,工程編譯鏈接也正常完成。輸出目錄中也生成了.lib文件, 但是在客戶端程序中使用此靜態(tài)庫時,會報告以下鏈接錯誤:
1>main.cpp
1>Linking...
1>main.obj : error LNK2001: unresolved external symbol "bool __cdecl Serialize(struct tagCostTime const &,char * *,int &)" ([email?protected]@@@@[email?protected]@[email?protected])
1>K:\Mywork\TestDemo\Release\TestDemo.exe : fatal error LNK1120: 1 unresolved externals
這是個典型的鏈接錯誤,說明鏈接器并未找到它想要的函數(shù)。看來生成靜態(tài)庫也未導(dǎo)出我想要的函數(shù)。 喔!喔!有些暈乎了。
靜下心來想了想,C++ 的模板是一項面向編譯器的技術(shù)(對于編譯器來說可以簡單認(rèn)為它就是個宏機(jī)制), 而編譯器只有在你的代碼顯示的實(shí)例化一個 函數(shù)模板時才會給你實(shí)作出一個模板函數(shù)。而在我們在構(gòu)建一個庫項目時一般不會自己去調(diào)用我們導(dǎo)出函數(shù)。所以此情此景我的庫并未導(dǎo)出任何函數(shù),而客戶端出現(xiàn)鏈接錯誤也就不足為怪了。
就此問題,跟同事討論了一番,最后的結(jié)論是 C++模板技術(shù)并不適合這個場合的應(yīng)用。
這個問題最簡單的方法就應(yīng)該是按部就班的為每一個要序列化的結(jié)構(gòu)體對象去重載(...)和(...) 函數(shù)。
可這么一來,我的頭文件中會有一大坨的函數(shù)聲明,而且可以預(yù)見:如果將來又有新的結(jié)構(gòu)體對象需要序列化了,那么我必須得在這個頭文件中為它添加相應(yīng)的重載函數(shù)。這可是個又費(fèi)力又沒啥技術(shù)含量的活兒,對于我這種懶人來說是個大忌。
得想個法兒,想個法兒。。。
還是對最初定義的那個 函數(shù)模板 接口的頭文件依依不舍,那種簡單形式才是我想要的。可C++模板技術(shù)在此啥忙也幫不上。只是個徒有其表的家伙而已。
等等,等等,既然編譯器不會為我實(shí)作出相應(yīng)的模板函數(shù),但是客戶端的鏈接器卻非要不可,而鏈接器又不知道這世界上有函數(shù)模板這貨,那我何不就是使用這個函數(shù)模板接口的頭文件,而在 .cpp 中為每個結(jié)構(gòu)體實(shí)現(xiàn)了相應(yīng)的重載函數(shù)(注意:這些重載函數(shù)的定義一定要與函數(shù)模板的聲明一致):
// libexport.h
#include
#include
#define __DLL
#include "libexport.h"
DLLEXPORT
bool Serialize( const COSTTIME& costtime, char** pbuffer, int& buflen )
{
bool bOK( true );
try
{
printf( "Serialize COSTTIME object.\r\n" );
}
catch(...)
{
bOK = false;
}
return bOK;
}
DLLEXPORT
bool Serialize( const IP& costtime, char** pbuffer, int& buflen )
{
bool bOK( true );
try
{
printf( "Serialize IP object.\r\n" );
}
catch(...)
{
bOK = false;
}
return bOK;
}
DLLEXPORT
bool Serialize( const DATAFRAME& costtime, char** pbuffer, int& buflen )
{
bool bOK( true );
try
{
printf( "Serialize DATAFRAME object.\r\n" );
}
catch(...)
{
bOK = false;
}
return bOK;
}
客戶端示例代碼:
// main.cpp
#include
#include
#include
#include "../MyLib/libexport.h"
int main()
{
int buflen(0);
char* pbuffer = NULL;
COSTTIME costtime;
Serialize( costtime, &pbuffer, buflen );
IP ip;
Serialize( ip, &pbuffer, buflen );
DATAFRAME dataFrame;
Serialize( dataFrame, &pbuffer, buflen );
system( "Pause" );
return 0;
}
編譯鏈接運(yùn)行,程序輸出為:
Serialize COSTTIME object.
Serialize IP object.
Serialize DATAFRAME object.
請按任意鍵繼續(xù). . .
就這么定了!
后記:
這個解決方案對于模板技術(shù)來說,只是使用它的語法技巧。在庫項目這端,這個頭文件就是個擺設(shè),沒有任何意義。
對我來說,這么干的好處是 省去了將來維護(hù)和重發(fā)布頭文件的工作(庫的維護(hù)和發(fā)布工作還是必須的)。
對于使用庫的客戶端代碼來說,這個頭文件還真是不含糊的說!