最近有个C++项目有这样一个的需求:一些结构体对象需要 序列化和反序列 操作。而我手头上只有 Boost库能干这事,但有个不太好的情况是其他同事的机器上并安装Boost库,因此我决定将这个对象序列化的操作封装到 一个Dll库中以提供给整个项目组使用。
问题在此时出现了,因为要序列化的结构体有若干个,而我希望提供一个统一的接口函数调用来序列化和反序列化对象,所以很自然想到了C++的模板技术,于是很快就有了下面这个头文件:
// 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
这个头文件很简单,只有两个函数模板接口, 随后在.cpp文件中实现这两个函数模板,也成功地编译链接这个DLL库工程,但是在输出目录下只看到了 .Dll文件而没有 发现.lib文件,也就是说我的这个Dll库中并未导出任何函数。如果将工程生成类型改为静态库,工程编译链接也正常完成。输出目录中也生成了.lib文件, 但是在客户端程序中使用此静态库时,会报告以下链接错误:
1>main.cpp
1>Linking...
1>main.obj : error LNK2001: unresolved external symbol "bool __cdecl Serialize(struct tagCostTime const &,char * *,int &)" (??$Serialize@UtagCostTime@@@@YA_NABUtagCostTime@@PAPADAAH@Z)
1>K:\Mywork\TestDemo\Release\TestDemo.exe : fatal error LNK1120: 1 unresolved externals
静下心来想了想,C++ 的模板是一项面向编译器的技术(对于编译器来说可以简单认为它就是个宏机制), 而编译器只有在你的代码显示的实例化一个 函数模板时才会给你实作出一个模板函数。而在我们在构建一个库项目时一般不会自己去调用我们导出函数。所以此情此景我的库并未导出任何函数,而客户端出现链接错误也就不足为怪了。
就此问题,跟同事讨论了一番,最后的结论是 C++模板技术并不适合这个场合的应用。
这个问题最简单的方法就应该是按部就班的为每一个要序列化的结构体对象去重载Serialize(...)和Deserialize(...) 函数。
可这么一来,我的头文件中会有一大坨的函数声明,而且可以预见:如果将来又有新的结构体对象需要序列化了,那么我必须得在这个头文件中为它添加相应的重载函数。这可是个又费力又没啥技术含量的活儿,对于我这种懒人来说是个大忌。
得想个法儿,想个法儿。。。
还是对最初定义的那个 函数模板 接口的头文件依依不舍,那种简单形式才是我想要的。可C++模板技术在此啥忙也帮不上。只是个徒有其表的家伙而已。
等等,等等,既然编译器不会为我实作出相应的模板函数,但是客户端的链接器却非要不可,而链接器又不知道这世界上有函数模板这货,那我何不就是使用这个函数模板接口的头文件,而在 .cpp 中为每个结构体实现了相应的重载函数(注意:这些重载函数的定义一定要与函数模板的声明一致):
// 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;
}
编译链接运行,程序输出为:
Serialize COSTTIME object.
Serialize IP object.
Serialize DATAFRAME object.
请按任意键继续. . .
就这么定了!
后记:
这个解决方案对于模板技术来说,只是使用它的语法技巧。在库项目这端,这个头文件就是个摆设,没有任何意义。
对我来说,这么干的好处是 省去了将来维护和重发布头文件的工作(库的维护和发布工作还是必须的)。
对于使用库的客户端代码来说,这个头文件还真是不含糊的说!