转一篇文章,原文出处没有找到,看来kevins也是转的
——————————————————
程序使用动态库DLL一般分为隐式加载和显式加载两种,分别对应两种链接情况。本文主要讨论显式加载的技术问题。我们知道,要显式加载一个DLL,并取得其中导出的函数地址一般是通过如下步骤:
(1) 用LoadLibrary加载dll文件,获得该dll的模块句柄;
(2) 定义一个函数指针类型,并声明一个变量;
(3) 用GetProcAddress取得该dll中目标函数的地址,赋值给函数指针变量;
(4) 调用函数指针变量。
这 个方法要求dll文件位于硬盘上面。现在假设我们的dll已经位于内存中,比如通过脱壳、解密或者解压缩得到,能不能不把它写入硬盘文件,而直接从内存加 载呢?答案是肯定的。经过多天的研究,非法操作了N次,修改了M个BUG,死亡了若干脑细胞后,终于有了初步的结果,下面做个总结与大家共享。
一、加载的步骤
由 于没有相关的资料说明,只能凭借感觉来写。首先LoadLibrary是把dll的代码映射到exe进程的虚拟地址空间中,我们要实现的也是这个。所以先 要弄清楚dll的文件结构。好在这个比较简单,它和exe一样也是PE文件结构,关于PE文件的资料很多,阅读一番后,基本上知道了必须做的几个工作:
(1)判断内存数据是否是一个有效的DLL。这个功能通过函数CheckDataValide完成。原型是:
BOOL CMemLoadDll::CheckDataValide(void* lpFileData, int DataLength);
(2)计算加载该DLL所需的虚拟内存大小。这个功能通过函数CalcTotalImageSize完成。原型是:
int CMemLoadDll::CalcTotalImageSize();
(3)将DLL数据复制到所分配的虚拟内存块中。该功能通过函数CopyDllDatas完成。要注意段对齐。
void CMemLoadDll::CopyDllDatas(void* pDest, void* pSrc);
(4)修正基地重定位数据。这个功能通过函数DoRelocation完成。原型是:
void CMemLoadDll::DoRelocation( void *NewBase);
(5)填充该DLL的引入地址表。这个功能由函数FillRavAddress完成。原型是:
BOOL CMemLoadDll::FillRavAddress(void *pImageBase);
(6)根据DLL每个节的属性设置其对应内存页的读写属性。我这里做了简化,所有内存区域都设置成一样的读写属性。
(7)调用入口函数DllMain,完成初始化工作。这一步我一开始忽略了,所以总是发现自己加载的dll和LoadLibrary加载的dll有些不同(我把整块内存区域保存到两个文件中进行比较,够晕的)。只是最近猜想到还需要这一步。
(8)保存dll的基地址(即分配的内存块起始地址),用于查找dll的导出函数。从现在开始这个dll已经完全映射到了进程的虚拟地址空间,可以使用它了。
(9)不需要dll的时候,释放所分配的虚拟内存。
二、要说明的几个问题
(1)目前CMemLoadDll仅仅针对win32 动态库,没有考虑mfc常规和扩展dll。
(2) 只考虑使用dll中的函数,对于导出类的dll,由于通常都是隐式链接,所以也没有考虑。导出变量的dll虽然也是隐式链接,但是通过查找函数的方法也可 以找到该变量,不过在取值的时候一定要符合dll中对变量的定义,比如dll中导出的是一个int变量,则得到该变量在dll中的地址后,需要强制转换成 int*指针,然后取值。
(3)查找函数的功能通过函数
FARPROC CMemLoadDll::MemGetProcAddress(LPCSTR lpProcName);
实现,参数是dll导出的函数(或者变量)的名字。这里必须注意函数名修饰,通常不加extern”C”的函数,编译以后在dll中导出的都是修饰名,比如:
在dll头文件中: extern __declspec(dllexport) int nTestDll;
在.dll中的导出符号变成 ?nTestDll@@3HA
所以,为了能够找到我们需要的函数,必须在.h中添加extern “C”修饰。最好是给dll加一个def文件,里面明确给出每个函数的导出名字。
(4)PE中的内容比较多,有些细节没有考虑。比如CheckDataValide函数中没有考虑dll对操作系统版本的要求。
(5)PE文件中的节有很多种。可以从节表(或者叫做区块表)中一一找到。而且每个节的属性都不同。例如:.text, .data, .rsrc, .crt等等。由于这个代码基于手头已有的pe文件资料,对于不熟悉的节,在映射dll数据的时候没有考虑是否需要处理。
(6) 一开始把dll映射到进程的地址空间以后,我试图直接使用GetProcAddress查找函数。最初我认为LoadLibrary返回的 HINSTANCE值是0×10000000,把它传递给GetProcAddress可以找到目标函数,而我也把dll映射到0×10000000这个 地址,但是当我把这个值传递给GetProcAddress的时候,发现无法找到函数,用GetLastError得到错误码一看是无效句柄的错误,这才 明白原来LoadLibrary在加载dll的时候,同时创建了一个句柄放入进程的句柄表,而我们要做这个工作是比较麻烦的,所以只能自己写一个查找函 数。
(7)释放dll所占据的虚拟内存,原来我使用
VirtualFree((LPVOID)pImageBase, 0,MEM_FREE);
后来发现有问题,应该使用 VirtualFree((LPVOID)pImageBase, 0, MEM_RELEASE);
(8)MemGetProcAddress不仅支持通过函数名查找,还支持通过导出序号查找函数。例如下面的用法:
DLLFUNCTION fDll = (DLLFUNCTION)a.MemGetProcAddress((LPCTSTR)1);
三、创建测试用的DLL,工程的名字取”TestDll”
用VC向导创建一个WIN32 DLL工程,里面选择“导出一些符号”,为了测试需要,对源代码进行如下修改:
(1)头文件
// This class is exported from the TestDll.dll
class TESTDLL_API CTestDll {
public:
CTestDll(void);
};
extern TESTDLL_API int nTestDll;
//要修改的地方,添加了extern “C” 和 char *参数:
extern “C” TESTDLL_API int fnTestDll(char *);
(2)cpp文件
a. 添加 #include “stdlib.h”
b. DllMain中
case DLL_PROCESS_DETACH:
nTestDll = 12345;
break;
c. 初始化变量
TESTDLL_API int nTestDll=654321;
d. 修改函数
TESTDLL_API int fnTestDll(char *p)
{
if(p == NULL)
return nTestDll;
else
return atoi(p);
}
四、创建测试工程。使用一个dlg工程,测试代码如下:
假设 DllNameBuffer里面保存有dll文件的路径
CFile f;
if(f.Open(DllNameBuffer,CFile::modeRead))
{
int FileLength = f.GetLength();
void *lpBuf = new char[FileLength];
f.Read(lpBuf, FileLength);
f.Close();
CMemLoadDll a;
if(a.MemLoadLibrary(lpBuf, FileLength)) //加载dll到当前进程的地址空间
{
typedef int (*DLLFUNCTION)(char *);
DLLFUNCTION fDll = (DLLFUNCTION)a.MemGetProcAddress(”fnTestDll”);
if(fDll != NULL)
{
MessageBox(”找到函数!!”);
CString str;
str.Format(”Result is: %d & %d”,fDll(NULL), fDll(”100″));
MessageBox(str);
}
else
{
DWORD err = GetLastError();
CString str;
str.Format(”Error: %d”,err);
MessageBox(str);
}
}
delete[] lpBuf;
}
五、加载类源代码。
typedef BOOL (__stdcall *ProcDllMain)(HINSTANCE, DWORD, LPVOID );
class CMemLoadDll
{
public:
CMemLoadDll();
~CMemLoadDll();
BOOL MemLoadLibrary( void* lpFileData , int DataLength); // Dll file data buffer
FARPROC MemGetProcAddress(LPCSTR lpProcName);
private:
BOOL isLoadOk;
BOOL CheckDataValide(void* lpFileData, int DataLength);
int CalcTotalImageSize();
void CopyDllDatas(void* pDest, void* pSrc);
BOOL FillRavAddress(void* pBase);
void DoRelocation(void* pNewBase);
int GetAlignedSize(int Origin, int Alignment);
private:
ProcDllMain pDllMain;
private:
DWORD pImageBase;
PIMAGE_DOS_HEADER pDosHeader;
PIMAGE_NT_HEADERS pNTHeader;
PIMAGE_SECTION_HEADER pSectionHeader;
};
CMemLoadDll::CMemLoadDll()
{
isLoadOk = FALSE;
pImageBase = NULL;
pDllMain = NULL;
}
CMemLoadDll::~CMemLoadDll()
{
if(isLoadOk)
{
ASSERT(pImageBase != NULL);
ASSERT(pDllMain != NULL);
//脱钩,准备卸载dll
pDllMain((HINSTANCE)pImageBase,DLL_PROCESS_DETACH,0);
VirtualFree((LPVOID)pImageBase, 0, MEM_RELEASE);
}
}
//MemLoadLibrary函数从内存缓冲区数据中加载一个dll到当前进程的地址空间,缺省位置0×10000000
//返回值: 成功返回TRUE , 失败返回FALSE
//lpFileData: 存放dll文件数据的缓冲区
//DataLength: 缓冲区中数据的总长度
BOOL CMemLoadDll::MemLoadLibrary(void* lpFileData, int DataLength)
{
if(pImageBase != NULL)
{
return FALSE; //已经加载一个dll,还没有释放,不能加载新的dll
}
//检查数据有效性,并初始化
if(!CheckDataValide(lpFileData, DataLength))return FALSE;
//计算所需的加载空间
int ImageSize = CalcTotalImageSize();
if(ImageSize == 0) return FALSE;
// 分配虚拟内存
void *pMemoryAddress = VirtualAlloc((LPVOID)0×10000000, ImageSize,
MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if(pMemoryAddress == NULL) return FALSE;
else
{
CopyDllDatas(pMemoryAddress, lpFileData); //复制dll数据,并对齐每个段
//重定位信息
if(pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress >0
&& pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size>0)
{
DoRelocation(pMemoryAddress);
}
//填充引入地址表
if(!FillRavAddress(pMemoryAddress)) //修正引入地址表失败
{
VirtualFree(pMemoryAddress,0,MEM_RELEASE);
return FALSE;
}
//修改页属性。应该根据每个页的属性单独设置其对应内存页的属性。这里简化一下。
//统一设置成一个属性PAGE_EXECUTE_READWRITE
unsigned long old;
VirtualProtect(pMemoryAddress, ImageSize, PAGE_EXECUTE_READWRITE,&old);
}
//修正基地址
pNTHeader->OptionalHeader.ImageBase = (DWORD)pMemoryAddress;
//接下来要调用一下dll的入口函数,做初始化工作。
pDllMain = (ProcDllMain)(pNTHeader->OptionalHeader.AddressOfEntryPoint +(DWORD) pMemoryAddress);
BOOL InitResult = pDllMain((HINSTANCE)pMemoryAddress,DLL_PROCESS_ATTACH,0);
if(!InitResult) //初始化失败
{
pDllMain((HINSTANCE)pMemoryAddress,DLL_PROCESS_DETACH,0);
VirtualFree(pMemoryAddress,0,MEM_RELEASE);
pDllMain = NULL;
return FALSE;
}
isLoadOk = TRUE;
pImageBase = (DWORD)pMemoryAddress;
return TRUE;
}
//MemGetProcAddress函数从dll中获取指定函数的地址
//返回值: 成功返回函数地址 , 失败返回NULL
//lpProcName: 要查找函数的名字或者序号
FARPROC CMemLoadDll::MemGetProcAddress(LPCSTR lpProcName)
{
if(pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress == 0 ||
pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size == 0)
return NULL;
if(!isLoadOk) return NULL;
DWORD OffsetStart = pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
DWORD Size = pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;
PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)((DWORD)pImageBase + pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
int iBase = pExport->Base;
int iNumberOfFunctions = pExport->NumberOfFunctions;
int iNumberOfNames = pExport->NumberOfNames; //<= iNumberOfFunctions
LPDWORD pAddressOfFunctions = (LPDWORD)(pExport->AddressOfFunctions + pImageBase);
LPWORD pAddressOfOrdinals = (LPWORD)(pExport->AddressOfNameOrdinals + pImageBase);
LPDWORD pAddressOfNames = (LPDWORD)(pExport->AddressOfNames + pImageBase);
int iOrdinal = -1;
if(((DWORD)lpProcName & 0xFFFF0000) == 0) //IT IS A ORDINAL!
{
iOrdinal = (DWORD)lpProcName & 0×0000FFFF - iBase;
}
else //use name
{
int iFound = -1;
for(int i=0;i {
char* pName= (char* )(pAddressOfNames[i] + pImageBase);
if(strcmp(pName, lpProcName) == 0)
{
iFound = i; break;
}
}
if(iFound >= 0)
{
iOrdinal = (int)(pAddressOfOrdinals[iFound]);
}
}
if(iOrdinal < 0 || iOrdinal >= iNumberOfFunctions ) return NULL;
else
{
DWORD pFunctionOffset = pAddressOfFunctions[iOrdinal];
if(pFunctionOffset > OffsetStart && pFunctionOffset < (OffsetStart+Size))//maybe Export Forwarding
return NULL;
else return (FARPROC)(pFunctionOffset + pImageBase);
}
}
// 重定向PE用到的地址
void CMemLoadDll::DoRelocation( void *NewBase)
{
/* 重定位表的结构:
// DWORD sectionAddress, DWORD size (包括本节需要重定位的数据)
// 例如 1000节需要修正5个重定位数据的话,重定位表的数据是
// 00 10 00 00 14 00 00 00 xxxx xxxx xxxx xxxx xxxx 0000
// ———– ———– —-
// 给出节的偏移 总尺寸=8+6*2 需要修正的地址 用于对齐4字节
// 重定位表是若干个相连,如果address 和 size都是0 表示结束
// 需要修正的地址是12位的,高4位是形态字,intel cpu下是3
*/
//假设NewBase是0×600000,而文件中设置的缺省ImageBase是0×400000,则修正偏移量就是0×200000
DWORD Delta = (DWORD)NewBase - pNTHeader->OptionalHeader.ImageBase;
//注意重定位表的位置可能和硬盘文件中的偏移地址不同,应该使用加载后的地址
PIMAGE_BASE_RELOCATION pLoc = (PIMAGE_BASE_RELOCATION)((unsigned long)NewBase
+ pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
while((pLoc->VirtualAddress + pLoc->SizeOfBlock) != 0) //开始扫描重定位表
{
WORD *pLocData = (WORD *)((int)pLoc + sizeof(IMAGE_BASE_RELOCATION));
//计算本节需要修正的重定位项(地址)的数目
int NumberOfReloc = (pLoc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION))/sizeof(WORD);
for( int i=0 ; i < NumberOfReloc; i++)
{
if( (DWORD)(pLocData[i] & 0xF000) == 0×00003000) //这是一个需要修正的地址
{
// 举例:
// pLoc->VirtualAddress = 0×1000;
// pLocData[i] = 0×313E; 表示本节偏移地址0×13E处需要修正
// 因此 pAddress = 基地址 + 0×113E
// 里面的内容是 A1 ( 0c d4 02 10) 汇编代码是: mov eax , [1002d40c]
// 需要修正1002d40c这个地址
DWORD * pAddress = (DWORD *)((unsigned long)NewBase + pLoc->VirtualAddress + (pLocData[i] & 0×0FFF));
*pAddress += Delta;
}
}
//转移到下一个节进行处理
pLoc = (PIMAGE_BASE_RELOCATION)((DWORD)pLoc + pLoc->SizeOfBlock);
}
}
//填充引入地址表
BOOL CMemLoadDll::FillRavAddress(void *pImageBase)
{
// 引入表实际上是一个 IMAGE_IMPORT_DESCRIPTOR 结构数组,全部是0表示结束
// 数组定义如下:
//
// DWORD OriginalFirstThunk; // 0表示结束,否则指向未绑定的IAT结构数组
// DWORD TimeDateStamp;
// DWORD ForwarderChain; // -1 if no forwarders
// DWORD Name; // 给出dll的名字
// DWORD FirstThunk; // 指向IAT结构数组的地址(绑定后,这些IAT里面就是实际的函数地址)
unsigned long Offset = pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress ;
if(Offset == 0) return TRUE; //No Import Table
PIMAGE_IMPORT_DESCRIPTOR pID = (PIMAGE_IMPORT_DESCRIPTOR)((unsigned long) pImageBase + Offset);
while(pID->Characteristics != 0 )
{
PIMAGE_THUNK_DATA pRealIAT = (PIMAGE_THUNK_DATA)((unsigned long)pImageBase + pID->FirstThunk);
PIMAGE_THUNK_DATA pOriginalIAT = (PIMAGE_THUNK_DATA)((unsigned long)pImageBase + pID->OriginalFirstThunk);
//获取dll的名字
char buf[256]; //dll name;
BYTE* pName = (BYTE*)((unsigned long)pImageBase + pID->Name);
for(int i=0;i<256;i++)
{
if(pName[i] == 0)break;
buf[i] = pName[i];
}
HMODULE hDll = GetModuleHandle(buf);
if(hDll == NULL) {
hDll = LoadLibrary (buf);
if (hDll == NULL)
return FALSE; //NOT FOUND DLL
} //获取DLL中每个导出函数的地址,填入IAT
//每个IAT结构是 :
// union { PBYTE ForwarderString;
// PDWORD Function;
// DWORD Ordinal;
// PIMAGE_IMPORT_BY_NAME AddressOfData;
// } u1;
// 长度是一个DWORD ,正好容纳一个地址。
for(i=0; ;i++)
{
if(pOriginalIAT[i].u1.Function == 0)break;
FARPROC lpFunction = NULL;
if(pOriginalIAT[i].u1.Ordinal & IMAGE_ORDINAL_FLAG) //这里的值给出的是导出序号
{
lpFunction = GetProcAddress(hDll, (LPCSTR)(pOriginalIAT[i].u1.Ordinal & 0×0000FFFF));
}
else //按照名字导入
{
//获取此IAT项所描述的函数名称
PIMAGE_IMPORT_BY_NAME pByName = (PIMAGE_IMPORT_BY_NAME)
((DWORD)pImageBase + (DWORD)(pOriginalIAT[i].u1.AddressOfData));
// if(pByName->Hint !=0)
// lpFunction = GetProcAddress(hDll, (LPCSTR)pByName->Hint);
// else
lpFunction = GetProcAddress(hDll, (char *)pByName->Name);
}
if(lpFunction != NULL) //找到了!
{
pRealIAT[i].u1.Function = (PDWORD) lpFunction;
}
else return FALSE;
}
//move to next
pID = (PIMAGE_IMPORT_DESCRIPTOR)( (DWORD)pID + sizeof(IMAGE_IMPORT_DESCRIPTOR));
}
return TRUE;
}
//CheckDataValide函数用于检查缓冲区中的数据是否有效的dll文件
//返回值: 是一个可执行的dll则返回TRUE,否则返回FALSE。
//lpFileData: 存放dll数据的内存缓冲区
//DataLength: dll文件的长度
BOOL CMemLoadDll::CheckDataValide(void* lpFileData, int DataLength)
{
//检查长度
if(DataLength < sizeof(IMAGE_DOS_HEADER)) return FALSE;
pDosHeader = (PIMAGE_DOS_HEADER)lpFileData; // DOS头
//检查dos头的标记
if(pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) return FALSE; //0×5A4D : MZ
//检查长度
if((DWORD)DataLength < (pDosHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS)) ) return FALSE;
//取得pe头
pNTHeader = (PIMAGE_NT_HEADERS)( (unsigned long)lpFileData + pDosHeader->e_lfanew); // PE头
//检查pe头的合法性
if(pNTHeader->Signature != IMAGE_NT_SIGNATURE) return FALSE; //0×00004550 : PE00
if((pNTHeader->FileHeader.Characteristics & IMAGE_FILE_DLL) == 0) //0×2000 : File is a DLL
return FALSE;
if((pNTHeader->FileHeader.Characteristics & IMAGE_FILE_EXECUTABLE_IMAGE) == 0) //0×0002 : 指出文件可以运行
return FALSE;
if(pNTHeader->FileHeader.SizeOfOptionalHeader != sizeof(IMAGE_OPTIONAL_HEADER)) return FALSE;
//取得节表(段表)
pSectionHeader = (PIMAGE_SECTION_HEADER)((int)pNTHeader + sizeof(IMAGE_NT_HEADERS));
//验证每个节表的空间
for(int i=0; i< pNTHeader->FileHeader.NumberOfSections; i++)
{
if((pSectionHeader[i].PointerToRawData + pSectionHeader[i].SizeOfRawData) > (DWORD)DataLength)return FALSE;
}
return TRUE;
}
//计算对齐边界
int CMemLoadDll::GetAlignedSize(int Origin, int Alignment)
{
return (Origin + Alignment - 1) / Alignment * Alignment;
}
//计算整个dll映像文件的尺寸
int CMemLoadDll::CalcTotalImageSize()
{
int Size;
if(pNTHeader == NULL)return 0;
int nAlign = pNTHeader->OptionalHeader.SectionAlignment; //段对齐字节数
// 计算所有头的尺寸。包括dos, coff, pe头 和 段表的大小
Size = GetAlignedSize(pNTHeader->OptionalHeader.SizeOfHeaders, nAlign);
// 计算所有节的大小
for(int i=0; i < pNTHeader->FileHeader.NumberOfSections; ++i)
{
//得到该节的大小
int CodeSize = pSectionHeader[i].Misc.VirtualSize ;
int LoadSize = pSectionHeader[i].SizeOfRawData;
int MaxSize = (LoadSize > CodeSize)?(LoadSize):(CodeSize);
int SectionSize = GetAlignedSize(pSectionHeader[i].VirtualAddress + MaxSize, nAlign);
if(Size < SectionSize)
Size = SectionSize; //Use the Max;
}
return Size;
}
//CopyDllDatas函数将dll数据复制到指定内存区域,并对齐所有节
//pSrc: 存放dll数据的原始缓冲区
//pDest:目标内存地址
void CMemLoadDll::CopyDllDatas(void* pDest, void* pSrc)
{
// 计算需要复制的PE头+段表字节数
int HeaderSize = pNTHeader->OptionalHeader.SizeOfHeaders;
int SectionSize = pNTHeader->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER);
int MoveSize = HeaderSize + SectionSize;
//复制头和段信息
memmove(pDest, pSrc, MoveSize);
//复制每个节
for(int i=0; i < pNTHeader->FileHeader.NumberOfSections; ++i)
{
if(pSectionHeader[i].VirtualAddress == 0 || pSectionHeader[i].SizeOfRawData == 0)continue;
// 定位该节在内存中的位置
void *pSectionAddress = (void *)((unsigned long)pDest + pSectionHeader[i].VirtualAddress);
// 复制段数据到虚拟内存
memmove((void *)pSectionAddress,
(void *)((DWORD)pSrc + pSectionHeader[i].PointerToRawData),
pSectionHeader[i].SizeOfRawData);
}
//修正指针,指向新分配的内存
//新的dos头
pDosHeader = (PIMAGE_DOS_HEADER)pDest;
//新的pe头地址
pNTHeader = (PIMAGE_NT_HEADERS)((int)pDest + (pDosHeader->e_lfanew));
//新的节表地址
pSectionHeader = (PIMAGE_SECTION_HEADER)((int)pNTHeader + sizeof(IMAGE_NT_HEADERS));
return ;
}
灰蒙蒙的童年 苍白无力的青春 无数的昼夜颠倒 拿得那么少 做得那么多 房子遥遥无期 年华逝去 奔三了 妈妈说 儿啊 我20岁和你老爸结婚就怀上你了 妈妈真幸福
歌曲:有没有一首歌会让你想起我
歌手:周华健
专辑:忘忧草
作词:李焯雄 李宗盛
作曲:周华雄
灯熄灭了
月亮是寂寞的眼
静静看着
谁孤枕难眠
远处传来那首熟悉的歌
那些心声为何那样微弱
很久不见
你现在都还好吗
你曾说过
你不愿一个人
我们都活在这个城市里面
却为何没有再见面
却只和陌生人擦肩
有没有那么一首歌
会让你轻轻跟着和
牵动我们共同过去
记忆它不会沉默
有没有那么一首歌
会让你心里记着我
让你欢喜也让你忧
这么一个我
最真的梦
你现在还记得吗
你如今也是
一个有故事的人
天空下着一样冷冷的雨
落在同样的世界
昨天已越来越遥远
有没有那么一首歌
会让你轻轻跟着和
牵动我们共同过去
记忆从未沉默过
有没有那么一首歌
会让你心里记着我
让你欢喜也让你忧
这么一个我
有没有那么一首歌
会让你轻轻跟着和
随着我们生命起伏
一起唱的主题歌
有没有那么一首歌
会让你突然想起我
让你欢喜也让你忧
这么一个我
我现在唱的这首歌
若是让你想起了我
涌上来的若是寂寞
我想知道为什么
有没有那么一首歌
会让你突然想起我
让你欢喜也让你忧
这么一个我
我现在唱的这首歌
就代表我对你诉说
就算日子匆匆过去
我们曾一起走过
我现在唱的这首歌
就代表我对你诉说
就算日子匆匆过去
我们曾走过
就算日子匆匆过去
我们曾走过
日前在一个项目中有UTC时间和本地时间转换的需求,查了查 MSDN,需要先获取TIME_ZONE_INFORMATION 结构,然后用TzSpecificLocalTimeToSystemTime和SystemTimeToTzSpecificLocalTime实现转换。
实现代码如下:
void TimeUtcToLocal(LPSYSTEMTIME lpUtcTime,LPSYSTEMTIME lpSpecificLocalTime)
{
TIME_ZONE_INFORMATION tzInfo;
GetTimeZoneInformation(&tzInfo);
SystemTimeToTzSpecificLocalTime(&tzInfo,lpUtcTime,lpSpecificLocalTime);
}
void TimeLocalToUtc(LPSYSTEMTIME lpSpecificLocalTime,LPSYSTEMTIME lpUtcTime)
{
TIME_ZONE_INFORMATION tzInfo;
GetTimeZoneInformation(&tzInfo);
TzSpecificLocalTimeToSystemTime(&tzInfo,lpSpecificLocalTime,lpUtcTime);
}
在XP上运行,一切正常,放到2k上,问题出来了,LoadLibrary失效,模块无法加载。
在团队一位大哥的帮助下,发现这两个 API在2k的kernel32.dll模块中没有导出地址,问题定位在这两个API是XP中独有的,2k不支持。造成这个错误的原因应该归结为开发人员(PS:也就是我了,呵呵)在开发过程中不够严谨,没有注意到文档中提到的OS版本依赖性。
明白问题后,做如下修改:
void TimeUtcToLocal(LPSYSTEMTIME lpUtcTime,LPSYSTEMTIME lpSpecificLocalTime)
{
FILETIME ftLocal = {0}, ftUtc = {0};
SystemTimeToFileTime(lpUtcTime, &ftUtc);
FileTimeToLocalFileTime(&ftUtc, &ftLocal);
FileTimeToSystemTime(&ftLocal, lpSpecificLocalTime);
}
void TimeLocalToUtc(LPSYSTEMTIME lpSpecificLocalTime,LPSYSTEMTIME lpUtcTime)
{
FILETIME ftLocal = {0}, ftUtc = {0};
SystemTimeToFileTime(lpSpecificLocalTime, &ftLocal);
LocalFileTimeToFileTime(&ftLocal, &ftUtc);
FileTimeToSystemTime(&ftUtc, lpUtcTime);
}
更改后在不同版本OS下运行测试兼容性,一切正常。
题外话:开发人员在研发过程中,任何一行不经意的、没有了解其确切行为的代码,都可能在最终发布的产品中产生不可预期的行为或造成不可挽救的灾难。
解决方法:阅读文档的时候,请不单单只是注意到接口的参数、行为、依赖模块,请顺便注意一下兼容的OS版本。
A:拿买房的百万块钱去嫖,一次才一百多,可以嫖一万多次,算一个星期嫖三次,可以嫖3333个星期,大约可以嫖六七十年呢。
B:12月11日,第九届中国住交会在北京开幕。行为艺术家梁克刚将自己锁在“房奴”镣铐里,频频到各大房地产开发商的展台“抗议”。

看官们,你们选A还是选B呢?反正对fb来说,是只有A可以选的,呵呵。
没有太复杂的技术难点,一切在于迅速的把握需求,其实这正是敏捷开发的要旨所在,一切都可以非常快速的建立,非常快速的重构,我们的开发工具,底层库和框架,包括搜索引擎和web文档提供的帮助,都给我们提供了敏捷的能力。
一. 不要过设计:never over design
二. web架构生命周期:web architecture‘s life cycle
三. 缓存:Cache
四. 核心模块一定要自己开发:DIY your core module
五. 合理选择数据存储方式:reasonable data storage
六. 搞清楚谁是最重要的人:who’s the most important guy
七. 不要执着于文档:don’t be crazy about document
八. 团队:team
关于吃得饱和吃得着的权衡:
1、首页不要超过3屏。
2、尽量不要用图。当你打开photoshop的时候 -_-|||
3、考虑一下偏远山区的拨号用户也要访问你的网站吧。
4、考虑一下莫桑比克的用户也要访问的网站吧。
5、考虑一下南北的电信的不联不通吧。
6、使用CSS+div设计的你的网站吧。
7、优化一下JS吧。别将别人的JS拿来就用。
8、网站访问速度慢,先别怪带宽和服务器,先找一下页面的设计的原因吧。
9、快些、再快些;轻些,再轻些。取消一切可有可无的东西。网站设计,最难的是减法。
10、能访问到永远比页面漂亮更重要。这就像吃饱了才会不饿一样。
有关web效率,有著名的14条规则,由yahoo性能效率小组所总结,并广为流传。已出现相关插件YSlow(fb友情提醒:配置YSlow插件的运行环境需要依赖Firebug,关于Firebug的介绍及下载资源介绍请看《一款Firefox插件-GetFireBug》),针对具体网页按彼规则评分,Tenni Theurer小姐和她团队的14 rules总结在下面
- Make Fewer HTTP Requests
- Use a Content Delivery Network
- Add an Expires Header
- Gzip Components
- Put CSS at the Top
- Move Scripts to the Bottom
- Avoid CSS Expressions
- Make JavaScript and CSS External
- Reduce DNS Lookups
- Minify JavaScript
- Avoid Redirects
- Remove Duplicate Scripts
- Configure ETags
- Make Ajax Cacheable
相关连接:


分享到做啥
分享到收客
