<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>c/c++</title><link>http://blog.vckbase.com/iwaswzq/category/363.html</link><description>c/c++</description><managingEditor>九月鹰飞</managingEditor><dc:language>af</dc:language><generator>.Text Version 0.958.2004.214</generator><item><dc:creator>九月鹰飞</dc:creator><title>从内存中加载动态库(二)</title><link>http://blog.vckbase.com/iwaswzq/archive/2006/07/28/21563.html</link><pubDate>Fri, 28 Jul 2006 04:57:00 GMT</pubDate><guid>http://blog.vckbase.com/iwaswzq/archive/2006/07/28/21563.html</guid><wfw:comment>http://blog.vckbase.com/iwaswzq/comments/21563.html</wfw:comment><comments>http://blog.vckbase.com/iwaswzq/archive/2006/07/28/21563.html#Feedback</comments><slash:comments>7</slash:comments><wfw:commentRss>http://blog.vckbase.com/iwaswzq/comments/commentRss/21563.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/iwaswzq/services/trackbacks/21563.html</trackback:ping><description>&lt;P&gt;&amp;lt;PRE&amp;gt;&lt;BR&gt;五、加载类的源代码。（编译环境vc6,win98）&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;typedef&amp;nbsp;&amp;nbsp; BOOL (__stdcall *ProcDllMain)(HINSTANCE, DWORD,&amp;nbsp; LPVOID );&lt;/P&gt;
&lt;P&gt;class CMemLoadDll&lt;BR&gt;{&lt;BR&gt;public:&lt;BR&gt;&amp;nbsp;CMemLoadDll();&lt;BR&gt;&amp;nbsp;~CMemLoadDll();&lt;BR&gt;&amp;nbsp;BOOL&amp;nbsp;&amp;nbsp;&amp;nbsp; MemLoadLibrary( void* lpFileData , int DataLength);&amp;nbsp; // Dll file data buffer&lt;BR&gt;&amp;nbsp;FARPROC MemGetProcAddress(LPCSTR lpProcName);&lt;BR&gt;private:&lt;BR&gt;&amp;nbsp;BOOL isLoadOk;&lt;BR&gt;&amp;nbsp;BOOL CheckDataValide(void* lpFileData, int DataLength);&lt;BR&gt;&amp;nbsp;int&amp;nbsp; CalcTotalImageSize();&lt;BR&gt;&amp;nbsp;void CopyDllDatas(void* pDest, void* pSrc);&lt;BR&gt;&amp;nbsp;BOOL FillRavAddress(void* pBase);&lt;BR&gt;&amp;nbsp;void DoRelocation(void* pNewBase);&lt;BR&gt;&amp;nbsp;int&amp;nbsp; GetAlignedSize(int Origin, int Alignment); &lt;BR&gt;private:&lt;BR&gt;&amp;nbsp;ProcDllMain pDllMain;&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;private:&lt;BR&gt;&amp;nbsp;DWORD&amp;nbsp; pImageBase;&lt;BR&gt;&amp;nbsp;PIMAGE_DOS_HEADER pDosHeader;&lt;BR&gt;&amp;nbsp;PIMAGE_NT_HEADERS pNTHeader;&lt;BR&gt;&amp;nbsp;PIMAGE_SECTION_HEADER pSectionHeader;&lt;BR&gt;};&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;CMemLoadDll::CMemLoadDll()&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;isLoadOk = FALSE;&lt;BR&gt;&amp;nbsp;pImageBase = NULL;&lt;BR&gt;&amp;nbsp;pDllMain = NULL;&lt;BR&gt;}&lt;BR&gt;CMemLoadDll::~CMemLoadDll()&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;if(isLoadOk)&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;ASSERT(pImageBase != NULL);&lt;BR&gt;&amp;nbsp;&amp;nbsp;ASSERT(pDllMain&amp;nbsp;&amp;nbsp; != NULL);&lt;BR&gt;&amp;nbsp;&amp;nbsp;//脱钩，准备卸载dll&lt;BR&gt;&amp;nbsp;&amp;nbsp;pDllMain((HINSTANCE)pImageBase,DLL_PROCESS_DETACH,0);&lt;BR&gt;&amp;nbsp;&amp;nbsp;VirtualFree((LPVOID)pImageBase, 0, MEM_RELEASE);&lt;BR&gt;&amp;nbsp;}&lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;//MemLoadLibrary函数从内存缓冲区数据中加载一个dll到当前进程的地址空间，缺省位置0x10000000&lt;BR&gt;//返回值： 成功返回TRUE , 失败返回FALSE&lt;BR&gt;//lpFileData: 存放dll文件数据的缓冲区&lt;BR&gt;//DataLength: 缓冲区中数据的总长度&lt;BR&gt;BOOL CMemLoadDll::MemLoadLibrary(void* lpFileData, int DataLength)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;if(pImageBase != NULL)&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;return FALSE;&amp;nbsp; //已经加载一个dll，还没有释放，不能加载新的dll&lt;BR&gt;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;//检查数据有效性，并初始化&lt;BR&gt;&amp;nbsp;if(!CheckDataValide(lpFileData, DataLength))return FALSE;&lt;BR&gt;&amp;nbsp;//计算所需的加载空间&lt;BR&gt;&amp;nbsp;int ImageSize = CalcTotalImageSize();&lt;BR&gt;&amp;nbsp;if(ImageSize == 0) return FALSE;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;// 分配虚拟内存&lt;BR&gt;&amp;nbsp;void *pMemoryAddress = VirtualAlloc((LPVOID)0x10000000, ImageSize,&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE); &lt;BR&gt;&amp;nbsp;if(pMemoryAddress == NULL) return FALSE;&lt;BR&gt;&amp;nbsp;else&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;CopyDllDatas(pMemoryAddress, lpFileData); //复制dll数据，并对齐每个段&lt;BR&gt;&amp;nbsp;&amp;nbsp;//重定位信息&lt;BR&gt;&amp;nbsp;&amp;nbsp;if(pNTHeader-&amp;gt;OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress &amp;gt;0&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;amp;&amp;amp; pNTHeader-&amp;gt;OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size&amp;gt;0)&lt;BR&gt;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;DoRelocation(pMemoryAddress);&lt;BR&gt;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;&amp;nbsp;//填充引入地址表&lt;BR&gt;&amp;nbsp;&amp;nbsp;if(!FillRavAddress(pMemoryAddress)) //修正引入地址表失败&lt;BR&gt;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;VirtualFree(pMemoryAddress,0,MEM_RELEASE);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;return FALSE;&lt;BR&gt;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;&amp;nbsp;//修改页属性。应该根据每个页的属性单独设置其对应内存页的属性。这里简化一下。&lt;BR&gt;&amp;nbsp;&amp;nbsp;//统一设置成一个属性PAGE_EXECUTE_READWRITE&lt;BR&gt;&amp;nbsp;&amp;nbsp;unsigned long old;&lt;BR&gt;&amp;nbsp;&amp;nbsp;VirtualProtect(pMemoryAddress, ImageSize, PAGE_EXECUTE_READWRITE,&amp;amp;old);&lt;BR&gt;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;//修正基地址&lt;BR&gt;&amp;nbsp;pNTHeader-&amp;gt;OptionalHeader.ImageBase = (DWORD)pMemoryAddress;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;//接下来要调用一下dll的入口函数，做初始化工作。&lt;BR&gt;&amp;nbsp;pDllMain = (ProcDllMain)(pNTHeader-&amp;gt;OptionalHeader.AddressOfEntryPoint +(DWORD) pMemoryAddress);&lt;BR&gt;&amp;nbsp;BOOL InitResult = pDllMain((HINSTANCE)pMemoryAddress,DLL_PROCESS_ATTACH,0);&lt;BR&gt;&amp;nbsp;if(!InitResult) //初始化失败&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;pDllMain((HINSTANCE)pMemoryAddress,DLL_PROCESS_DETACH,0);&lt;BR&gt;&amp;nbsp;&amp;nbsp;VirtualFree(pMemoryAddress,0,MEM_RELEASE);&lt;BR&gt;&amp;nbsp;&amp;nbsp;pDllMain = NULL;&lt;BR&gt;&amp;nbsp;&amp;nbsp;return FALSE;&lt;BR&gt;&amp;nbsp;}&lt;/P&gt;
&lt;P&gt;&amp;nbsp;isLoadOk = TRUE;&lt;BR&gt;&amp;nbsp;pImageBase = (DWORD)pMemoryAddress;&lt;BR&gt;&amp;nbsp;return TRUE;&lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;//MemGetProcAddress函数从dll中获取指定函数的地址&lt;BR&gt;//返回值： 成功返回函数地址 , 失败返回NULL&lt;BR&gt;//lpProcName: 要查找函数的名字或者序号&lt;BR&gt;FARPROC&amp;nbsp; CMemLoadDll::MemGetProcAddress(LPCSTR lpProcName)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;if(pNTHeader-&amp;gt;OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress == 0 ||&lt;BR&gt;&amp;nbsp;&amp;nbsp;pNTHeader-&amp;gt;OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size == 0)&lt;BR&gt;&amp;nbsp;&amp;nbsp;return NULL;&lt;BR&gt;&amp;nbsp;if(!isLoadOk) return NULL;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;DWORD OffsetStart = pNTHeader-&amp;gt;OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;&lt;BR&gt;&amp;nbsp;DWORD Size = pNTHeader-&amp;gt;OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)((DWORD)pImageBase + pNTHeader-&amp;gt;OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);&lt;BR&gt;&amp;nbsp;int iBase = pExport-&amp;gt;Base;&lt;BR&gt;&amp;nbsp;int iNumberOfFunctions = pExport-&amp;gt;NumberOfFunctions;&lt;BR&gt;&amp;nbsp;int iNumberOfNames = pExport-&amp;gt;NumberOfNames; //&amp;lt;= iNumberOfFunctions&lt;BR&gt;&amp;nbsp;LPDWORD pAddressOfFunctions = (LPDWORD)(pExport-&amp;gt;AddressOfFunctions + pImageBase);&lt;BR&gt;&amp;nbsp;LPWORD&amp;nbsp; pAddressOfOrdinals = (LPWORD)(pExport-&amp;gt;AddressOfNameOrdinals + pImageBase);&lt;BR&gt;&amp;nbsp;LPDWORD pAddressOfNames&amp;nbsp; = (LPDWORD)(pExport-&amp;gt;AddressOfNames + pImageBase);&lt;/P&gt;
&lt;P&gt;&amp;nbsp;int iOrdinal = -1;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;if(((DWORD)lpProcName &amp;amp; 0xFFFF0000) == 0) //IT IS A ORDINAL!&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;iOrdinal = (DWORD)lpProcName &amp;amp; 0x0000FFFF - iBase;&lt;BR&gt;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;else&amp;nbsp; //use name&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;int iFound = -1;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;for(int i=0;i&amp;lt;iNumberOfNames;i++)&lt;BR&gt;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;char* pName= (char* )(pAddressOfNames[i] + pImageBase);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;if(strcmp(pName, lpProcName) == 0)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;iFound = i; break;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;&amp;nbsp;if(iFound &amp;gt;= 0)&lt;BR&gt;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;iOrdinal = (int)(pAddressOfOrdinals[iFound]);&lt;BR&gt;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;}&lt;/P&gt;
&lt;P&gt;&amp;nbsp;if(iOrdinal &amp;lt; 0 || iOrdinal &amp;gt;= iNumberOfFunctions ) return NULL;&lt;BR&gt;&amp;nbsp;else&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;DWORD pFunctionOffset = pAddressOfFunctions[iOrdinal];&lt;BR&gt;&amp;nbsp;&amp;nbsp;if(pFunctionOffset &amp;gt; OffsetStart &amp;amp;&amp;amp; pFunctionOffset &amp;lt; (OffsetStart+Size))//maybe Export Forwarding&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;return NULL;&lt;BR&gt;&amp;nbsp;&amp;nbsp;else return (FARPROC)(pFunctionOffset + pImageBase);&lt;BR&gt;&amp;nbsp;}&lt;/P&gt;
&lt;P&gt;}&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;// 重定向PE用到的地址&lt;BR&gt;void CMemLoadDll::DoRelocation( void *NewBase)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;/* 重定位表的结构：&lt;BR&gt;&amp;nbsp;// DWORD sectionAddress, DWORD size (包括本节需要重定位的数据)&lt;BR&gt;&amp;nbsp;// 例如 1000节需要修正5个重定位数据的话，重定位表的数据是&lt;BR&gt;&amp;nbsp;// 00 10 00 00&amp;nbsp;&amp;nbsp; 14 00 00 00&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; xxxx xxxx xxxx xxxx xxxx 0000&lt;BR&gt;&amp;nbsp;// -----------&amp;nbsp;&amp;nbsp; -----------&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ----&lt;BR&gt;&amp;nbsp;// 给出节的偏移&amp;nbsp; 总尺寸=8+6*2&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 需要修正的地址&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 用于对齐4字节&lt;BR&gt;&amp;nbsp;// 重定位表是若干个相连，如果address 和 size都是0 表示结束&lt;BR&gt;&amp;nbsp;// 需要修正的地址是12位的，高4位是形态字，intel cpu下是3&lt;BR&gt;&amp;nbsp;*/&lt;BR&gt;&amp;nbsp;//假设NewBase是0x600000,而文件中设置的缺省ImageBase是0x400000,则修正偏移量就是0x200000&lt;BR&gt;&amp;nbsp;DWORD Delta = (DWORD)NewBase - pNTHeader-&amp;gt;OptionalHeader.ImageBase;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;//注意重定位表的位置可能和硬盘文件中的偏移地址不同，应该使用加载后的地址&lt;BR&gt;&amp;nbsp;PIMAGE_BASE_RELOCATION pLoc = (PIMAGE_BASE_RELOCATION)((unsigned long)NewBase &lt;BR&gt;&amp;nbsp;&amp;nbsp;+ pNTHeader-&amp;gt;OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);&lt;BR&gt;&amp;nbsp;while((pLoc-&amp;gt;VirtualAddress + pLoc-&amp;gt;SizeOfBlock) != 0) //开始扫描重定位表&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;WORD *pLocData = (WORD *)((int)pLoc + sizeof(IMAGE_BASE_RELOCATION));&lt;BR&gt;&amp;nbsp;&amp;nbsp;//计算本节需要修正的重定位项（地址）的数目&lt;BR&gt;&amp;nbsp;&amp;nbsp;int NumberOfReloc = (pLoc-&amp;gt;SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION))/sizeof(WORD);&lt;BR&gt;&amp;nbsp;&amp;nbsp;for( int i=0 ; i &amp;lt; NumberOfReloc; i++)&lt;BR&gt;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;if( (DWORD)(pLocData[i] &amp;amp; 0xF000) == 0x00003000) //这是一个需要修正的地址&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 举例： &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// pLoc-&amp;gt;VirtualAddress = 0x1000; &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// pLocData[i] = 0x313E; 表示本节偏移地址0x13E处需要修正&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 因此 pAddress = 基地址 + 0x113E&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 里面的内容是 A1 ( 0c d4 02 10)&amp;nbsp; 汇编代码是： mov eax , [1002d40c]&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 需要修正1002d40c这个地址&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;DWORD * pAddress = (DWORD *)((unsigned long)NewBase + pLoc-&amp;gt;VirtualAddress + (pLocData[i] &amp;amp; 0x0FFF));&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;*pAddress += Delta;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;&amp;nbsp;//转移到下一个节进行处理&lt;BR&gt;&amp;nbsp;&amp;nbsp;pLoc = (PIMAGE_BASE_RELOCATION)((DWORD)pLoc + pLoc-&amp;gt;SizeOfBlock);&lt;BR&gt;&amp;nbsp;}&lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;//填充引入地址表&lt;BR&gt;BOOL CMemLoadDll::FillRavAddress(void *pImageBase)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;// 引入表实际上是一个 IMAGE_IMPORT_DESCRIPTOR 结构数组，全部是0表示结束&lt;BR&gt;&amp;nbsp;// 数组定义如下：&lt;BR&gt;&amp;nbsp;// &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // DWORD&amp;nbsp;&amp;nbsp; OriginalFirstThunk;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 0表示结束，否则指向未绑定的IAT结构数组&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // DWORD&amp;nbsp;&amp;nbsp; TimeDateStamp; &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // DWORD&amp;nbsp;&amp;nbsp; ForwarderChain;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // -1 if no forwarders&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // DWORD&amp;nbsp;&amp;nbsp; Name;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 给出dll的名字&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // DWORD&amp;nbsp;&amp;nbsp; FirstThunk;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 指向IAT结构数组的地址(绑定后，这些IAT里面就是实际的函数地址)&lt;BR&gt;&amp;nbsp;unsigned long Offset = pNTHeader-&amp;gt;OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress ;&lt;BR&gt;&amp;nbsp;if(Offset == 0) return TRUE; //No Import Table&lt;BR&gt;&amp;nbsp;PIMAGE_IMPORT_DESCRIPTOR pID = (PIMAGE_IMPORT_DESCRIPTOR)((unsigned long) pImageBase + Offset);&lt;BR&gt;&amp;nbsp;while(pID-&amp;gt;Characteristics != 0 )&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;PIMAGE_THUNK_DATA pRealIAT = (PIMAGE_THUNK_DATA)((unsigned long)pImageBase + pID-&amp;gt;FirstThunk);&lt;BR&gt;&amp;nbsp;&amp;nbsp;PIMAGE_THUNK_DATA pOriginalIAT = (PIMAGE_THUNK_DATA)((unsigned long)pImageBase + pID-&amp;gt;OriginalFirstThunk);&lt;BR&gt;&amp;nbsp;&amp;nbsp;//获取dll的名字&lt;BR&gt;&amp;nbsp;&amp;nbsp;char buf[256]; //dll name;&lt;BR&gt;&amp;nbsp;&amp;nbsp;BYTE* pName = (BYTE*)((unsigned long)pImageBase + pID-&amp;gt;Name);&lt;BR&gt;&amp;nbsp;&amp;nbsp;for(int i=0;i&amp;lt;256;i++)&lt;BR&gt;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;if(pName[i] == 0)break;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;buf[i] = pName[i];&lt;BR&gt;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;&amp;nbsp;if(i&amp;gt;=256) return FALSE;&amp;nbsp; // bad dll name&lt;BR&gt;&amp;nbsp;&amp;nbsp;else buf[i] = 0;&lt;BR&gt;&amp;nbsp;&amp;nbsp;HMODULE hDll = GetModuleHandle(buf);&lt;BR&gt;&amp;nbsp;&amp;nbsp;if(hDll == NULL)return FALSE; //NOT FOUND DLL&lt;BR&gt;&amp;nbsp;&amp;nbsp;//获取DLL中每个导出函数的地址，填入IAT&lt;BR&gt;&amp;nbsp;&amp;nbsp;//每个IAT结构是 ：&lt;BR&gt;&amp;nbsp;&amp;nbsp;// union {&amp;nbsp;PBYTE&amp;nbsp; ForwarderString;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //&amp;nbsp;&amp;nbsp;&amp;nbsp;PDWORD Function;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //&amp;nbsp;&amp;nbsp;&amp;nbsp;DWORD Ordinal;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //&amp;nbsp;&amp;nbsp;&amp;nbsp;PIMAGE_IMPORT_BY_NAME&amp;nbsp; AddressOfData;&lt;BR&gt;&amp;nbsp;&amp;nbsp;// } u1;&lt;BR&gt;&amp;nbsp;&amp;nbsp;// 长度是一个DWORD ，正好容纳一个地址。&lt;BR&gt;&amp;nbsp;&amp;nbsp;for(i=0; ;i++)&lt;BR&gt;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;if(pOriginalIAT[i].u1.Function == 0)break;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;FARPROC lpFunction = NULL;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;if(pOriginalIAT[i].u1.Ordinal &amp;amp; IMAGE_ORDINAL_FLAG) //这里的值给出的是导出序号&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lpFunction = GetProcAddress(hDll, (LPCSTR)(pOriginalIAT[i].u1.Ordinal &amp;amp; 0x0000FFFF));&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;else //按照名字导入&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//获取此IAT项所描述的函数名称&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;PIMAGE_IMPORT_BY_NAME pByName = (PIMAGE_IMPORT_BY_NAME)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;((DWORD)pImageBase + (DWORD)(pOriginalIAT[i].u1.AddressOfData));&lt;BR&gt;//&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if(pByName-&amp;gt;Hint !=0)&lt;BR&gt;//&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lpFunction = GetProcAddress(hDll, (LPCSTR)pByName-&amp;gt;Hint);&lt;BR&gt;//&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lpFunction = GetProcAddress(hDll, (char *)pByName-&amp;gt;Name);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;if(lpFunction != NULL)&amp;nbsp;&amp;nbsp; //找到了！&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;pRealIAT[i].u1.Function = (PDWORD) lpFunction;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;else return FALSE;&lt;BR&gt;&amp;nbsp;&amp;nbsp;}&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;//move to next &lt;BR&gt;&amp;nbsp;&amp;nbsp;pID = (PIMAGE_IMPORT_DESCRIPTOR)( (DWORD)pID + sizeof(IMAGE_IMPORT_DESCRIPTOR));&lt;BR&gt;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;return TRUE;&lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;//CheckDataValide函数用于检查缓冲区中的数据是否有效的dll文件&lt;BR&gt;//返回值： 是一个可执行的dll则返回TRUE，否则返回FALSE。&lt;BR&gt;//lpFileData: 存放dll数据的内存缓冲区&lt;BR&gt;//DataLength: dll文件的长度&lt;BR&gt;BOOL CMemLoadDll::CheckDataValide(void* lpFileData, int DataLength)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;//检查长度&lt;BR&gt;&amp;nbsp;if(DataLength &amp;lt; sizeof(IMAGE_DOS_HEADER)) return FALSE;&lt;BR&gt;&amp;nbsp;pDosHeader = (PIMAGE_DOS_HEADER)lpFileData;&amp;nbsp;&amp;nbsp;// DOS头&lt;BR&gt;&amp;nbsp;//检查dos头的标记&lt;BR&gt;&amp;nbsp;if(pDosHeader-&amp;gt;e_magic != IMAGE_DOS_SIGNATURE) return FALSE;&amp;nbsp;&amp;nbsp;//0x5A4D : MZ&lt;/P&gt;
&lt;P&gt;&amp;nbsp;//检查长度&lt;BR&gt;&amp;nbsp;if((DWORD)DataLength &amp;lt; (pDosHeader-&amp;gt;e_lfanew + sizeof(IMAGE_NT_HEADERS)) ) return FALSE;&lt;BR&gt;&amp;nbsp;//取得pe头&lt;BR&gt;&amp;nbsp;pNTHeader = (PIMAGE_NT_HEADERS)( (unsigned long)lpFileData + pDosHeader-&amp;gt;e_lfanew); // PE头&lt;BR&gt;&amp;nbsp;//检查pe头的合法性&lt;BR&gt;&amp;nbsp;if(pNTHeader-&amp;gt;Signature != IMAGE_NT_SIGNATURE) return FALSE;&amp;nbsp;&amp;nbsp;//0x00004550 : PE00&lt;BR&gt;&amp;nbsp;if((pNTHeader-&amp;gt;FileHeader.Characteristics &amp;amp; IMAGE_FILE_DLL) == 0)&amp;nbsp;//0x2000&amp;nbsp; : File is a DLL&lt;BR&gt;&amp;nbsp;&amp;nbsp;return FALSE;&amp;nbsp; &lt;BR&gt;&amp;nbsp;if((pNTHeader-&amp;gt;FileHeader.Characteristics &amp;amp; IMAGE_FILE_EXECUTABLE_IMAGE) == 0) //0x0002 : 指出文件可以运行&lt;BR&gt;&amp;nbsp;&amp;nbsp;return FALSE;&lt;BR&gt;&amp;nbsp;if(pNTHeader-&amp;gt;FileHeader.SizeOfOptionalHeader != sizeof(IMAGE_OPTIONAL_HEADER)) return FALSE;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;BR&gt;&amp;nbsp;//取得节表（段表）&lt;BR&gt;&amp;nbsp;pSectionHeader = (PIMAGE_SECTION_HEADER)((int)pNTHeader + sizeof(IMAGE_NT_HEADERS));&lt;BR&gt;&amp;nbsp;//验证每个节表的空间&lt;BR&gt;&amp;nbsp;for(int i=0; i&amp;lt; pNTHeader-&amp;gt;FileHeader.NumberOfSections; i++)&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;if((pSectionHeader[i].PointerToRawData + pSectionHeader[i].SizeOfRawData) &amp;gt; (DWORD)DataLength)return FALSE;&lt;BR&gt;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;return TRUE;&lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;//计算对齐边界&lt;BR&gt;int CMemLoadDll::GetAlignedSize(int Origin, int Alignment)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;return (Origin + Alignment - 1) / Alignment * Alignment;&lt;BR&gt;}&lt;BR&gt;//计算整个dll映像文件的尺寸&lt;BR&gt;int CMemLoadDll::CalcTotalImageSize()&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;int Size;&lt;BR&gt;&amp;nbsp;if(pNTHeader == NULL)return 0;&lt;BR&gt;&amp;nbsp;int nAlign = pNTHeader-&amp;gt;OptionalHeader.SectionAlignment; //段对齐字节数&lt;/P&gt;
&lt;P&gt;&amp;nbsp;// 计算所有头的尺寸。包括dos, coff, pe头 和 段表的大小&lt;BR&gt;&amp;nbsp;Size = GetAlignedSize(pNTHeader-&amp;gt;OptionalHeader.SizeOfHeaders, nAlign);&lt;BR&gt;&amp;nbsp;// 计算所有节的大小&lt;BR&gt;&amp;nbsp;for(int i=0; i &amp;lt; pNTHeader-&amp;gt;FileHeader.NumberOfSections; ++i)&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;//得到该节的大小&lt;BR&gt;&amp;nbsp;&amp;nbsp;int CodeSize = pSectionHeader[i].Misc.VirtualSize ;&lt;BR&gt;&amp;nbsp;&amp;nbsp;int LoadSize = pSectionHeader[i].SizeOfRawData;&lt;BR&gt;&amp;nbsp;&amp;nbsp;int MaxSize = (LoadSize &amp;gt; CodeSize)?(LoadSize):(CodeSize);&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;int SectionSize = GetAlignedSize(pSectionHeader[i].VirtualAddress + MaxSize, nAlign);&lt;BR&gt;&amp;nbsp;&amp;nbsp;if(Size &amp;lt; SectionSize) &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;Size = SectionSize;&amp;nbsp; //Use the Max;&lt;BR&gt;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;return Size;&lt;BR&gt;}&lt;BR&gt;//CopyDllDatas函数将dll数据复制到指定内存区域，并对齐所有节&lt;BR&gt;//pSrc: 存放dll数据的原始缓冲区&lt;BR&gt;//pDest:目标内存地址&lt;BR&gt;void CMemLoadDll::CopyDllDatas(void* pDest, void* pSrc)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;// 计算需要复制的PE头+段表字节数&lt;BR&gt;&amp;nbsp;int&amp;nbsp; HeaderSize = pNTHeader-&amp;gt;OptionalHeader.SizeOfHeaders;&lt;BR&gt;&amp;nbsp;int&amp;nbsp; SectionSize = pNTHeader-&amp;gt;FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER);&lt;BR&gt;&amp;nbsp;int&amp;nbsp; MoveSize = HeaderSize + SectionSize;&lt;BR&gt;&amp;nbsp;//复制头和段信息&lt;BR&gt;&amp;nbsp;memmove(pDest, pSrc, MoveSize);&lt;/P&gt;
&lt;P&gt;&amp;nbsp;//复制每个节&lt;BR&gt;&amp;nbsp;for(int i=0; i &amp;lt; pNTHeader-&amp;gt;FileHeader.NumberOfSections; ++i)&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;if(pSectionHeader[i].VirtualAddress == 0 || pSectionHeader[i].SizeOfRawData == 0)continue;&lt;BR&gt;&amp;nbsp;&amp;nbsp;// 定位该节在内存中的位置&lt;BR&gt;&amp;nbsp;&amp;nbsp;void *pSectionAddress = (void *)((unsigned long)pDest + pSectionHeader[i].VirtualAddress);&lt;BR&gt;&amp;nbsp;&amp;nbsp;// 复制段数据到虚拟内存&lt;BR&gt;&amp;nbsp;&amp;nbsp;memmove((void *)pSectionAddress,&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; (void *)((DWORD)pSrc + pSectionHeader[i].PointerToRawData),&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;pSectionHeader[i].SizeOfRawData);&lt;BR&gt;&amp;nbsp;}&lt;/P&gt;
&lt;P&gt;&amp;nbsp;//修正指针，指向新分配的内存&lt;BR&gt;&amp;nbsp;//新的dos头&lt;BR&gt;&amp;nbsp;pDosHeader = (PIMAGE_DOS_HEADER)pDest;&lt;BR&gt;&amp;nbsp;//新的pe头地址&lt;BR&gt;&amp;nbsp;pNTHeader = (PIMAGE_NT_HEADERS)((int)pDest + (pDosHeader-&amp;gt;e_lfanew));&lt;BR&gt;&amp;nbsp;//新的节表地址&lt;BR&gt;&amp;nbsp;pSectionHeader = (PIMAGE_SECTION_HEADER)((int)pNTHeader + sizeof(IMAGE_NT_HEADERS));&lt;BR&gt;&amp;nbsp;return ;&lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;谨依此文祝贺我在VCKBASE里升级。&lt;BR&gt;iwaswzq 2006/7/27&lt;BR&gt;&amp;lt;/PRE&amp;gt;&lt;BR&gt;&lt;/P&gt;&lt;img src ="http://blog.vckbase.com/iwaswzq/aggbug/21563.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>九月鹰飞</dc:creator><title>从内存中加载动态库(一)</title><link>http://blog.vckbase.com/iwaswzq/archive/2006/07/28/21562.html</link><pubDate>Fri, 28 Jul 2006 04:55:00 GMT</pubDate><guid>http://blog.vckbase.com/iwaswzq/archive/2006/07/28/21562.html</guid><wfw:comment>http://blog.vckbase.com/iwaswzq/comments/21562.html</wfw:comment><comments>http://blog.vckbase.com/iwaswzq/archive/2006/07/28/21562.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://blog.vckbase.com/iwaswzq/comments/commentRss/21562.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/iwaswzq/services/trackbacks/21562.html</trackback:ping><description>&lt;P&gt;&amp;lt;PRE&amp;gt;&lt;BR&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 程序使用动态库DLL一般分为隐式加载和显式加载两种，分别对应两种链接情况。本文主要讨论显式加载的技术问题。我们知道，要显式加载一个DLL，并取得其中导出的函数地址一般是通过如下步骤：&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; (1) 用LoadLibrary加载dll文件，获得该dll的模块句柄；&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; (2) 定义一个函数指针类型，并声明一个变量；&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; (3) 用GetProcAddress取得该dll中目标函数的地址，赋值给函数指针变量；&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; (4) 调用函数指针变量。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 这个方法要求dll文件位于硬盘上面。现在假设我们的dll已经位于内存中，比如通过脱壳、解密或者解压缩得到，能不能不把它写入硬盘文件，而直接从内存加载呢？答案是肯定的。经过多天的研究，非法操作了N次，修改了M个BUG，死亡了若干脑细胞后，终于有了初步的结果，下面做个总结与大家共享。&lt;/P&gt;
&lt;P&gt;一、加载的步骤&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 由于没有相关的资料说明，只能凭借感觉来写。首先LoadLibrary是把dll的代码映射到exe进程的虚拟地址空间中，我们要实现的也是这个。所以先要弄清楚dll的文件结构。好在这个比较简单，它和exe一样也是PE文件结构，关于PE文件的资料很多，阅读一番后，基本上知道了必须做的几个工作：&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; (1)判断内存数据是否是一个有效的DLL。这个功能通过函数CheckDataValide完成。原型是：&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; BOOL CMemLoadDll::CheckDataValide(void* lpFileData, int DataLength);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; (2)计算加载该DLL所需的虚拟内存大小。这个功能通过函数CalcTotalImageSize完成。原型是：&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; int CMemLoadDll::CalcTotalImageSize();&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; (3)将DLL数据复制到所分配的虚拟内存块中。该功能通过函数CopyDllDatas完成。要注意段对齐。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; void CMemLoadDll::CopyDllDatas(void* pDest, void* pSrc);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; (4)修正基地重定位数据。这个功能通过函数DoRelocation完成。原型是：&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; void CMemLoadDll::DoRelocation( void *NewBase);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; (5)填充该DLL的引入地址表。这个功能由函数FillRavAddress完成。原型是：&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; BOOL CMemLoadDll::FillRavAddress(void *pImageBase);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; (6)根据DLL每个节的属性设置其对应内存页的读写属性。我这里做了简化，所有内存区域都设置成一样的读写属性。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; (7)调用入口函数DllMain，完成初始化工作。这一步我一开始忽略了，所以总是发现自己加载的dll和LoadLibrary加载的dll有些不同(我把整块内存区域保存到两个文件中进行比较，够晕的)。只是最近猜想到还需要这一步。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; (8)保存dll的基地址（即分配的内存块起始地址）,用于查找dll的导出函数。从现在开始这个dll已经完全映射到了进程的虚拟地址空间，可以使用它了。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; (9)不需要dll的时候，释放所分配的虚拟内存。&lt;/P&gt;
&lt;P&gt;二、要说明的几个问题&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp; (1)目前CMemLoadDll仅仅针对win32 动态库，没有考虑mfc常规和扩展dll。&lt;BR&gt;&amp;nbsp;&amp;nbsp; (2)只考虑使用dll中的函数，对于导出类的dll，由于通常都是隐式链接，所以也没有考虑。导出变量的dll虽然也是隐式链接，但是通过查找函数的方法也可以找到该变量，不过在取值的时候一定要符合dll中对变量的定义，比如dll中导出的是一个int变量，则得到该变量在dll中的地址后，需要强制转换成int*指针，然后取值。&lt;BR&gt;&amp;nbsp;&amp;nbsp; (3)查找函数的功能通过函数&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; FARPROC&amp;nbsp; CMemLoadDll::MemGetProcAddress(LPCSTR lpProcName);&lt;BR&gt;实现，参数是dll导出的函数（或者变量）的名字。这里必须注意函数名修饰，通常不加extern"C"的函数，编译以后在dll中导出的都是修饰名，比如：&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 在dll头文件中: extern __declspec(dllexport) int nTestDll;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 在.dll中的导出符号变成 &lt;A href="mailto:?nTestDll@@3HA"&gt;?nTestDll@@3HA&lt;/A&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp; 所以，为了能够找到我们需要的函数，必须在.h中添加extern "C"修饰。最好是给dll加一个def文件，里面明确给出每个函数的导出名字。&lt;BR&gt;&amp;nbsp;&amp;nbsp; (4)PE中的内容比较多，有些细节没有考虑。比如CheckDataValide函数中没有考虑dll对操作系统版本的要求。&lt;BR&gt;&amp;nbsp;&amp;nbsp; (5)PE文件中的节有很多种。可以从节表（或者叫做区块表）中一一找到。而且每个节的属性都不同。例如：.text, .data, .rsrc, .crt等等。由于这个代码基于手头已有的pe文件资料，对于不熟悉的节，在映射dll数据的时候没有考虑是否需要处理。&lt;BR&gt;&amp;nbsp;&amp;nbsp; (6)一开始把dll映射到进程的地址空间以后，我试图直接使用GetProcAddress查找函数。最初我认为LoadLibrary返回的HINSTANCE值是0x10000000，把它传递给GetProcAddress可以找到目标函数，而我也把dll映射到0x10000000这个地址，但是当我把这个值传递给GetProcAddress的时候，发现无法找到函数，用GetLastError得到错误码一看是无效句柄的错误，这才明白原来LoadLibrary在加载dll的时候，同时创建了一个句柄放入进程的句柄表，而我们要做这个工作是比较麻烦的，所以只能自己写一个查找函数。&lt;BR&gt;&amp;nbsp;&amp;nbsp; (7)释放dll所占据的虚拟内存，原来我使用&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; VirtualFree((LPVOID)pImageBase, 0,MEM_FREE);&lt;BR&gt;后来发现有问题，应该使用 VirtualFree((LPVOID)pImageBase, 0, MEM_RELEASE);&lt;BR&gt;&amp;nbsp;&amp;nbsp; (8)MemGetProcAddress不仅支持通过函数名查找，还支持通过导出序号查找函数。例如下面的用法：&lt;BR&gt;&amp;nbsp;DLLFUNCTION fDll = (DLLFUNCTION)a.MemGetProcAddress((LPCTSTR)1);&lt;/P&gt;
&lt;P&gt;三、创建测试用的DLL，工程的名字取"TestDll"&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 用VC向导创建一个WIN32 DLL工程，里面选择&amp;#8220;导出一些符号&amp;#8221;，为了测试需要，对源代码进行如下修改：&lt;BR&gt;&amp;nbsp;（1）头文件&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // This class is exported from the TestDll.dll&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; class TESTDLL_API CTestDll {&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; public:&lt;BR&gt;&amp;nbsp;CTestDll(void);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; };&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; extern TESTDLL_API int nTestDll;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; //要修改的地方，添加了extern "C" 和 char *参数：&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; extern "C"&amp;nbsp; TESTDLL_API int fnTestDll(char *);&lt;BR&gt;&amp;nbsp; （2）cpp文件&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; a. 添加 #include "stdlib.h"&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; b. DllMain中&lt;BR&gt;&amp;nbsp;&amp;nbsp;case DLL_PROCESS_DETACH:&lt;BR&gt;&lt;FONT color=#0000ff&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;nTestDll = 12345;&lt;BR&gt;&lt;/FONT&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;break;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; c. 初始化变量&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; TESTDLL_API int nTestDll=654321;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; d. 修改函数&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; TESTDLL_API int fnTestDll(char *p)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;if(p == NULL)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; return nTestDll;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;else&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; return atoi(p);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;四、创建测试工程。使用一个dlg工程，测试代码如下：&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 假设 DllNameBuffer里面保存有dll文件的路径&lt;BR&gt;&amp;nbsp;CFile f;&lt;BR&gt;&amp;nbsp;if(f.Open(DllNameBuffer,CFile::modeRead))&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;int FileLength = f.GetLength();&lt;BR&gt;&amp;nbsp;&amp;nbsp;void *lpBuf = new char[FileLength];&lt;BR&gt;&amp;nbsp;&amp;nbsp;f.Read(lpBuf, FileLength);&lt;BR&gt;&amp;nbsp;&amp;nbsp;f.Close();&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;CMemLoadDll a;&lt;BR&gt;&amp;nbsp;&amp;nbsp;if(a.MemLoadLibrary(lpBuf, FileLength)) //加载dll到当前进程的地址空间&lt;BR&gt;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;typedef&amp;nbsp; int (*DLLFUNCTION)(char *);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;DLLFUNCTION fDll = (DLLFUNCTION)a.MemGetProcAddress("fnTestDll");&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;if(fDll != NULL)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MessageBox("找到函数！！");&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;CString str;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;str.Format("Result is: %d &amp;amp; %d",fDll(NULL), fDll("100"));&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MessageBox(str);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;else&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;DWORD err = GetLastError();&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;CString str;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;str.Format("Error: %d",err);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MessageBox(str);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;&amp;nbsp;}&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;delete[] lpBuf;&lt;BR&gt;&amp;nbsp;} &lt;/P&gt;
&lt;P&gt;五、加载类源代码。（在后续贴子里面给出）&lt;/P&gt;
&lt;P&gt;iwaswzq 2006/7/27&lt;BR&gt;&amp;lt;/PRE&amp;gt;&lt;/P&gt;&lt;img src ="http://blog.vckbase.com/iwaswzq/aggbug/21562.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>九月鹰飞</dc:creator><title>奇怪的SetCheck</title><link>http://blog.vckbase.com/iwaswzq/archive/2006/02/19/17836.html</link><pubDate>Sun, 19 Feb 2006 08:01:00 GMT</pubDate><guid>http://blog.vckbase.com/iwaswzq/archive/2006/02/19/17836.html</guid><wfw:comment>http://blog.vckbase.com/iwaswzq/comments/17836.html</wfw:comment><comments>http://blog.vckbase.com/iwaswzq/archive/2006/02/19/17836.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://blog.vckbase.com/iwaswzq/comments/commentRss/17836.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/iwaswzq/services/trackbacks/17836.html</trackback:ping><description>&lt;P&gt;一、问题的提出&lt;/P&gt;
&lt;P&gt;CTreeCtrl有个属性TVS_HASBUTTONS，如果创建控件的时候加上了这个属性，则在每个节点的左侧&lt;BR&gt;都有一个按钮，用来表示节点的选择状态。通过两个函数SetCheck / GetCheck来设置和获取指定&lt;BR&gt;节点的选择状态。&lt;/P&gt;
&lt;P&gt;但是奇怪的是，在对话框中按照常规的方法使用了SetCheck，最后CTreeCtrl并没有显示节点被选&lt;BR&gt;中，下面是测试例子：&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp; 1、用wizard创建一个对话框工程，并且在上面放置一个CTreeCtrl控件。&lt;BR&gt;&amp;nbsp;&amp;nbsp; 2、设置CTreeCtrl的属性，"More Styles"里面选中"Check Boxes"，给它加上复选框。&lt;BR&gt;&amp;nbsp;&amp;nbsp; 3、对话框初始化的时候，给CTreeCtrl控件添加节点，并设置选中。代码如下：&lt;/P&gt;
&lt;P&gt;&lt;FONT style="BACKGROUND-COLOR: #a9a9a9" color=#0000ff&gt;BOOL CTestCheckDlg::OnInitDialog()&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;CDialog::OnInitDialog();&lt;BR&gt;。。。&lt;BR&gt;&amp;nbsp;// TODO: Add extra initialization here&lt;BR&gt;&amp;nbsp;HTREEITEM hRoot = m_Tree.InsertItem("Root");&lt;BR&gt;&amp;nbsp;m_Tree.SetCheck(hRoot);&lt;BR&gt;&amp;nbsp;m_Tree.InsertItem("Child1", hRoot);&lt;BR&gt;&amp;nbsp;m_Tree.InsertItem("Child2", hRoot);&lt;BR&gt;&amp;nbsp;m_Tree.Expand(hRoot, TVE_EXPAND );&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT style="BACKGROUND-COLOR: #a9a9a9" color=#0000ff&gt;&amp;nbsp;return TRUE;&lt;BR&gt;}&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;但是，对话框运行以后，树控件的根节点并没有显示被选中。用GetCheck测试，返回FALSE。但是&lt;BR&gt;如果在OnInitDialog里面SetCheck以后，立刻用GetCheck测试，发现返回的却是TRUE。&lt;/P&gt;
&lt;P&gt;进一步测试可以发现，对话框显示以后，任何时候再次使用SetCheck都没有问题了。&lt;/P&gt;
&lt;P&gt;二、问题分析&lt;/P&gt;
&lt;P&gt;由于无法直接监视树控件根节点状态的变化，为了弄清楚树控件究竟是什么时候修改了我们设置的&lt;BR&gt;check状态，我在其他消息响应函数中进一步用GetCheck检查，发现对话框在第一次OnPaint的时候&lt;BR&gt;，树控件根节点的check状态还是选中的，但紧接着下个消息来到后，它的状态就发生了改变。我&lt;BR&gt;思来想去肯定是树控件本身的问题。可能是它第一次画自己的时候，修改了每个节点的选择状态。&lt;/P&gt;
&lt;P&gt;但是它为何这么做？我开始猜测和它的图表列表有关系。为了验证这个想法，加入下面的代码：&lt;/P&gt;
&lt;P&gt;&lt;FONT style="BACKGROUND-COLOR: #d3d3d3" color=#0000ff&gt;&amp;nbsp;CImageList* pStateIcon = m_Tree.GetImageList(TVSIL_STATE);&lt;BR&gt;&amp;nbsp;int nItem = (pStateIcon == NULL)?(0):(pStateIcon-&amp;gt;GetImageCount());&lt;BR&gt;&amp;nbsp;CString str;&lt;BR&gt;&amp;nbsp;str.Format("Count of State Icon: %d\n", nItem);&lt;BR&gt;&amp;nbsp;TRACE(str);&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;果然不出所料，测试结果发现，在OnInitDialog里面，树控件虽然添加了数据，但是它的State Icon&lt;BR&gt;并没有加载，ImageList是空的。一旦它显示自己后，ImageList就不是NULL了，里面的图标数目是3。&lt;BR&gt;也就是说它使用了3个图标。&lt;/P&gt;
&lt;P&gt;显然是树控件在创建自己的时候，并没有加载所需的图标列表，而是在显示的时候，发现需要后，它才&lt;BR&gt;加载，并且重新复位了每个节点的选择状态。它之所以这么做，我想可能是出于效率方面的考虑。也就&lt;BR&gt;是说，如果用户没有添加TVS_HASBUTTONS，那么它就不需要这个图标列表了。&lt;/P&gt;
&lt;P&gt;三、解决问题&lt;/P&gt;
&lt;P&gt;既然知道了问题的所在，那么鉴于在对话框初始化的时候，设置树控件的数据和选择状态是一个常规的&lt;BR&gt;做法，我就不打算修改这些代码的位置，而采用提前给它加载图标列表的方法。方法如下：&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp; 1、在资源中添加一个位图资源IDB_BITMAP1，尺寸拉伸成48*16。（长48，高16，共三个图标）&lt;BR&gt;&amp;nbsp;&amp;nbsp; 2、在位图上面画三个图标，每个大小都是16*16，第二个是X，第三个是选择（对勾）。至于第一个&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 的用途，还不清楚。&lt;BR&gt;&amp;nbsp;&amp;nbsp; 3、在对话框的头文件中，添加一个CImageList对象：&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CImageList m_ImageList;&lt;BR&gt;&amp;nbsp;&amp;nbsp; 4、在对话框初始化函数中，添加如下代码：&lt;/P&gt;
&lt;P&gt;&lt;FONT style="BACKGROUND-COLOR: #d3d3d3" color=#0000ff&gt;&amp;nbsp;m_ImageList.Create(IDB_BITMAP1, 16,3,RGB(255,255,255));&lt;BR&gt;&amp;nbsp;m_Tree.SetImageList(&amp;amp;m_ImageList, TVSIL_STATE);&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;运行以后，发现问题已经解决。只不过树控件的状态图标已经换成了自己的，可以弄得更好看一些。&lt;/P&gt;
&lt;P&gt;以上代码的测试环境[win98 / vc6.0]&lt;/P&gt;
&lt;P&gt;2006/2/17 iwaswzq&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;img src ="http://blog.vckbase.com/iwaswzq/aggbug/17836.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>九月鹰飞</dc:creator><title>scanf中有意思的过滤</title><link>http://blog.vckbase.com/iwaswzq/archive/2005/10/20/13646.html</link><pubDate>Thu, 20 Oct 2005 10:46:00 GMT</pubDate><guid>http://blog.vckbase.com/iwaswzq/archive/2005/10/20/13646.html</guid><wfw:comment>http://blog.vckbase.com/iwaswzq/comments/13646.html</wfw:comment><comments>http://blog.vckbase.com/iwaswzq/archive/2005/10/20/13646.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://blog.vckbase.com/iwaswzq/comments/commentRss/13646.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/iwaswzq/services/trackbacks/13646.html</trackback:ping><description>1、scanf输入数据的时候，它的参数表达式有过滤功能。比如：&lt;BR&gt;int i,j,k;&lt;BR&gt;scanf("%d空格%d空格%d空格",&amp;amp;i,&amp;amp;j,&amp;amp;k);&lt;BR&gt;你输入 10空格20空格30回车， scanf开始处理，它把10 读入到i中，看到后面的空格，会自动删除之。虽然&lt;BR&gt;最后30后面没有空格也没有关系。但是如果10后面或者20后面没有空格，或者是其它字符，比如：&lt;BR&gt;10,20空格30回车，输入就会出错。当然多个空格也没有关系，例如：10空格空格20空格30回车。因为扫描&lt;BR&gt;整数的时候会自动删除多余的空格。&lt;BR&gt;如果用scanf("%d空格,%d,空格%d",&amp;amp;i,&amp;amp;j,&amp;amp;k); //注意空格和逗号的顺序，则输入和结果的比较是：&lt;BR&gt;10空格,20,空格30&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //和表达式正好匹配，没有问题&lt;BR&gt;10,20,30&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //ok&lt;BR&gt;10,空格20,空格30&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //ok&lt;BR&gt;10,空格20空格,30&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //出错&lt;BR&gt;10空格20空格30&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //更错&lt;BR&gt;2、过滤具有多重功能。例如下面输入字符的例子：&lt;BR&gt;&amp;nbsp;char c;&lt;BR&gt;&amp;nbsp;for(;;)&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;scanf("%cabc",&amp;amp;c);&lt;BR&gt;&amp;nbsp;&amp;nbsp;if(c=='q')break;&lt;BR&gt;&amp;nbsp;&amp;nbsp;printf("c=%c\n",c);&lt;BR&gt;&amp;nbsp;}&lt;BR&gt;则在输入的字符串里面遇到a, ab, abc, 会自动删除。例如输入：&lt;BR&gt;1a2ab3abc4bc5ba6aa7abca8&lt;BR&gt;输出结果是：&lt;BR&gt;c=1&lt;BR&gt;c=2&lt;BR&gt;c=3&lt;BR&gt;c=4&lt;BR&gt;c=b&lt;BR&gt;c=c&lt;BR&gt;c=5&lt;BR&gt;c=b&lt;BR&gt;c=6&lt;BR&gt;c=a&lt;BR&gt;c=7&lt;BR&gt;c=a&lt;BR&gt;c=8&lt;BR&gt;&lt;BR&gt;可以看到实际的过滤情况，但是有意思的一点是scanf不会过滤bc，而且一旦成功过滤一组输入，&lt;BR&gt;接下来的数据就会直接读入，例如遇到aa的情况，过滤掉a以后，下一个a被成功读入。&lt;BR&gt;&lt;BR&gt;而且上面的例子，如果输入一些数据后，再输入q退出，则还会输出一个c=， 表明读入了一个回车符，&lt;BR&gt;真的很奇怪。可以如下测试：&lt;BR&gt;&amp;nbsp;char c;&lt;BR&gt;&amp;nbsp;for(;;)&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;scanf("%cabc",&amp;amp;c);&lt;BR&gt;&amp;nbsp;&amp;nbsp;if(c=='q')break;&lt;BR&gt;&amp;nbsp;&amp;nbsp;if(c!='\n')&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;printf("c=%c\n",c);&lt;BR&gt;&amp;nbsp;&amp;nbsp;else &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;printf("haha!\n");&lt;BR&gt;&amp;nbsp;}&lt;BR&gt;3、再做进一步的测试，会发现更诡异的事情，把上面的代码改成：&lt;BR&gt;&amp;nbsp;&amp;nbsp;scanf("%c abc",&amp;amp;c); //注意abc前面的空格&lt;BR&gt;你会发现输入q以后，程序并没有退出，还需要再输入一个字符。改成scanf("%c",&amp;amp;c);就没有问题了。&lt;BR&gt;&lt;BR&gt;上述现象应该不是什么语法问题，是scanf这个函数本身的代码产生的问题。诸如下面这样的代码：&lt;BR&gt;char a[20];&lt;BR&gt;&amp;nbsp;scanf("%[A-Za-z ]s", a); &lt;BR&gt;因为很少用到它，我不想再去研究了，谁感兴趣谁去弄吧，呵呵。&lt;BR&gt;&lt;img src ="http://blog.vckbase.com/iwaswzq/aggbug/13646.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>九月鹰飞</dc:creator><title>关于 Gallery</title><link>http://blog.vckbase.com/iwaswzq/archive/2005/07/21/9873.html</link><pubDate>Wed, 20 Jul 2005 18:22:00 GMT</pubDate><guid>http://blog.vckbase.com/iwaswzq/archive/2005/07/21/9873.html</guid><wfw:comment>http://blog.vckbase.com/iwaswzq/comments/9873.html</wfw:comment><comments>http://blog.vckbase.com/iwaswzq/archive/2005/07/21/9873.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://blog.vckbase.com/iwaswzq/comments/commentRss/9873.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/iwaswzq/services/trackbacks/9873.html</trackback:ping><description>&lt;P&gt;关于 Gallery&lt;/P&gt;
&lt;P&gt;-----------------------------------------------------&lt;BR&gt;前言：&lt;BR&gt;&amp;nbsp; &lt;BR&gt;&amp;nbsp;&amp;nbsp; 想在工程里面插入一个ActiveX控件，却发现我的VC找不到&lt;BR&gt;任何注册的东西，深感奇怪。看了一晚上msdn中关于 Gallery&lt;BR&gt;的东西，终于有所收获，不敢独享，特此贴出。&lt;BR&gt;-----------------------------------------------------&lt;/P&gt;
&lt;P&gt;说明：&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp; Visual C++ 6 通过 Gallery 实现代码重用。你可以在自己的工程中从这里&lt;BR&gt;添加很多东西，从而增强自己程序的功能，比如图像控件、图表控件等等。&lt;/P&gt;
&lt;P&gt;Gallery里面的控件被分成两类：&lt;/P&gt;
&lt;P&gt;1、Visual C++ Components&lt;BR&gt;2、Registered ActiveX Controls&lt;/P&gt;
&lt;P&gt;以及其它用户自己产生的控件类和第三方的控件或者组件。&lt;/P&gt;
&lt;P&gt;*****************************************************&lt;BR&gt;一、访问 Gallery &lt;/P&gt;
&lt;P&gt;在&amp;#8220;工程&amp;#8221;菜单，选择&amp;#8220; Add To Project&amp;#8221;，然后点&amp;#8220;Components and Controls&amp;#8221;命令，则vc打开 Gallery 对话框，和打开文件对话框一样。&lt;/P&gt;
&lt;P&gt;Gallery 目录里面缺省包含两个子目录，一个是&amp;#8220;Visual C++ Components&amp;#8221;，一个是&amp;#8220;Registered ActiveX Controls&amp;#8221;，另外其它的目录都是用户自己的控件目录。&lt;/P&gt;
&lt;P&gt;这个 Gallery 目录一般位于Visual C++软件目录下面的MSDEV98子目录里面。对于这些个目录有如下&lt;BR&gt;说明：&lt;/P&gt;
&lt;P&gt;（1）、不允许移动或者改名。如果你这么做了，这些目录会自动创建新的。&lt;BR&gt;（2）、里面包含的仅仅是各种控件的快捷方式和注册信息。&lt;BR&gt;（3）、不要用来保存其它文件。&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;*****************************************************&lt;BR&gt;二、关于两个子目录&lt;/P&gt;
&lt;P&gt;Visual C++ Components 目录包含了系统中注册的Visual C++ 组件(.DLL files)的快捷方式。Registered ActiveX Controls 目录包含的是所有在系统中注册的ActiveX控件(包括.OCX或者.DLL)的link。&lt;/P&gt;
&lt;P&gt;这些组件要么是安装VC的时候自动注册到系统中的，要么是后来运行某些程序的时候注册的。&lt;/P&gt;
&lt;P&gt;每次打开 Gallery 目录，它都会根据当前注册表的内容，重新产生所有注册组件的链接。也就是说:&lt;/P&gt;
&lt;P&gt;1、如果你在这个目录里面删除了指向某个注册组件的link，则下次打开 Gallery的时候，这个link会再次出现。&lt;/P&gt;
&lt;P&gt;2、如果你删除了一个注册组件，但是没有unregister它，则 Gallery中仍然有指向它的link。但是这个link不能用了。&lt;/P&gt;
&lt;P&gt;3、如果你复制了一个组件在你的计算机上，但是没有注册，则它的link不会出现在 Gallery 里面。&lt;/P&gt;
&lt;P&gt;*****************************************************&lt;BR&gt;三、向工程中添加 Gallery 中的组件&lt;/P&gt;
&lt;P&gt;使用 Gallery，可以很方便的向工程中添加组件或者 ActiveX 控件。或者重用以前的代码。只需选中要添加的东西，然后单击"Insert"按钮。&lt;/P&gt;
&lt;P&gt;添加的具体效果取决于你选中什么。例如，如果你选择的是一个用户自定义组件(OGX)，则会在你的工程里面添加头文件和CPP文件以及相应的资源。&lt;/P&gt;
&lt;P&gt;假如你选择插入的是一个 ActiveX控件，则Gallery会生成一个叫做ActiveX control wrapper的编程接口，控件通过这个接口和你的程序进行通讯。这个接口包括一个头文件和一个cpp文件。&lt;/P&gt;
&lt;P&gt;一般来说，每个 Gallery 目录中的控件都有帮助，你可以选择一个，然后点"More Info"按钮查看。其它第三方的控件一般也有自己的文档可以参考。&lt;/P&gt;
&lt;P&gt;注意，下列工程中无法从 Gallery 中添加 ActiveX控件：&lt;/P&gt;
&lt;P&gt;1、不使用MFC的ATL工程；&lt;BR&gt;2、Win32 工程；&lt;BR&gt;3、其它任何没有clw 文件的工程。&lt;/P&gt;
&lt;P&gt;这些类型的工程中如果需要使用某个 ActiveX 控件，可以从对话框编辑器的右键菜单中选择&amp;#8220;Insert ActiveX Control&amp;#8221;，然后从打开的对话框中选择你需要的控件。如果该对话框是基于mfc的，则wizards会支持它，但是控件本身不会出现在那个controls资源工具条上面。因为我的VC的 Gallery 里面没有任何东西，所以我用这个方法在我的对话框上面加上了ActiveX控件，但是可惜的是没有添加任何头文件和cpp文件，因此看不到这个控件的源代码，感到愤愤不平。&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;下面是向工程中添加注册 ActiveX 控件的步骤：&lt;BR&gt;1、打开工程的工作空间&lt;BR&gt;2、如果该工作空间中有多个工程，请在&amp;#8220;工程&amp;#8221;菜单中选择&amp;#8220;设置活动工程&amp;#8221;命令。&lt;BR&gt;3、在&amp;#8220;工程&amp;#8221;菜单，选择&amp;#8220;添加工程&amp;#8221;，然后选择"Components and Controls" 。&lt;BR&gt;4、在 Gallery 中打开Registered ActiveX Controls 文件夹，选择你所需的控件。然后点&amp;#8220;Insert&amp;#8221;按钮。&lt;BR&gt;5、出现请求确认对话框，确认即可。&lt;/P&gt;
&lt;P&gt;添加组件的步骤如下：&lt;/P&gt;
&lt;P&gt;1，2，3同上面。&lt;BR&gt;4、在 Gallery 中打开 Visual C++ Components 文件夹，然后选择你需要的组件。然后点&amp;#8220;Insert&amp;#8221;。&lt;BR&gt;5、然后一般会出现一个提示框，让你设置特定组件的属性和选项，有些组件是不需要任何设置信息的。&lt;BR&gt;6、点 OK以后，Gallery会把组件中包含的文件和资源复制到当前的活动工程中。对于有些组件仅仅是修改你的工程文件和资源，而不会添加任何文件。你可以使用 ClassView 查看新加入的组件类，或者到ResourceView中查看新加入的资源。&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;*****************************************************&lt;BR&gt;四、向 Gallery 中添加东西&lt;/P&gt;
&lt;P&gt;你可以通过Gallery 搜集整理自己的重用代码。这一点很有用途，各位看官切记。&lt;/P&gt;
&lt;P&gt;可以把自己工程中的某个vc++类转换成组件保存到Gallery中。 Gallery会自动创建一个组件（OGX）打包你的类，包括所有相关的文件和资源。你在也不需要使用剪切/复制的方法了，同时再也不用担心名字冲突了，因为 Gallery 会自动处理所有命名问题。&lt;/P&gt;
&lt;P&gt;注意不要把ATL 类添加到 Gallery中。对于 ATL类，可能显示添加成功，但是实际上它们包含的某些东西无法保存到 Gallery中，所以以后使用的时候会产生问题。&lt;/P&gt;
&lt;P&gt;添加类的步骤如下：&lt;BR&gt;1、打开包含目标类的工程，如果里面有多个project，注意设置活动工程。&lt;BR&gt;2、在 ClassView 中鼠标右击目标类，然后选择 "Add to Gallery"菜单命令。&lt;BR&gt;然后在 Gallery 中就会创建一个新的文件夹，名字就是当前工程的名字，你打开它就可以看到里面有一个OGX组件。其它的工程就可以使用它了。多方便啊！&lt;BR&gt;在 Gallery中，你可以重命名、删除或者复制OGX组件，只需用鼠标右键单击它们，然后选择相应命令即可。&lt;/P&gt;
&lt;P&gt;不过需要注意的是，最好不要把OGX组件移动到那两个缺省 Gallery目录中。&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;如果要添加第三方组件或者ActiveX控件到 Gallery 中，需要运行该控件的安装程序，安装程序所做的工作一般包括：下载控件，注册，在Registered ActiveX Controls 文件夹中添加快捷方式等。但是并不是所有控件都有安装程序，如果没有的话，就无法添加到 Gallery中了。&lt;/P&gt;
&lt;P&gt;我上面说到的那个问题，虽然控件成功地注册到系统中，但是 Gallery 中找不到它。用鼠标右键的方法加入工程中又不好用，幸运的是我下载了一个示例工程，里面用到了这个控件，所以里面也有这个控件的一个类（h和cpp)，我采用上面所说的保存类的方法，在 Gallery中保存了一个OGX组件，这样一来，其它工程就可以使用了,很是方便，哈哈。&lt;/P&gt;
&lt;P&gt;有些时候注册组件是通过 Regsvr32.exe ，有些情况下是通过inf文件安装的。&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;*****************************************************&lt;BR&gt;五、管理 Gallery里面的东西&lt;/P&gt;
&lt;P&gt;Gallery最初把组件分成两组，放在两个缺省子文件夹中，你可以按照自己的喜好组织和管理里面的组件。例如创建新的文件夹保存新的组件，可以删除、重命名或者重组你自己创建的文件夹。&lt;/P&gt;
&lt;P&gt;当你创建了自己的某个文件夹以后，你可以在资源管理器中，把两个缺省文件夹里面的组件拖动到你的文件夹中。因为都是快捷方式，所以下次启动 Gallery，缺省目录里面的link会自动恢复。而你的文件夹里面已经有link了。&lt;/P&gt;
&lt;P&gt;这些操作都很简单，就不说了。需要注意的是如果要删除Gallery里面的组件，需要先使用&lt;/P&gt;
&lt;P&gt;Regsvr32.exe&amp;nbsp; /u &lt;/P&gt;
&lt;P&gt;命令卸载组件，然后从Gallery中删除。&lt;/P&gt;
&lt;P&gt;虽然你可以删除两个缺省子文件夹，但是下次启动Visual C++，它们又会出现。而且里面的快捷方式(link)也会恢复。但是切记，如果删除了 Gallery 目录，则下次启动Visual C++后，虽然 Gallery 目录恢复，但是里面就空了。&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;iwaswzq 2005/7/21凌晨&lt;/P&gt;&lt;img src ="http://blog.vckbase.com/iwaswzq/aggbug/9873.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>九月鹰飞</dc:creator><title>宏定义里面的换行</title><link>http://blog.vckbase.com/iwaswzq/archive/2005/07/07/9306.html</link><pubDate>Thu, 07 Jul 2005 15:12:00 GMT</pubDate><guid>http://blog.vckbase.com/iwaswzq/archive/2005/07/07/9306.html</guid><wfw:comment>http://blog.vckbase.com/iwaswzq/comments/9306.html</wfw:comment><comments>http://blog.vckbase.com/iwaswzq/archive/2005/07/07/9306.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://blog.vckbase.com/iwaswzq/comments/commentRss/9306.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/iwaswzq/services/trackbacks/9306.html</trackback:ping><description>&lt;P&gt;有个朋友要把嵌入式汇编代码块定义成宏，例如&lt;BR&gt;&lt;BR&gt;void func()&lt;BR&gt;{&lt;BR&gt;&amp;nbsp; ...&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; __asm {&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; mov&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; dx, 0&amp;nbsp;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; mov&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ax, 0&amp;nbsp;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; };&lt;BR&gt;&amp;nbsp;&amp;nbsp; ...&lt;BR&gt;}&lt;BR&gt;用宏定义这个代码块的时候因为必须使用续行符\， 所以想来想去只有这样了：&lt;BR&gt;&lt;BR&gt;#define MYINLINEASM&amp;nbsp; __asm{ \&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; __asm&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; mov&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; dx, 0 \&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; __asm&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; mov&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ax, 0 \&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; __asm&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; };&lt;BR&gt;&lt;BR&gt;（其实括号不要也行，因为这里每行都有__asm关键字了）&lt;/P&gt;
&lt;P&gt;void func()&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;&amp;nbsp; ...&lt;BR&gt;&amp;nbsp; &amp;nbsp;MYINLINEASM&lt;BR&gt;&amp;nbsp;&amp;nbsp; ...&lt;BR&gt;}&lt;BR&gt;&lt;BR&gt;否则的话，会由于续行符的问题引起编译错误，例如：&lt;BR&gt;#define MYINLINEASM&amp;nbsp; __asm{ \&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;mov&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; dx, 0 \&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp; mov&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ax, 0 \&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; };&lt;BR&gt;&lt;BR&gt;这样就不行了。过去几天了，又想起来，特此记之。&lt;BR&gt;&lt;/P&gt;&lt;img src ="http://blog.vckbase.com/iwaswzq/aggbug/9306.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>九月鹰飞</dc:creator><title>关于可变参数函数的若干问题</title><link>http://blog.vckbase.com/iwaswzq/archive/2005/06/20/6912.html</link><pubDate>Mon, 20 Jun 2005 07:17:00 GMT</pubDate><guid>http://blog.vckbase.com/iwaswzq/archive/2005/06/20/6912.html</guid><wfw:comment>http://blog.vckbase.com/iwaswzq/comments/6912.html</wfw:comment><comments>http://blog.vckbase.com/iwaswzq/archive/2005/06/20/6912.html#Feedback</comments><slash:comments>20</slash:comments><wfw:commentRss>http://blog.vckbase.com/iwaswzq/comments/commentRss/6912.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/iwaswzq/services/trackbacks/6912.html</trackback:ping><description>&lt;P&gt;&lt;BR&gt;关于可变参数函数的若干问题&lt;/P&gt;
&lt;P&gt;iwaswzq 2005/6/15&lt;/P&gt;
&lt;P&gt;------------------------------------------------------------------&lt;/P&gt;
&lt;P&gt;c/c++支持可变参数的函数，即函数的参数是不确定的。&lt;/P&gt;
&lt;P&gt;一、为什么要使用可变参数的函数？&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 一般我们编程的时候，函数中形式参数的数目通常是确定的，在调用时要依次给出与形式参数对应的所有实际参数。但在某些情况下希望函数的参数个数可以根据需要确定，因此c语言引入可变参数函数。这也是c功能强大的一个方面，其它某些语言，比如fortran就没有这个功能。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 典型的可变参数函数的例子有大家熟悉的printf()、scanf()等。&lt;/P&gt;
&lt;P&gt;二、c/c++如何实现可变参数的函数？&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 为了支持可变参数函数，C语言引入新的调用协议， 即C语言调用约定 __cdecl 。 采用C/C++语言编程的时候，默认使用这个调用约定。如果要采用其它调用约定，必须添加其它关键字声明，例如WIN32 API使用PASCAL调用约定，函数名字之前必须加__stdcall关键字。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 采用C调用约定时，函数的参数是从右到左入栈，个数可变。由于函数体不能预先知道传进来的参数个数，因此采用本约定时必须由函数调用者负责堆栈清理。举个例子：&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp; //C调用约定函数&lt;BR&gt;&amp;nbsp;&amp;nbsp; int __cdecl Add(int a, int b)&lt;BR&gt;&amp;nbsp;&amp;nbsp; {&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; return (a + b);&lt;BR&gt;&amp;nbsp;&amp;nbsp; }&lt;BR&gt;&amp;nbsp;&amp;nbsp; &lt;BR&gt;&amp;nbsp;&amp;nbsp; 函数调用：&lt;BR&gt;&amp;nbsp;&amp;nbsp; Add(1, 2);&lt;BR&gt;&amp;nbsp;&amp;nbsp; //汇编代码是：&lt;BR&gt;&amp;nbsp;&amp;nbsp; push&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 2&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ;参数b入栈&lt;BR&gt;&amp;nbsp;&amp;nbsp; push&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ;参数a入栈&lt;BR&gt;&amp;nbsp;&amp;nbsp; call&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; @Add&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ;调用函数。其实还有编译器用于定位函数的表达式这里把它省略了&lt;BR&gt;&amp;nbsp;&amp;nbsp; add&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; esp,8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ;调用者负责清栈&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 如果调用函数的时候使用的调用协议和函数原型中声明的不一致，就会导致栈错误，这是另外一个话题，这里不再细说。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 另外c/c++编译器采用宏的形式支持可变参数函数。这些宏包括va_start、va_arg和va_end等。之所以这么做，是为了增加程序的可移植性。屏蔽不同的硬件平台造成的差异。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 支持可变参数函数的所有宏都定义在stdarg.h 和 varargs.h中。例如标准ANSI形式下，这些宏的定义是：&lt;/P&gt;
&lt;P&gt;&amp;nbsp;typedef char *&amp;nbsp; va_list;&amp;nbsp; //字符串指针&lt;/P&gt;
&lt;P&gt;&amp;nbsp;#define _INTSIZEOF(n)&amp;nbsp;&amp;nbsp; ( (sizeof(n) + sizeof(int) - 1) &amp;amp; ~(sizeof(int) - 1) )&lt;/P&gt;
&lt;P&gt;&amp;nbsp;#define va_start(ap,v)&amp;nbsp; ( ap = (va_list)&amp;amp;v + _INTSIZEOF(v) )&lt;BR&gt;&amp;nbsp;#define va_arg(ap,t)&amp;nbsp;&amp;nbsp;&amp;nbsp; ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )&lt;BR&gt;&amp;nbsp;#define va_end(ap)&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ( ap = (va_list)0 )&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 使用宏_INTSIZEOF是为了按照整数字节对齐指针，因为c调用协议下面，参数入栈都是整数字节（指针或者值）。&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;三、如何定义这类的函数。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 可变参数函数在不同的系统下，采用不同的形式定义。&lt;/P&gt;
&lt;P&gt;1、用ANSI标准形式时，参数个数可变的函数的原型声明是： &lt;/P&gt;
&lt;P&gt;&amp;nbsp;type funcname(type para1, type para2, ...);&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 关于这个定义，有三点需要说明：&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 一般来说，这种形式至少需要一个普通的形式参数，可变参数就是通过三个'.'来定义的。所以"..."不表示省略，而是函数原型的一部分。type是函数返回值和形式参数的类型。&lt;BR&gt;例如：&lt;/P&gt;
&lt;P&gt;&amp;nbsp;int&amp;nbsp; MyPrintf(char const* fmt, ...);&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 但是，我们也可以这样定义函数：&lt;/P&gt;
&lt;P&gt;&amp;nbsp;void MyFunc(...);&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 但是，这样的话，我们就无法使用函数的参数了，因为无法通过上面所讲的宏来提取每个参数。所以除非你的函数代码中的确没有用到参数表中的任何参数，否则必须在参数表中使用至少一个普通参数。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 注意，可变参数只能位于函数参数表的最后。不能这样：&lt;/P&gt;
&lt;P&gt;&amp;nbsp;void MyFunc(..., int i);&lt;/P&gt;
&lt;P&gt;2、采用与UNIX 兼容系统下的声明方式时，参数个数可变的函数原型是： &lt;/P&gt;
&lt;P&gt;&amp;nbsp;type funcname(va_alist);&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 但是要求函数实现的时候，函数名字后面必须加上va_dcl。例如：&lt;/P&gt;
&lt;P&gt;&amp;nbsp; #include &amp;lt;varargs.h&amp;gt;&lt;BR&gt;&amp;nbsp; int average( va_list );&lt;/P&gt;
&lt;P&gt;&amp;nbsp; void main( void )&lt;BR&gt;&amp;nbsp; {&lt;BR&gt;&amp;nbsp;&amp;nbsp; 。。。//代码&lt;BR&gt;&amp;nbsp; }&lt;/P&gt;
&lt;P&gt;&amp;nbsp; /* UNIX兼容形式*/&lt;BR&gt;&amp;nbsp; int average( va_alist )&lt;BR&gt;&amp;nbsp; va_dcl&lt;BR&gt;&amp;nbsp; {&lt;BR&gt;&amp;nbsp;&amp;nbsp; 。。。//代码&lt;BR&gt;&amp;nbsp; }&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 这种形式不需要提供任何普通的形式参数。type是函数返回值的类型。va_dcl是对函数原型声明中参数va_alist的详细声明，实际是一个宏定义。根据平台的不同，va_dcl的定义稍有不同。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 在varargs.h中，va_dcl的定义后面已经包括了一个分号。因此函数实现的时候，va_dcl后不再需要加上分号了。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;BR&gt;3、采用头文件stdarg.h编写的程序是符合ANSI标准的，可以在各种操作系统和硬件上运行；而采用头文件varargs.h的方式仅仅是为了与以前的程序兼容，两种方式的基本原理是一致的，只是在语法形式上有一些细微的区别。 所以一般编程的时候使用stdarg.h。下面的所有例子代码都采用ANSI标准格式。&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;四、可变参数函数的基本使用方法&lt;/P&gt;
&lt;P&gt;下面通过若干例子，说明如何实现可变参数函数的定义和调用。&lt;/P&gt;
&lt;P&gt;//================================ 例子程序1 ===============&lt;BR&gt;#include &amp;lt; stdio.h &amp;gt;&lt;BR&gt;#include &amp;lt; string.h &amp;gt;&lt;BR&gt;#include &amp;lt; stdarg.h &amp;gt;&lt;/P&gt;
&lt;P&gt;/* 函数原型声明，至少需要一个确定的参数，注意括号内的省略号 */&lt;BR&gt;int demo( char *, ... );&amp;nbsp;&lt;/P&gt;
&lt;P&gt;void main( void )&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;demo("DEMO", "This", "is", "a", "demo!", "\0");&lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;int demo( char *msg, ... )&amp;nbsp;&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;va_list argp;&amp;nbsp;/* 定义保存函数参数的结构 */&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;int argno = 0;&amp;nbsp;/* 纪录参数个数 */&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;char *para;&amp;nbsp;/* 存放取出的字符串参数 */&lt;/P&gt;
&lt;P&gt;&amp;nbsp;// 使用宏va_start, 使argp指向传入的第一个可选参数，&lt;BR&gt;&amp;nbsp;// 注意 msg是参数表中最后一个确定的参数，并非参数表中第一个参数&lt;BR&gt;&amp;nbsp;va_start( argp, msg );&amp;nbsp;&lt;BR&gt;&amp;nbsp;&amp;nbsp; &lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;while (1)&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;//取出当前的参数，类型为char *&lt;BR&gt;&amp;nbsp;&amp;nbsp;//如果不给出正确的类型，将得到错误的参数&lt;BR&gt;&amp;nbsp;&amp;nbsp;para = va_arg( argp, char *);&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;if ( strcmp( para, "\0") == 0 )&amp;nbsp;/* 采用空串指示参数输入结束 */&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;break;&lt;BR&gt;&amp;nbsp;&amp;nbsp;printf("参数 #%d 是: %s\n", argno, para);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;argno++;&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;}&lt;BR&gt;&amp;nbsp;&amp;nbsp;va_end( argp );&amp;nbsp;/* 将argp置为NULL */&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;return 0;&lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;//输出结果&lt;BR&gt;参数 #0 是: This&lt;BR&gt;参数 #1 是: is&lt;BR&gt;参数 #2 是: a&lt;BR&gt;参数 #3 是: demo!&lt;/P&gt;
&lt;P&gt;注意到上面的例子没有使用第一个参数，下面的例子将使用所有参数&lt;/P&gt;
&lt;P&gt;//================================ 例子程序2 ===============&lt;/P&gt;
&lt;P&gt;#include &amp;lt;stdio.h&amp;gt;&lt;BR&gt;#include &amp;lt;stdarg.h&amp;gt;&lt;BR&gt;int average( int first, ... );&amp;nbsp; //输入若干整数，求它们的平均值&lt;/P&gt;
&lt;P&gt;void main( void )&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;&amp;nbsp; /* 调用3个整数(-1表示结尾) */&lt;BR&gt;&amp;nbsp;&amp;nbsp; printf( "Average is: %d\n", average(2,3,4, -1));&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp; /*调用4个整数*/&lt;BR&gt;&amp;nbsp;&amp;nbsp; printf( "Average is: %d\n", average(5,7,9, 11,-1));&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp; /*只有结束符的调用*/&lt;BR&gt;&amp;nbsp;&amp;nbsp; printf( "Average is: %d\n", average(-1) );&lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;/* 返回若干整数平均值的函数 */&lt;BR&gt;int average( int first, ... )&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;&amp;nbsp; int count = 0, sum = 0, i = first;&lt;BR&gt;&amp;nbsp;&amp;nbsp; va_list marker;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp; va_start( marker, first ); //初始化&lt;BR&gt;&amp;nbsp;&amp;nbsp; while( i != -1 )&lt;BR&gt;&amp;nbsp;&amp;nbsp; {&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; sum += i; //先加第一个参数&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; count++;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; i = va_arg( marker, int);//取下一个参数&lt;BR&gt;&amp;nbsp;&amp;nbsp; }&lt;BR&gt;&amp;nbsp;&amp;nbsp; va_end( marker );&lt;BR&gt;&amp;nbsp;&amp;nbsp; return( sum ? (sum / count) : 0 );&lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;//输出结果&lt;BR&gt;Average is: 3&lt;BR&gt;Average is: 8&lt;BR&gt;Average is: 0&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;五、关于可变参数的传递问题&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 有人问到这个问题，假如我定义了一个可变参数函数，在这个函数内部又要调用其它可变参数函数，那么如何传递参数呢？上面的例子都是使用宏va_arg逐个把参数提取出来使用，能否不提取，直接把它们传递给另外的函数呢？&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 我们先看printf的实现：&lt;/P&gt;
&lt;P&gt;int __cdecl printf (const char *format, ...)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; va_list arglist;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; int buffing;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; int retval;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; va_start(arglist, format); //arglist指向format后面的第一个参数&lt;/P&gt;
&lt;P&gt;&amp;nbsp;。。。//不关心其它代码&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; retval = _output(stdout,format,arglist); //把format格式和参数传递给output函数&lt;/P&gt;
&lt;P&gt;&amp;nbsp;。。。//不关心其它代码&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; return(retval);&lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;我们先模仿这个函数写一个：&lt;/P&gt;
&lt;P&gt;#include &amp;lt;stdio.h&amp;gt;&lt;BR&gt;#include &amp;lt;stdarg.h&amp;gt;&lt;/P&gt;
&lt;P&gt;int mywrite(char *fmt, ...)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; va_list arglist;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; va_start(arglist, fmt);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; return printf(fmt,arglist);&lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;void main()&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;int i=10, j=20;&lt;BR&gt;&amp;nbsp;char buf[] = "This is a test";&lt;BR&gt;&amp;nbsp;double f= 12.345;&lt;BR&gt;&amp;nbsp;mywrite("String: %s\nInt: %d, %d\nFloat :%4.2f\n", buf, i, j, f);&lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 运行一下看看，哈，错误百出。仔细分析原因，根据宏的定义我们知道 arglist是一个指针，它指向第一个可变的参数，但是所有的参数都位于栈中，所以arglist指向栈中某个位置，通过arglist的值，我们可以直接查看栈里面的内容：&lt;/P&gt;
&lt;P&gt;arglist -&amp;gt; 指向栈里面，内容包括&lt;/P&gt;
&lt;P&gt;0067FD78&amp;nbsp; E0 FD 67 00&amp;nbsp; //指向字符串"This is a test"&lt;BR&gt;0067FD7C&amp;nbsp; 0A 00 00 00&amp;nbsp; //整数 i 的值&lt;BR&gt;0067FD80&amp;nbsp; 14 00 00 00&amp;nbsp; //整数 j 的值&lt;BR&gt;0067FD84&amp;nbsp; 71 3D 0A D7&amp;nbsp; //double 变量 f, 占用8个字节&lt;BR&gt;0067FD88&amp;nbsp; A3 B0 28 40 &lt;BR&gt;0067FD8C&amp;nbsp; 00 00 00 00 &lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 如果直接调用 printf(fmt, arglist); 仅仅是把arglist指针的值0067FD78入栈，然后把格式字符串入栈，相当于调用：&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; printf(fmt, 0067FD78);&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 自然这样的调用肯定会出现错误。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 我们能不能逐个把参数提取出来，再传递给其它函数呢？先考虑一次性把所有参数传递进去的问题。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 如果调用的是系统库函数，这种情况下是不可能的。因为提取参数是在运行态，而参数入栈是在编译的时候确定的。无法让编译器预知运行态的事情给出正确的参数入栈代码。而我们在运行态虽然可以提取每个参数，但是无法将参数一次性全部压栈，即使使用汇编代码实现起来也是很困难的，因为不单是一个简单的push代码就可以做到。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 如果接受参数的函数也是我们自己写的，自然我们可以把arglist指针入栈，然后在函数中自己解析arglist指针里面的参数，逐个提取出来处理。但是这样做似乎没有什么意义，一方面，这个函数没有必要也做成可变参数函数，另一方面直接在第一个函数中解析参数，然后处理不是更简单么？&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 我们唯一可以做到的是，逐个解析参数，然后循环中调用其它可变参数函数，每次传递一个参数。这里又有一个问题，就是参数表中的不可变参数的传递问题，有些情况下不能简单的传递，以上面的例子为例， 通常我们解析参数的同时，还需要解析格式字符串：&lt;/P&gt;
&lt;P&gt;#include &amp;lt;windows.h&amp;gt;&lt;BR&gt;#include &amp;lt;stdio.h&amp;gt;&lt;BR&gt;#include &amp;lt;stdarg.h&amp;gt;&lt;/P&gt;
&lt;P&gt;//测试一下这个，开个玩笑&lt;BR&gt;void t(...)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;printf("\n");&lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;int mywrite(char *fmt, ...)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; va_list arglist;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; va_start(arglist, fmt);&lt;/P&gt;
&lt;P&gt;&amp;nbsp; char temp[255];&lt;BR&gt;&amp;nbsp; strcpy(temp, fmt);&amp;nbsp;//Copy the Format string&lt;BR&gt;&amp;nbsp; char Format[255];&lt;/P&gt;
&lt;P&gt;&amp;nbsp; char *p = strchr(temp,'%');&lt;BR&gt;&amp;nbsp; int i=0;&lt;BR&gt;&amp;nbsp; int iParam;&lt;BR&gt;&amp;nbsp; double fParam;&lt;BR&gt;&amp;nbsp; while(p != NULL)&lt;BR&gt;&amp;nbsp; {&lt;BR&gt;&amp;nbsp;&amp;nbsp; while((*p&amp;lt;'a' || *p&amp;gt;'z') &amp;amp;&amp;amp; (*p!=0) ) p++;&lt;BR&gt;&amp;nbsp;&amp;nbsp; if(*p == 0)break;&lt;BR&gt;&amp;nbsp;&amp;nbsp; p++;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp; //格式字符串&lt;BR&gt;&amp;nbsp;&amp;nbsp; int nChar = p - temp;&lt;BR&gt;&amp;nbsp;&amp;nbsp; strncpy(Format,temp, nChar);&lt;BR&gt;&amp;nbsp;&amp;nbsp; Format[nChar] = 0;&lt;BR&gt;&amp;nbsp;&amp;nbsp; //参数&lt;BR&gt;&amp;nbsp;&amp;nbsp; if(Format[nChar-1] != 'f')&lt;BR&gt;&amp;nbsp;&amp;nbsp; {&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; iParam = va_arg( arglist, int);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; printf(Format, iParam);&lt;BR&gt;&amp;nbsp;&amp;nbsp; }&lt;BR&gt;&amp;nbsp;&amp;nbsp; else&lt;BR&gt;&amp;nbsp;&amp;nbsp; {&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; fParam = va_arg( arglist, double);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; printf(Format, fParam);&lt;BR&gt;&amp;nbsp;&amp;nbsp; }&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp; i++;&lt;BR&gt;&amp;nbsp;&amp;nbsp; if(*p == 0) break;&lt;BR&gt;&amp;nbsp;&amp;nbsp; strcpy(temp, p);&lt;BR&gt;&amp;nbsp;&amp;nbsp; p = strchr(temp, '%');&lt;BR&gt;&amp;nbsp; }&lt;BR&gt;&amp;nbsp; if(temp[0] != 0)&lt;BR&gt;&amp;nbsp;&amp;nbsp; printf(temp);&lt;/P&gt;
&lt;P&gt;&amp;nbsp; return i;&lt;/P&gt;
&lt;P&gt;}&lt;/P&gt;
&lt;P&gt;void main()&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;int i=10, j=20;&lt;BR&gt;&amp;nbsp;char buf[] = "This is a test";&lt;BR&gt;&amp;nbsp;double f= 123.456;&lt;BR&gt;&amp;nbsp;mywrite("String: %s\nInt: %d, %d\nFloat :%4.2f\nEnd", buf, i, j, f, 0);&lt;BR&gt;&amp;nbsp;t("aaa", i);&lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;//输出：&lt;BR&gt;String: This is a test&lt;BR&gt;Int: 10, 20&lt;BR&gt;Float :123.46&lt;BR&gt;End&lt;/P&gt;
&lt;P&gt;当然这里的解析是不完善的。&lt;BR&gt;----------------------------------------&lt;BR&gt;iwaswzq 2005/6/15&lt;/P&gt;&lt;img src ="http://blog.vckbase.com/iwaswzq/aggbug/6912.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>九月鹰飞</dc:creator><title>星星来看看</title><link>http://blog.vckbase.com/iwaswzq/archive/2005/06/12/6432.html</link><pubDate>Sun, 12 Jun 2005 01:41:00 GMT</pubDate><guid>http://blog.vckbase.com/iwaswzq/archive/2005/06/12/6432.html</guid><wfw:comment>http://blog.vckbase.com/iwaswzq/comments/6432.html</wfw:comment><comments>http://blog.vckbase.com/iwaswzq/archive/2005/06/12/6432.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://blog.vckbase.com/iwaswzq/comments/commentRss/6432.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/iwaswzq/services/trackbacks/6432.html</trackback:ping><description>关于这个问题，曾经讨论过，apnic也总结过关于&amp;#8220;在构造函数中调用构造函数&amp;#8221;的问题。&lt;BR&gt;&lt;BR&gt;#include &amp;lt;iostream&amp;gt;&lt;BR&gt;using namespace std;&lt;BR&gt;&lt;BR&gt;class A&lt;BR&gt;{&lt;BR&gt;public:&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;A();&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;A(int i);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;int data;&lt;BR&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;~A();&lt;BR&gt;};&lt;BR&gt;&lt;BR&gt;A::A()&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cout&amp;lt;&amp;lt; " call A::A() " &amp;lt;&amp;lt; endl;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data = 0;&lt;BR&gt;}&lt;BR&gt;A::A(int i)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cout&amp;lt;&amp;lt; " call A::A(int) " &amp;lt;&amp;lt; endl;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data = i;&lt;BR&gt;}&lt;BR&gt;A::~A()&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cout&amp;lt;&amp;lt; " call A::~A() " &amp;lt;&amp;lt; endl;&lt;BR&gt;}&lt;BR&gt;&lt;BR&gt;class B:public A&lt;BR&gt;{&lt;BR&gt;public:&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;B();&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;B(int i);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;~B();&lt;BR&gt;};&lt;BR&gt;&lt;BR&gt;B::B(int i)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cout&amp;lt;&amp;lt; " call B::B(int) " &amp;lt;&amp;lt; endl;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;B();&lt;BR&gt;}&lt;BR&gt;&lt;BR&gt;B::B():A(10)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cout&amp;lt;&amp;lt; " call B::B() " &amp;lt;&amp;lt; endl;&lt;BR&gt;}&lt;BR&gt;B::~B()&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cout&amp;lt;&amp;lt; " call B::~B() " &amp;lt;&amp;lt; endl;&lt;BR&gt;}&lt;BR&gt;void main()&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;B b(2);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cout&amp;lt;&amp;lt; b.data &amp;lt;&amp;lt; endl;&lt;BR&gt;}&lt;BR&gt;一般认为B b(2);应该调用B::B(int i)构造函数，里面调用B();&lt;BR&gt;而B::B()调用的是A(10)构造函数，A::A(int i)会把data设置成i，就是10。&lt;BR&gt;但是运行结果是0：&lt;BR&gt;&lt;BR&gt;call A::A()&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//调用B(int)之前，先调用基类的缺省构造函数&lt;BR&gt;call B::B(int)&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //调用B(int)&lt;BR&gt;call A::A(int)&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //由于B()中使用了初始化列表，这里先调用了A(int)&lt;BR&gt;call B::B()&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//然后才调用了B()&lt;BR&gt;call B::~B()&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //居然调用析构函数&lt;BR&gt;call A::~A()&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //自然也会调用基类的析构函数&lt;BR&gt;0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //结果&lt;BR&gt;call B::~B()&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //再次析构，销毁对象&lt;BR&gt;call A::~A() &lt;BR&gt;&lt;BR&gt;从上面的调用过程中，可以看到只要调用类的构造函数，就会在栈中创建对象，是这样子的么？&lt;BR&gt;&lt;BR&gt;但是如果改成构造函数中调用父类的构造函数：&lt;BR&gt;B::B(int i)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;cout&amp;lt;&amp;lt; " call B::B(int) " &amp;lt;&amp;lt; endl;&lt;BR&gt;&amp;nbsp;A::A(i);&amp;nbsp; //call this constructor.&lt;BR&gt;}&lt;BR&gt;运行结果是：&lt;BR&gt;call A::A()&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;BR&gt;call B::B(int)&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;BR&gt;call A::A(int)&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;BR&gt;call A::~A()&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;BR&gt;0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;BR&gt;call B::~B()&amp;nbsp;&amp;nbsp;&lt;BR&gt;call A::~A() &lt;BR&gt;少了对B的构造函数和析构函数的调用，但是仍然调用了~A(); 这是为什么？&lt;BR&gt;&lt;BR&gt;看来只能改成这样：&lt;BR&gt;B::B(int i):A(i)&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;BR&gt;{&lt;BR&gt;&amp;nbsp;cout&amp;lt;&amp;lt; " call B::B(int) " &amp;lt;&amp;lt; endl;&lt;BR&gt;}&lt;BR&gt;运行结果是：&lt;BR&gt;call A::A(int)&lt;BR&gt;call B::B(int)&lt;BR&gt;2&amp;nbsp;&amp;nbsp;&lt;BR&gt;call B::~B()&lt;BR&gt;call A::~A() &lt;BR&gt;只有这个结果是正确的，难道父类的构造函数只能在初始化列表中使用？&lt;BR&gt;&lt;BR&gt;&lt;img src ="http://blog.vckbase.com/iwaswzq/aggbug/6432.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>九月鹰飞</dc:creator><title>浮点数格式及整数到浮点数的简单算法</title><link>http://blog.vckbase.com/iwaswzq/archive/2004/12/16/2100.html</link><pubDate>Thu, 16 Dec 2004 02:50:00 GMT</pubDate><guid>http://blog.vckbase.com/iwaswzq/archive/2004/12/16/2100.html</guid><wfw:comment>http://blog.vckbase.com/iwaswzq/comments/2100.html</wfw:comment><comments>http://blog.vckbase.com/iwaswzq/archive/2004/12/16/2100.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://blog.vckbase.com/iwaswzq/comments/commentRss/2100.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/iwaswzq/services/trackbacks/2100.html</trackback:ping><description>&lt;img src ="http://blog.vckbase.com/iwaswzq/aggbug/2100.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>九月鹰飞</dc:creator><title>关于volatile关键字的说明以及测试</title><link>http://blog.vckbase.com/iwaswzq/archive/2004/12/05/1896.html</link><pubDate>Sat, 04 Dec 2004 21:33:00 GMT</pubDate><guid>http://blog.vckbase.com/iwaswzq/archive/2004/12/05/1896.html</guid><wfw:comment>http://blog.vckbase.com/iwaswzq/comments/1896.html</wfw:comment><comments>http://blog.vckbase.com/iwaswzq/archive/2004/12/05/1896.html#Feedback</comments><slash:comments>27</slash:comments><wfw:commentRss>http://blog.vckbase.com/iwaswzq/comments/commentRss/1896.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/iwaswzq/services/trackbacks/1896.html</trackback:ping><description>&lt;img src ="http://blog.vckbase.com/iwaswzq/aggbug/1896.html" width = "1" height = "1" /&gt;</description></item></channel></rss>