VC,MFC开发技巧收集

来源:互联网 发布:mac截图快捷键设置 编辑:程序博客网 时间:2024/05/17 05:52

(mainmenu->GetSubMenu (0))->DeleteMenu(i,MF_BYPOSITION);
break;
}
}
十五、改变应用程序的图标


十六、另一种改变窗口标题的方法
使用语句 CWnd* m_pCWnd = AfxGetMainWnd( ),然后,再以如下形式调用SetWindowText()函数:
SetWindowText( *m_pCWnd,(LPCTSTR)m_WindowText);// m_WindowText可以是一个CString类的变量。

十七、剪切板上通过增强元文件拷贝图像数据
下面代码拷贝通过元文件拷贝图像数据到任何应用程序,其可以放置在CView派生类的函数中。
CMetaFileDC * m_pMetaDC = new CMetaFileDC();
m_pMetaDC->CreateEnhanced(GetDC(),NULL,NULL,"whatever");
//draw meta file
//do what ever you want to do: bitmaps, lines, text...
//close meta file dc and prepare for clipboard;
HENHMETAFILE hMF = m_pMetaDC->CloseEnhanced();
//copy to clipboard
OpenClipboard();
EmptyClipboard();
::SetClipboardData(CF_ENHMETAFILE,hMF);
CloseClipboard();
//DeleteMetaFile(hMF);
delete m_pMetaDC;

十八、剪切板上文本数据的传送
把文本放置到剪接板上:
CString source;
//put your text in source
if(OpenClipboard())
{
HGLOBAL clipbuffer;
char * buffer;
EmptyClipboard();
clipbuffer = GlobalAlloc(GMEM_DDESHARE, source.GetLength()+1);
buffer = (char*)GlobalLock(clipbuffer);
strcpy(buffer, LPCSTR(source));
GlobalUnlock(clipbuffer);
SetClipboardData(CF_TEXT,clipbuffer);
CloseClipboard();
}
从剪接板上获取文本:
char * buffer;
if(OpenClipboard())
{
buffer = (char*)GetClipboardData(CF_TEXT);
//do something with buffer here
//before it goes out of scope
}
CloseClipboard();

十九、将捕捉屏幕图像到剪切版中
void CShowBmpInDlgDlg::OnCutScreen()
{
ShowWindow(SW_HIDE);
RECT r_bmp={0,0,::GetSystemMetrics(SM_CXSCREEN),
::GetSystemMetrics(SM_CYSCREEN)};
HBITMAP hBitmap = CopyScreenToBitmap(&r_bmp);
//hWnd为程序窗口句柄
if (OpenClipboard())
{
EmptyClipboard();
SetClipboardData(CF_BITMAP, hBitmap);
CloseClipboard();
}
ShowWindow(SW_SHOW);
}
HBITMAP CShowBmpInDlgDlg::CopyScreenToBitmap(LPRECT lpRect)//lpRect 代表选定区域
{
HDC hScrDC, hMemDC; // 屏幕和内存设备描述表
HBITMAP hBitmap, hOldBitmap; // 位图句柄
int nX, nY, nX2, nY2; // 选定区域坐标
int nWidth, nHeight; // 位图宽度和高度
int xScrn, yScrn; // 屏幕分辨率
if (IsRectEmpty(lpRect)) // 确保选定区域不为空矩形
return NULL;
//为屏幕创建设备描述表
hScrDC = CreateDC("DISPLAY", NULL, NULL, NULL);
//为屏幕设备描述表创建兼容的内存设备描述表
hMemDC = CreateCompatibleDC(hScrDC);
// 获得选定区域坐标
nX = lpRect->left;
nY = lpRect->top;
nX2 = lpRect->right;
nY2 = lpRect->bottom;
// 获得屏幕分辨率
xScrn = GetDeviceCaps(hScrDC, HORZRES);
yScrn = GetDeviceCaps(hScrDC, VERTRES);
//确保选定区域是可见的
if (nX<0)
nX = 0;
if (nY<0)
nY = 0;
if (nX2>xScrn)
nX2 = xScrn;
if (nY2>yScrn)
nY2 = yScrn;
nWidth = nX2 - nX;
nHeight = nY2 - nY;
// 创建一个与屏幕设备描述表兼容的位图
hBitmap = CreateCompatibleBitmap(hScrDC, nWidth, nHeight);
// 把新位图选到内存设备描述表中
hOldBitmap =(HBITMAP)SelectObject(hMemDC, hBitmap);
// 把屏幕设备描述表拷贝到内存设备描述表中
BitBlt(hMemDC, 0, 0, nWidth, nHeight,
hScrDC, nX, nY, SRCCOPY);
//得到屏幕位图的句柄
hBitmap = (HBITMAP)SelectObject(hMemDC, hOldBitmap);
//清除
DeleteDC(hScrDC);
DeleteDC(hMemDC);
// 返回位图句柄
return hBitmap;
}

二十、如何将位图缩放显示在Static控件中
//在Staic控件内显示位图
void CShowBmpInDlgDlg::ShowBmpInStaic()
{
CBitmap hbmp;
HBITMAP hbitmap;
//将pStatic指向要显示的地方
CStatic *pStaic=(CStatic*)GetDlgItem(IDC_IMAGE);
//装载资源 MM.bmp是我的一个文件名,用你的替换
hbitmap=(HBITMAP)::LoadImage (::AfxGetInstanceHandle(),"MM.bmp",
IMAGE_BITMAP,0,0,LR_LOADFROMFILE|LR_CREATEDIBSECTION);
hbmp.Attach(hbitmap);
//获取图片格式
BITMAP bm;
hbmp.GetBitmap(&bm);
CDC dcMem;
dcMem.CreateCompatibleDC(GetDC());
CBitmap *poldBitmap=(CBitmap*)dcMem.SelectObject(hbmp);
CRect lRect;
pStaic->GetClientRect(&lRect);
lRect.NormalizeRect();
//显示位图
pStaic->GetDC()->StretchBlt(lRect.left ,lRect.top ,lRect.Width(),lRect.Height(),
&dcMem,0 ,0,bm.bmWidth,bm.bmHeight,SRCCOPY);
dcMem.SelectObject(&poldBitmap);


usidc52010-11-11 23:38

用#include可以包含其他头文件中变量、函数的声明,为什么还要extern关键字,如果我想引用一个全局变量或 函数a,我只要直接在源文件中包含#include<xxx.h> (xxx.h包含了a的声明)不就可以了么,为什么还要用extern呢??这个问题一直也是似是而非的困扰着我许多年了,今天上网狠狠查了一下总算小有 所获了:

头文件

首先说下头文件,其实头文件对计算机而言没什么作用,她只是在预编译时在#include的地方展开一下,没别的意义了,其实头文件主要是给别人看的。

我做过一个实验,将头文件的后缀改成xxx.txt,然后在引用该头文件的地方用

#include"xxx.txt"

编译,链接都很顺利的过去了,由此可知,头文件仅仅为阅读代码作用,没其他的作用了!

不管是C还是C++,你把你的函数,变量或者结构体,类啥的放在你的.c或者.cpp文件里。然后编译成lib,dll,obj,.o等等,然后别人用的时候 最基本的gcc hisfile.cpp yourfile.o|obj|dll|lib 等等。
但对于我们程序员而言,他们怎么知道你的lib,dll...里面到底有什么东西?要看你的头文件。你的头文件就是对用户的说明。函数,参数,各种各样的接口的说明。
    那既然是说明,那么头文件里面放的自然就是关于函数,变量,类的“声明”了。记着,是“声明”,不是“定义”。
那么,我假设大家知道声明和定义的区别。所以,最好不要傻嘻嘻的在头文件里定义什么东西。比如全局变量:

#ifndef _XX_头文件.H
#define _XX_头文件.H
int A;
#endif

那么,很糟糕的是,这里的int A是个全局变量的定义,所以如果这个头文件被多次引用的话,你的A会被重复定义
显 然语法上错了。只不过有了这个#ifndef的条件编译,所以能保证你的头文件只被引用一次,不过也许还是会岔子,但若多个c文件包含这个头文件时还是会 出错的,因为宏名有效范围仅限于本c源文件,所以在这多个c文件编译时是不会出错的,但在链接时就会报错,说你多处定义了同一个变量,

Linking...
incl2.obj : error LNK2005: "int glb" (
?glb@@3HA) already defined in incl1.obj
Debug/incl.exe : fatal error LNK1169: one or more multiply defined symbols found

注意!!!

extern

这个关键字真的比较可恶,在声明的时候,这个extern居然可以被省略,所以会让你搞不清楚到底是声明还是定义,下面分变量和函数两类来说:

(1)变量

尤其是对于变量来说。
extern int a;//声明一个全局变量a
int a; //定义一个全局变量a

extern int a =0 ;//定义一个全局变量a 并给初值。
int a =0;//定义一个全局变量a,并给初值,

第四个 等于 第 三个,都是定义一个可以被外部使用的全局变量,并给初值。
糊涂了吧,他们看上去可真像。但是定义只能出现在一处。也就是说,不管是int a;还是extern int a=0;还是int a=0;都只能出现一次,而那个extern int a可以出现很多次。

当你要引用一个全局变量的时候,你就要声明,extern int a;这时候extern不能省略,因为省略了,就变成int a;这是一个定义,不是声明。

(2)函数
     函数,函数,对于函数也一样,也是定义和声明,定义的时候用extern,说明这个函数是可以被外部引用的,声明的时候用extern说明这是一个声明。 但由于函数的定义和声明是有区别的,定义函数要有函数体,声明函数没有函数体,所以函数定义和声明时都可以将extern省略掉,反正其他文件也是知道这个函数是在其他地方定义的,所以不加extern也行。两者如此不同,所以省略了extern也不会有问题。
    比如:

int fun(void)
{
return 0;
}

很好,我们定义了一个全局函数

int fun(void);
我们对它做了个声明,然后后面就可以用了
加不加extern都一样
我们也可以把对fun的声明 放在一个头文件里,最后变成这样

int fun(void);//函数声明,所以省略了extern,完整些是extern int fun(void);

int fun(void)
{
return 0;
}//一个完整的全局函数定义,因为有函数体,extern同样被省略了。
然后,一个客户,一个要使用你的fun的客户,把这个头文件包含进去,ok,一个全局的声明。没有问题。
但是,对应的,如果是这个客户要使用全局变量,那么要extern 某某变量;不然就成了定义了。

总结下:

对变量而言,如果你想在本源文件中使用另一个源文件的变量,就需要在使用前用extern声明该变量,或者在头文件中用extern声明该变量;

对函数而言,如果你想在本源文件中使用另一个源文件的函数,就需要在使用前用声明该变量,声明函数加不加extern都没关系,所以在头文件中函数可以不用加extern。


usidc52010-11-13 19:29通常除了从CObject继承的类重写Serialize外,我们经常还遇到对自定义Struct, enum等的序列化,这里就说说后两个的可以方法:
一、结构体:
       struct   A
                 {   
                     int      a ;   
                     float   b;   
                  }structA;
       1    最简单却较繁琐的方法:
              ar<<stuctA.a<<stuctA.b
        2   为结构体添加 重载符: 

              struct   A{   
                  int   a   ;   
                  float   b;   
                  friend   CArchive   &   operator   <<(CArchive   &   arc,A   &data);   
                  friend   CArchive   &   operator   >>(CArchive   &   arc,A   &data);   
              }   
    
             CArchive   &   operator   <<(CArchive   &   arc,A   &data)   
             {   
                 arc<<a<<b;   
                 return   arc;   
              }   
             CArchive   &   operator   >>(CArchive   &   arc,A   &data)   
             {   
                 arc>>a>>b   
                 return   arc;   
             }   
  二、枚举类型:
           enum   Shape   {   rectangle,   roundRectangle,   ellipse,line,circle,picture,text,polygon   };       
              Shape   m_nShape;   
           ar   <<   (WORD)   m_nShape;      
           WORD   wTemp1;   
           ar   >>   wTemp1;   m_nShape   =   (Shape)wTemp1; 

usidc52010-11-20 12:02
命令行窗口与普通的窗口有什么区别?你用spy++看一下就知道了。
如果你试图去截获命令行窗口的键盘消息,那将是失败的。

现在做一个实验:开始 -> 运行 -> 
pingwww.usidcbbs.com -t
这样得到一个命令行窗口,如果手工按Ctrl+C,它会结束。
那么如何编程模拟键盘动作呢?

首先测试 PostMessage, 结果是没有任何发应。

最后我找到了解决的办法。代码如下。

HWND hWnd = ::FindWindow("ConsoleWindowClass",NULL);
 if( !hWnd )
  return;

 ::SetForegroundWindow(hWnd );
 keybd_event(VK_CONTROL, 0, 0, 0);
 keybd_event('C', 0, 0, 0);
 keybd_event(('C'), 0, KEYEVENTF_KEYUP, 0);
 keybd_event(VK_CONTROL, 0, KEYEVENTF_KEYUP, 0);


usidc52011-08-21 16:19在vc下写代码的时候经常会碰到这样的情况, 程序用到了标准库的函数, vc提示有warning, 不安全, 需要用某某函数替代. 为了平台的可移植性不想更换函数. 但这一堆warning看着又烦人, 有时候隐藏在这一堆warning中的"真正"的warning还容易被忽略. 以下是忽略特定warning的方法.

在源代码中应用出现warning的函数前增加代码, C4996为使用fopen等时的warning代码

#pragma warning(disable: 4996)

// 用到fopen的代码

#pragma warning(default :4996) // 需要的话在适当的地方恢复该警告

勇敢天龙2011-08-22 22:43有意义,有收获,谢谢提供










usidc52011-08-24 15:291、第一步创建一“MFC AppWizard (dll)”工程,接下来选择“Regular Dll using shared MFC DLL”,点击“Finish”。

2、添加一对话框资源到工程中,从菜单中选择Insert->Resource,添加一“Dialog”
选择“New”,至此对话框已添加到DLL工程中。

3、为对话框添加一新类,如:CTest,基类为CDialog。

4、在MFCDLL.cpp中(因创建的工程为MFCDLL)添加接口函数:
[pre]extern "C" __declspec(dllexport) void Show(){    AFX_MANAGE_STATE(AfxGetStaticModuleState());    CTest test;    test.DoModal ();}[/pre]别忘了在文件中添加: #include "Test.h":),大功告成,编译吧!

5、用VC新建一对话框工程,在一按钮点击事件中添加如下代码:[pre]typedef void (WINAPI * TESTDLL)();HINSTANCE hmod;hmod = ::LoadLibrary ("mfcdll.dll");if(hmod==NULL){    AfxMessageBox("Fail");}TESTDLL lpproc;lpproc = (TESTDLL)GetProcAddress (hmod,"Show");if(lpproc!=(TESTDLL)NULL)    (*lpproc)();FreeLibrary(hmod);[/pre] 6、试试看,是不是DLL中对话框显示出来了:)

测试环境:VC6.0+Win2000

usidc52011-08-24 22:52
一、控件的介绍
MFC中封装了许多有用的类,CTabCtrl和CPropertySheet是其中两个比较常用的类型。CTabCtrl即Tab控件对应的类,Windows应用程序中大量使用了Tab控件。属性页对应的是CPropertySheet,你在桌面上右键鼠标弹出的显示对话框就是一个CPropertySheet类型的对象。事实上这类弹出的、类似“属性页”的窗口大部分都是CPropertySheet。
其实,这两个类之间也有着很密切的联系。使用Spy++观察一个属性页窗口可以发现,它也包含了一个Tab控件
二、属性页的使用
属性页的使用比较简单,过程也很清晰。先说一下,CPropertySheet和CDialog对话框类同样都派生于CWnd类,所以他俩在使用上有很多相似的地方。我不知道是不是这哥俩太相似了,以至于Spy++把我自建的一个CPropertySheet窗口认作是CDialog。
既然属性页和对话框相似,我们只要有了一个属性页的类,就可以使用DoModal()方法来产生一个模态的窗口,或者用Create()产生一个非模态的窗口。注意如果显示非模式的窗口,要考虑变量生存期的问题,这点和非模态对话框是一样的。
属性页窗口里面那么多的选项卡是怎么弄出来的?这就需要CPropertyPage。这是一个对话框类的子类,对应着一个选项卡。对一个属性页,我们使用AddPage()方法来添加选项卡
选项卡既然是对话框,我们就可以在资源窗口中建立对话框,然后利用类向导为他建立类,注意要选择派生自CPropertyPage。MSDN上面说,建立这类对话框资源模板时有几点要注意
1.保留TitleBar属性。对话框标题就是将来显示在选项卡上的标签
2.对话框风格设为Child,边框设为Thin
3.将对话框设为Disabled
好了,总结一下属性页的使用步骤就是
1.建立模板资源,构造你需要的每一个CPropertyPage。
2.使用CPropertySheet类,用AddPage()方法为其添加至少一个选项卡。或者,你可以派生一个自己的CMyPropertySheet类,在内部添加上各个CPropertyPage作为成员变量,并在构造函数中用AddPage
3.在需要使用属性页的地方声明变量,使用DoModal()或者Create()方法显示属性页
    // CPropertySheet简单示例
    CPropertySheet *pSheet = new CPropertySheet;
    CPropertyPage *pg1 = new CPropertyPage(IDD_PROPPAGE_SMALL);
    CPropertyPage *pg2 = new CPropertyPage(IDD_PROPPAGE_SMALL1);
    CPropertyPage *pg3 = new CPropertyPage(IDD_PROPPAGE_SMALL2);
    pSheet->AddPage(pg1);
    pSheet->AddPage(pg2);
    pSheet->AddPage(pg3);
    pSheet->DoModal();


三、Tab控件的使用
Tab控件一般用在对话框中。在我看来Tab控件本身并不复杂,其实也就是一组简单的按钮而已,因为它并不能直接对各个选项卡进行操纵。我们需要对用户选择选项卡的行为进行手工地响应
比如你希望对每一个选项卡显示不同的内容,你必须通过显示/隐藏不需要的内容。当每一个选项卡上的内容较多时,可以把这些内容放在一个容器里,比如一个对话框。这样,一个选项卡对应一个对话框,关系比较清晰。这些对话框应设置为没有标题栏、Child风格、无边框的、非模式的。以此为例,对话框中使用Tab控件的步骤是
1.在对话框中添加Tab控件
2.为各选项卡建立对话框模板资源
3.响应Tab控件的TCN_SELCHANGE消息,在OnSelChange()函数中,用ShowWindow()函数显示欲显示的选项卡,隐藏其他。非模式对话框的生存期问题这里仍然存在,不可忽视
4.初始化Tab控件。可在对话框初始化时手工调用OnSelChange()函数的方法来实现.

usidc52011-10-06 20:061.使用ModifyStyleEx改变了控件风格无效
答:修改之后,重绘一次,如果还不行的话,再试试看调用SetWindowPos(0,0,0,0,0,SWP_NOMOVE | SWP_NOSIZE | SWP_DRAWFRAME);

2.动态设置编辑框的ES_PASSWORD无效
答:修改之后,需要调用一次SetPasswordChar('*');

3.如何获取任务栏小图标?
答:有网友提出,能不能在程序界面上显示当前的托盘图标,这样更直观一些。这一点我也曾想到过,也知道奥秘就在TBBUTTON这个结构体的dwData域里面,可惜这个域的结构在MSDN中没有描述。我也曾试着发送TB_GETIMAGELIST消息获取image list的句柄,然后用CImageList::FromHandle加载到CImageList中,但却都失败了。
在csdn网友的帮助下,我弄清了dwData的结构,其实dwData有一个这样的机构,我们命名为TRAYDATA:
struct TRAYDATA  
{  
  HWND hwnd;  
  UINT uID;  
  UINT uCallbackMessage;  
  DWORD Reserved[2];  
  HICON hIcon; //托盘图标的句柄  
};  
要获取到这个hIcon,其实很简单:
//分配虚拟内存  
lngAddress = VirtualAllocEx(hProcess, NULL, sizeof(TBBUTTON), MEM_COMMIT, PAGE_READWRITE);  
//将hProcess进程内,地址为lngAddress中的内容(大小为sizeof(TBBUTTON))获取到tb中  
ret = ::ReadProcessMemory(hProcess, LPVOID(long(lngAddress)), &tb, sizeof(TBBUTTON), 0);  
//将hProcess进程内,地址为tb.dwData中的内容(大小为sizeof(TRAYDATA))获取到tray中  
ret = ::ReadProcessMemory(hProcess, LPVOID(tb.dwData), &tray, sizeof(TRAYDATA), 0);  
然后就可以用tray.hIcon索引到这个图标句柄了
//释放内存代码略

4.BeginWaitCursor和EndWaitCursor无效
答:SetCapture();BeginWaitCursor();....EndWaitCursor();ReleaseCapture();

5.打印源码中文件名和行号常用的宏名是什么?  
答:文件名:__FILE__ 行号:__LINE__  

6.如何具体判断一个字符串中哪个是英文字母
答:函数isalpha

7.在注册表中如何得到所有安装了的应用程序程序的信息
答:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall下面的子鍵

8.64位系统下运行32的程序,操作注册表遭遇重定向问题
答:PVOID OldValue;  
HINSTANCE hlibrary;  
typedef int (__stdcall * Wow64DisableWow64FsRedirection)(LPVOID);  
YWow64DisableWow64FsRedirection f_Wow64DisableWow64FsRedirection = NULL;  
typedef int (__stdcall * Wow64RevertWow64FsRedirection)(LPVOID);  
Wow64RevertWow64FsRedirection f_Wow64RevertWow64FsRedirection = NULL;  
hlibrary = LoadLibrary("Kernel32.dll");  
f_Wow64DisableWow64FsRedirection = (Wow64DisableWow64FsRedirection) GetProcAddress(hlibrary,"Wow64DisableWow64FsRedirection");  
if(!f_Wow64DisableWow64FsRedirection) { }  
f_Wow64DisableWow64FsRedirection (&OldValue);  
//这里写注册表操作函数,Open的时候要指定 KEY_WOW64_64KEY标志  
f_Wow64RevertWow64FsRedirection = (Wow64RevertWow64FsRedirection) GetProcAddress(hlibrary,"Wow64RevertWow64FsRedirection");  
if(!f_Wow64RevertWow64FsRedirection) { }  
f_Wow64RevertWow64FsRedirection (OldValue);  
FreeLibrary(hlibrary);

9.怎么使用系统默认浏览器在新窗口打开网页
答:ShellExecute是我们常用的一个API,可以运行程序,打开网页。
ShellExecute(NULL, "open", "http://www.csdn.net", NULL,NULL,SW_SHOWMAXIMIZED);
这样可以打开一个网页,但不是在新IE中打开,改成下面方式时就可以在一个新的IE中打开网页了
ShellExecute(NULL, "open", "IEXPLORE", "http://www.csdn.net",NULL,SW_SHOWMAXIMIZED);  

10.怎么清除串口的输入缓冲
答:PurgeComm(hCom,PURGE_TXABORT ¦ PURGE_RXABORT ¦ PURGE_TXCLEAR ¦ PURGE_RXCLEAR);

11.显示桌面属性对话框
答:ShellExecute(GetDesktopWindow()->m_hWnd,"open","control.exe","desk.cpl Display,@Theme","",SW_SHOW );

12.限制Edit只能输入0-F
答:WNDPROC wpOrigEditProc;  
LRESULT CALLBACK wpSubclassEditProc(HWND hDlg,UINT uiMsg,WPARAM wParam,LPARAM lParam);
wpOrigEditProc = (WNDPROC) SetWindowLong(GetDlgItem(IDC_EDIT_DATA)->m_hWnd,  
  GWL_WNDPROC, (LONG) wpSubclassEditProc);  
LRESULT CALLBACK wpSubclassEditProc(HWND hDlg,UINT uiMsg,WPARAM wParam,LPARAM lParam)
{
switch(uiMsg)
{
case WM_CHAR:
  if((wParam >= 0x30 && wParam <= 0x39) ||  
  (wParam >= 0x41 && wParam <= 0x46) ||
  (wParam >= 0x61 && wParam <= 0x66) ||
  (wParam == 0x08) || wParam == 0x20 || wParam == 0x0D)
  {
  return CallWindowProc(wpOrigEditProc,hDlg,uiMsg,wParam,lParam);
  }
  else
  {
  if(GetKeyState(VK_CONTROL) & 0x8000)
  {
  return CallWindowProc(wpOrigEditProc,hDlg,uiMsg,wParam,lParam);
  }
  return true;
  }
  break;
}
return CallWindowProc(wpOrigEditProc,hDlg,uiMsg,wParam,lParam);
}

usidc52011-10-06 20:07//一些帮助函数


//返回当前程序的路径
//例如c:\abc\abc.exe则返回c:\abc
BOOL    GetPath(TCHAR * strPath)
{
    TCHAR sDrive[_MAX_DRIVE];
    TCHAR sDir[_MAX_DIR];
    TCHAR sFilename[_MAX_FNAME],Filename[_MAX_FNAME];
    TCHAR sExt[_MAX_EXT];
    
    GetModuleFileName(NULL, Filename, _MAX_PATH);
    _tsplitpath(Filename, sDrive, sDir, sFilename, sExt);
    
    _tcscpy(strPath,sDrive);
    _tcscat(strPath,sDir);


    strPath[_tcslen(strPath)-1] = _T('\0');
    
    return TRUE;
}


//获得父进程ID,失败则返回0
DWORD   GetPPid()  
{          
    HANDLE            hProcessSnap   =   NULL;    
    PROCESSENTRY32    pe32    =    {0};
    DWORD dwPID = GetCurrentProcessId();
    
    hProcessSnap   =   CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,   0);    
    
    if(hProcessSnap==(HANDLE)-1)    
        return   (FALSE);    


    pe32.dwSize   =   sizeof(PROCESSENTRY32);    
    
    if   (Process32First(hProcessSnap,   &pe32))    
    {  
        while(pe32.th32ProcessID!=dwPID)  
            Process32Next(hProcessSnap,   &pe32);  
    }
    
    CloseHandle(hProcessSnap);
    
    return(pe32.th32ParentProcessID);  
}


//判断程序是否以服务运行
BOOL isRuninServices()
{
    DWORD dwPPID = GetPPid();


    HANDLE hProcessSnap   =   NULL;    
    PROCESSENTRY32   pe32  =   {0};    
  
    hProcessSnap   =   CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,   0);    
    
    if   (hProcessSnap   ==   (HANDLE)-1)    
        return   (FALSE);    
  
    pe32.dwSize   =   sizeof(PROCESSENTRY32);    
    
    if   (Process32First(hProcessSnap,   &pe32))    
    {  
        while(pe32.th32ProcessID!=dwPPID)  
            Process32Next(hProcessSnap,   &pe32);  
    }
    
    CloseHandle(hProcessSnap);


    if(_tcsncicmp(pe32.szExeFile,_T("services.exe"),20)==0)
        return TRUE;
    else return FALSE;


}


//提升为调试权限
BOOL EnableDebugPrivilege(BOOL bEnable)
{
    BOOL bOk = FALSE;
    HANDLE hToken;
    
    if(::OpenProcessToken(::GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
    {
        LUID uID;
        ::LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &uID);


        TOKEN_PRIVILEGES tp;
        tp.PrivilegeCount = 1;
        tp.Privileges[0].Luid = uID;
        tp.Privileges[0].Attributes = bEnable ? SE_PRIVILEGE_ENABLED : 0;
        ::AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
        bOk = (::GetLastError() == ERROR_SUCCESS);


        ::CloseHandle(hToken);
    }
    return bOk;
}


//用于在release下输出调试信息
void KDbgPrint(LPCTSTR lpszFormat, ...)
{
#define _countof(array) (sizeof(array)/sizeof(array[0]))
#ifdef KOUT_DEBUG_STRING
    va_list args;
    va_start(args, lpszFormat);


    int nBuf;
    TCHAR szBuffer[512];


    nBuf = _vsntprintf(szBuffer, _countof(szBuffer), lpszFormat, args);


    OutputDebugString(szBuffer);


    va_end(args);
#endif
}


//从地址去获得模块基址
HMODULE WINAPI ModuleFromAddress(PVOID pv)
{
    MEMORY_BASIC_INFORMATION mbi;
    if(::VirtualQuery(pv, &mbi, sizeof(mbi)) != 0)
    {
        return (HMODULE)mbi.AllocationBase;
    }
    else
    {
        return NULL;
    }
}


//获得DLL自身
BOOL GetDllPath(TCHAR * path)
{
    HMODULE hDll = ModuleFromAddress(GetDllPath);


    if(hDll==NULL)
        return FALSE;
    else
    {
        GetModuleFileName(hDll, path, _MAX_PATH);
        return TRUE;
    }
}




//格式化string
void format_string(string & str,LPCTSTR lpszFormat, ...)
{
#define _countof(array) (sizeof(array)/sizeof(array[0]))
    va_list args;
    va_start(args, lpszFormat);
    
    int nBuf;
    TCHAR szBuffer[1024];
    
    nBuf = _vsntprintf(szBuffer, _countof(szBuffer), lpszFormat, args);
    
    str = szBuffer;
    
    va_end(args);
}


//提升所需的权限
BOOL EnablePrivilege(LPCTSTR lpName,BOOL bEnable)
{
    BOOL bOk = FALSE;
    HANDLE hToken;
    
    if(::OpenProcessToken(::GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
    {
        LUID uID;
        ::LookupPrivilegeValue(NULL, lpName, &uID);
        
        TOKEN_PRIVILEGES tp;
        tp.PrivilegeCount = 1;
        tp.Privileges[0].Luid = uID;
        tp.Privileges[0].Attributes = bEnable ? SE_PRIVILEGE_ENABLED : 0;
        ::AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
        bOk = (::GetLastError() == ERROR_SUCCESS);
        
        ::CloseHandle(hToken);
    }
    return bOk;
}


//跳转到注册表
void Jump2Reg(string strRegPath,string strKey)
{
    //ShellExecute(NULL,NULL,"regjump.exe",strRegPath.c_str(),NULL,SW_SHOW);
    ShellExecute(NULL,NULL,"regedit.exe",NULL,NULL,SW_SHOW);
    
    //然后查找注册表窗口
    HWND hMain = NULL;
    HWND hTree = NULL;
    HWND hList = NULL;
    BOOL bFind = FALSE;
    
    for(int i=0;i<10;i++)
    {
        hMain = FindWindow("RegEdit_RegEdit","注册表编辑器");
        
        if(hMain!=NULL)
        {//查找成功
            bFind = TRUE;
            break;
        }
        
        Sleep(200);
    }
    
    if(bFind)
    {
        hTree = FindWindowEx(hMain,NULL,"SysTreeView32",NULL);
        hList = FindWindowEx(hMain,NULL,"SysListView32",NULL);
        
        //选择树
        SetForegroundWindow(hTree);
        SetFocus(hTree);
        
        DWORD dwPID;
        GetWindowThreadProcessId(hMain,&dwPID);
        HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID);
        
        //先把树给折叠起,最多30层
        for(int i=0;i<30;i++)
        {
            SendMessage(hTree, WM_KEYDOWN, VK_LEFT, 0);
        }
        
        SendMessage(hTree, WM_KEYDOWN, VK_RIGHT, 0);
        WaitForInputIdle(hProc, INFINITE);
        
        
        for(i=0;i<strRegPath.length();i++)
        {
            if(strRegPath.at(i)=='\\')
                SendMessage(hTree, WM_KEYDOWN, VK_RIGHT, 0);
            else
                SendMessage(hTree,WM_CHAR,WPARAM(strRegPath.at(i)),0);
            
        }
        
        WaitForInputIdle(hProc, INFINITE);
        
        //然后到列表了..
        SetForegroundWindow(hList);
        SetFocus(hList);
        
        Sleep(1000);
        
        SendMessage(hList, WM_KEYDOWN, VK_HOME, 0);
        
        for(i=0;i<strKey.length();i++)
        {
            SendMessage(hList,WM_CHAR,WPARAM(strKey.at(i)),0);
        }
        
        CloseHandle(hProc);
    }
    
    return;
}


//////////////////////////////////////////////////////////////////////////
//显示文件属性
void ShowProperties(string strPath)
{
    SHELLEXECUTEINFO si;
    
    ZeroMemory(&si,sizeof(SHELLEXECUTEINFO));
    
    si.cbSize = sizeof(SHELLEXECUTEINFO);
    si.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_INVOKEIDLIST | SEE_MASK_FLAG_NO_UI ;
    si.lpVerb = "properties";
    si.lpFile = strPath.c_str();
    si.nShow = SW_SHOW;
    
    ShellExecuteEx(&si);
}


//定位文件
void Jump2File(string strPath)
{
    string cmd = "/e,/select,"+strPath;
    ShellExecute(NULL,NULL,"explorer",cmd.c_str(),NULL,SW_SHOW);
}

usidc52011-10-06 20:071.动态修改ComboBox的DROPDOWN,DROPDOWNLIST属性

DWORD theStyle;
HWND theChild;  

theChild = ::GetWindow( GetDlgItem(IDC_COMBO1)->m_hWnd , GW_CHILD );  
theStyle = GetWindowLong(GetDlgItem(IDC_COMBO1)->m_hWnd, GWL_STYLE );  
theStyle &= ~CBS_DROPDOWN;//去掉DROPDOWN  
theStyle |= CBS_DROPDOWNLIST; //添加DROPDOWNLIST

::DestroyWindow( theChild );  
SetWindowLong(GetDlgItem(IDC_COMBO1)->m_hWnd, GWL_STYLE , theStyle );

2.从txt文件中一行一行地读数据

建议使用CFile和CArchive结合,利用CArchive的ReadString函数进行逐行读取。  
CFile file;  
file.Open("d:\\temp\\myfile.txt",CFile::modeRead | CFile::shareDenyWrite);  
CArchive arf(&file,CArchive::load);  
CString strTemp;  
arf.ReadString(strTemp);  
...  
arf.Close();  
file.Close();

3.打印RichEdit的内容
void PrintRichEdit(CRichEditCtrl *pRichEdit, CDC *dc, CRect rect)  
  {  
  FORMATRANGE fr;  
  int nLast = 0;  
  RECT richPage;  
    
  UINT nCurPrinterResX = dc->GetDeviceCaps(LOGPIXELSX);  
  float fPrnScrRatio = static_cast<float>(nCurPrinterResX * 1.0 / m_nScreenResolution);  
    
  long lLineWidth = ::MulDiv(rect.Width()*fPrnScrRatio, 1440, dc->GetDeviceCaps(LOGPIXELSX));  
    
  richPage.left = ::MulDiv(rect.left*fPrnScrRatio, 1440, dc->GetDeviceCaps(LOGPIXELSX));  
  richPage.top = ::MulDiv(rect.top*fPrnScrRatio , 1440, dc->GetDeviceCaps(LOGPIXELSY));  
  richPage.right = ::MulDiv(rect.right*fPrnScrRatio, 1440, dc->GetDeviceCaps(LOGPIXELSX));  
  richPage.bottom= ::MulDiv(rect.bottom*fPrnScrRatio, 1440, dc->GetDeviceCaps(LOGPIXELSY));  
  long lWidth = dc->GetDeviceCaps(PHYSICALWIDTH);  
  long lpixel = dc->GetDeviceCaps(LOGPIXELSX);  
  lpixel = dc->GetDeviceCaps(LOGPIXELSY);  
    
  fr.hdc = dc->m_hDC;  
  fr.hdcTarget = dc->m_hDC;  
  fr.rc = richPage;  
  fr.rcPage = richPage;  
  fr.chrg.cpMin = nLast;  
  fr.chrg.cpMax = -1;  
  dc->SaveDC();  
  pRichEdit->SetTargetDevice(*dc, lLineWidth);  
  nLast = pRichEdit->FormatRange(&fr, TRUE);  
  fr.chrg.cpMax = nLast;  
    
  pRichEdit->DisplayBand(&richPage);  
  pRichEdit->FormatRange(NULL, FALSE);  
  dc->RestoreDC(-1);  
  }

4.WritePrivateProfileSection写入CString,出现乱码
CString rString;
m_ListAllItems.GetText(i, rString);
rString += '\0';//添加这句,应该就可以了
WritePrivateProfileSection(SECTION_NAME, rString, strFileName);

5.
isdigit:判断字符是否是数字(0 – 9)  
isalpha:判断字符是否是字母(A – Z or a – z)  
isxdigit:判断是否是十六进制字符(A – F, a – f, or 0 – 9)  
isalnum:判断是否是数字和字母(A – Z, a – z, or 0 – 9)

6.sprintf,CString.Format中使用变量
CString str;
int i = 3;
str.Format("%0*d", i, ret);//效果一样
str.Format("%03d", ret);//效果一样

usidc52011-10-06 20:10如何制作透明窗体  
  使用SetLayeredWindowAttributes可以方便的制作透明窗体,此函数在w2k以上才支持,而且如果希望直接使用的话,可能需要下载最新的SDK。不过此函数在w2k的user32.dll里有实现,所以如果你不希望下载巨大的sdk的话,可以直接使用GetProcAddress获取该函数的指针。  


SetLayeredWindowAttributes的函数原型如下:  

BOOL SetLayeredWindowAttributes(  
HWND hwnd, // handle to the layered window  
COLORREF crKey, // specifies the color key  
BYTE bAlpha, // value for the blend function  
DWORD dwFlags // action  
);  


Windows NT/2000/XP: Included in Windows 2000 and later.  
Windows 95/98/Me: Unsupported.(注意了,在win9x里没法使用的)  
Header: Declared in Winuser.h; include Windows.h.  
Library: Use User32.lib.  


一些常量:  


WS_EX_LAYERED = 0x80000;  
LWA_ALPHA = 0x2;  
LWA_COLORKEY=0x1;  

  其中dwFlags有LWA_ALPHA和LWA_COLORKEY  

  LWA_ALPHA被设置的话,通过bAlpha决定透明度.  

  LWA_COLORKEY被设置的话,则指定被透明掉的颜色为crKey,其他颜色则正常显示.  

  要使使窗体拥有透明效果,首先要有WS_EX_LAYERED扩展属性(旧的sdk没有定义这个属性,所以可以直接指定为0x80000).  

  例子代码:  

  在OnInitDialog()加入:  

//加入WS_EX_LAYERED扩展属性  
SetWindowLong(this->GetSafeHwnd(),GWL_EXSTYLE,  
GetWindowLong(this->GetSafeHwnd(),GWL_EXSTYLE)^0x80000);  
HINSTANCE hInst = LoadLibrary("User32.DLL");  
if(hInst)  
{  
 typedef BOOL (WINAPI *MYFUNC)(HWND,COLORREF,BYTE,DWORD);  
 MYFUNC fun = NULL;  
 //取得SetLayeredWindowAttributes函数指针  
 fun=(MYFUNC)GetProcAddress(hInst, "SetLayeredWindowAttributes");  
 if(fun)fun(this->GetSafeHwnd(),0,128,2);  
 FreeLibrary(hInst);  
}  

  稍加修改还可以作出淡出淡入的效果. 注意第三个参数(128)不要取得太小了,为0的话就完全透明,看不到了。

usidc52011-10-06 20:10如何使框架窗口的图标为动画显示  

  可以用TIMER,但是TIMER不能有效的定时。因为TIMER发送的是窗口消息,当窗口忙于处理键盘、鼠标等消息时就不能及时处理TIMER,会使间隔时间变得很长 。  

  可以考虑用一个单独得TIMER线程,用Sleep()定时来解决此问题。  


UINT Timer(LPVOID param)  
{  
 HWND hWnd=(HWND)param;  
 while(1)  
 {  
  Sleep(ms);  
  PostMessage(hWnd,CH_PICTURE,NULL,NULL)  
 }  
}  

  Sleep(ms)后发送自定义消息。消息处理函数就选择某一个ICON或BITMAP来显示。如 :  


MyBotton.SetBitmap((HBITMAP)Bitmap
);  

  Bitmap是一个位图数组,存放有j个位图。消息处理函数运行一次,i就累加一次,当i==j时,i就回到0;

usidc52011-10-06 20:10]Q 如何处理ComboBox中的回车键?避免退出程序?
A 在一般的EDIT中采用的方法是处理PretranlateMessage(),执行代码
CWnd *pWnd = GetFocus();
if(pWnd != NULL)
{
  if(pWnd == GetDlgItem(IDC_EDIT1)
  {
  ...//IDC_EDIT1具有焦点
  }
}

但在ComboBox中好象不同,是ComboBox的编辑控件得到了焦点,所以判断代码:
BOOL CDlg::PreTranslateMessage(MSG *pMsg)
{
  if(pMsg->message==WM_KEYDOWN && pMsg->wParam == VK_RETURN)
  {
  CWnd *pWnd = GetFocus();
  if(pWnd != NULL)
  {
  if(pWnd->GetParent() == GetDlgItem(IDC_COMBO1)//更改ID
  {
  return TRUE;
  }
  }
  }
  return CDialog::PreTranslateMessage(pMsg);
}

//-------------------------------------------------
Q 动态创建的组合框如何设置下拉列表框的高度?
A m_combo.Create(WS_CHILD | WS_VISIBLE | WS_VSCROLL CBC_SORT | CBC_DROPDOWNLIST | WS_TABSTOP, CRect(320,10,580,280),this,114);
//CRect的最后一个参数(这里是280)就表示下拉大小

//-------------------------------------------------
Q 是否能不选择下拉列表样式而禁止用户输入值,有什么方法可以实现?
A 将下拉列表的编辑控件设置为只读的,方法如下:
CComboBox *pcombo;
CWnd *pWnd = pcombo->GetWindow(GW_CHILD);
while(pWnd)
{
  char classname[256];
  ::GetClassName(pWnd->m_hWnd,classname,256)
  if(strcmp(classname,"edit") == 0)
  {
  CEdit *pEdit;
  pEdit = new CEdit();
  pEdit->SubClassWindow(pWnd->m_hWnd);
  pEdit->SetReadOnly();
  pWnd = pWnd->GetNextWindow();
  delete pEdit;
  }
  if(pWnd)
  pWnd = pWnd->GetNextWindow();
}

//-------------------------------------------------
Q ComboBox的自定义弹出菜单,想在右击组合框的编辑部分的时候弹出菜单?
A 一种方法就是在CCustomCombo的OnCtlColor函数里进行,生成ComboBox中编辑框的子类,示例:
HBRUSH CCustomCombo::OnCtlColor(CDC *pDC,CWnd *pWnd,UINT nCtlColor)
{
  if(nCtlColor == CTLCOLOR_EDIT)
  {
  if(m_edit.GetSafeHwnd()==NULL)
  m_eidt.SubClassWindow(pWnd->GetSafeHwnd());
  }
  HBRUSH hbr = CComboBox::OnCtlColor(pDC,pWnd,nCtlColor);
  return hbr;
}
//其中m_edit是CEdit类的实现,它在WM_RBUTTONUP上显示右键菜单

//-------------------------------------------------
Q 如何给按钮加位图
A  
对动态创建的按钮:
CButton button;
button.Create(_T("My Button"),WS_CHILD | WS_VISIBLE | BS_BITMAP,CRect(10,10,60,50),pParentWnd,1);
button.SetBitmap(::LoadBitmap(NULL,MAKEINTRESOURCE(IBM_CHECK)));
或者修改风格:
UINT Style = Button.GetButtonStyle();
Style = Style | BS_BITMAP;
Button.SetBitmap(::LoadBitmap(NULL,MAKEINTRESOURCE(IBM_CHECK)));

//-------------------------------------------------
Q 如何在CButton派生类中以及父对话框中捕获BN_CLICKED消息?
A 于WM_NOTIFY消息相反,通知消息BN_CLICKED作为WM_COMMAND消息发送。因此应用程序应该使用ON_CONTROL_REFLECT_EC而不是ON_NOTIFY_REFLECT

//-------------------------------------------------
Q 如何判断某个对象是否具有当前焦点?
A return (CWnd::GetFocus() == pWnd);

//-------------------------------------------------
Q 如何设置编辑控件的数字限制属性?
A
long Style = GetWindowLong(m_EditCtrl.m_hWnd,GWL_STYLE);
Style |= ES_NUMBER;
SetWindowLong(m_EditCtrl.m_hWnd,GWL_STYLE,Style);

//-------------------------------------------------
Q 希望在LISTCTRL中显示文件,如何才能得到explorer使用的相同图象?
A 可以将系统的ImageList加到LISTCTRL上,然后用具有SHGFI_ICON标志的SHGetFileInfo获取适当的图标索引:
//图象列表设置
HIMAGELIST himagelist;
SHFILEINFO fi;
CImageList m_smalllist;
//得到系统小图标列表的句柄
himagelist = (HIMAGELIST)SHGetFileInfo((LPCTSTR)_T("C:\\"),0,&fi,sizeof(SHFILEINFO),SHGFI_SYSICONINDEX | SHGFI_SMALLICON);
//添加到小图象列表
m_smalllist.Attach(himagelist);
//设置LISTCTRL的图象列表
m_listCtrl.SetImageList(&m_smalllist,LVSIL_SMALL);
//分离图象列表
m_smalllist.Detach();

//-------------------------------------------------
Q 如何在列表的任何一列显示图标,而不是第一列?
A  
LV_ITEM item;
...
item.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_STATE | LVIF_PARAM;
item.iItem = ...//设置行号
item.lParam = ...//如何需要就设置lparam参数
item.iSubItem = ...//设置列号,从0开始的
item.stateMask = LVIS_STATEIMAGEMASK;
item.state = INDEXTOSTATEIMAGEMASK(...);//参数为图标号
item.iImage = ...//设置图标号
item.pszText = ...//显示文本
//插入新项
m_listctrl.InsertItem(&item);
//现在设置图标
m_listctrl.SetItemText(0,4,szField);

//-------------------------------------------------
Q 给LISTBOX添加新项时如何实现自动下滚?
A 在调用AddString后,添加如下代码:
m_listbox.SetTopIndex(m_listbox.GetCount()-1);

//-------------------------------------------------
Q listBox的文本超过框的宽度时,如何让水平滚动条正常工作?
A 用下面的代码,设置滚动条的宽度为最长的字符串宽度
void SetHorizontalExtent(CListBox &listbox)
{
  int index = listbox.GetCount();
  if(index == LB_ERROR)
  return;
  int nExtent = 0;
  if(index)
  {
  CDC *pDC = listbox.GetDC();
CFont *poldfont = pDC->SelectObject(listbox.GetFont());
CString s;
SIZE text;
LONG maxtxt = 0;
while(index--)
{
  listbox.GetText(index,s);
  text = pDC->GetOutputTextExtent(s);
  if(text.cx > maxtxt)
  maxtxt = text.cx;
  }
  text.cx = maxtxt;
  pDC->LPToDP(&text);
  nExtent = text.cx+2;
  pDC->SelectObject(poldfont);
  listbox.ReleaseDC(pDC);
  }  
  listbox.SetHorizontalExtent(nExtent);
}

usidc52011-10-06 20:11Q 在拆分视图的时候,创建了四个视图(2行2列),右下的是CFormView,其他的都是CView,在CMainFrame的OnCreateCilent不管怎么指定CRect的大小,下方的两个视图都占了整个窗口,需要拖动!
A 一般只需要在OnCreateClient的末尾添加:
m_wndSpitter.SetRowInfo(0,200,0);//添加此行代码

//-------------------------------------------------
Q 如何指定拆分窗口的最小宽度?
A 使用CSpitterWnd::SetColumnInfo()
  void SetColumnInfo(int col, //指定列
  int deal, //理想宽度(像素)
  int cxmin); //最小宽度(像素)
在使用SetColumnInfo之后还应该调用RecalLayout();重新调整布局。
  
//--------------------------------------------------
Q 如何判断工具栏是水平还是垂直的?
A if((m_toolbar.GetBarStyle() & CBRS_ALIGN_LEFT) == CBRS_ALIGN_LEFT ||  

(m_toolbar.GetBarStyle() & CBRS_ALIGN_RIGHT) == CBRS_ALIGN_RIGHT)
  AfxMessageBox("vertical");
  else
  AfxMessageBox("horizontal");

//--------------------------------------------------
Q 编程方式修改工具栏按钮的可见性?
A 示例代码:
DWORD style = m_toolbar.GetButtonStyle(nIndex);
if(m_bHide)
  m_toolbar.SetButtonStyle(nIndex,style & ~WS_VISIBLE);
else
  m_toolbar.SetButtonStyle(nIndex,style | WS_VISIBLE);
m_bHide = !m_bHide;

//--------------------------------------------------
Q 如何在状态栏添加按钮并响应?
A 创建一个从CButton派生的CMyButton类,在主框架类添加CMyButton类的成员变量,然后在OnCreate函数中创建按钮,并把它和状态栏关联起来:
m_mybtn.Create("MyButton",WS_CHILD | VISIBLE,CRect(0,0,60,20),&m_WndStatusBar,0);
通过处理BN_CLICKED消息,可以在CMyButton类中处理所有的点击事件

//--------------------------------------------------
Q 如何隐藏属性CPropertySheet的标题栏,使用ModifyStyle(WINDOW_CAPTION,0)没有效果
A 创建自己的CPropertySheet派生类,并覆盖OnInitDialog,转到默认的情况后,使用ModifyStyle来删除WS_CAPTION标志

//--------------------------------------------------
Q 如何让属性页有两行标签?
A 从CPropertySheet派生类,添加PreCreateWindow的处理,在调用基类之前添加代码:
cs.style |= TCS_MULTILINE;

//------------------------------------------------------
Q 如何在属性表的两个页之间传递数据?
A  
CPropertyPage有一个成员函数QuerySiblings(WPARAM, LPARAM)。应用程序可以使用这个函数。
QuerySiblings生成一条PSM_QUERYSIBLINGS消息,它传递给所有的兄弟,也就是属性表上的其他属性页。 一般可创建一个所有页可见的枚举,如:
enum{QUERY_MY_STRING, QUERY_SOMETHING_ELSE,.......}
然后,在一个属性页需要其他属性页中的信息时,使用代码:
CString myString;
if(lL == QuerySiblings(QUERY_MY_STRING,(LPARAM)&myString))
{
  ....//获取字符串
}
提供字符串的页处理PSM_QUERYSIBLINGS消息:
LRESULT CPageThatHasString::OnQuerySiblings(WPARAM wParam, LPARAM lParam)
{
  if(QUERY_MY_STRING == wParam)
  {
  *((CString *)lParam) = _T(“Test String“);
  return 1L;
  }
  else
  return 0L;
}

//----------------------------------------------------------
Q 如何让属性页具有两行标签?
A  
从CPropertySheet派生一个自己的类,添加一个PreCreateWindow的处理,然后在调用基类的处理前加如下代码:cs.style |= TCS_MULTILINE;

//-----------------------------------------------------------
Q 如何隐藏属性页的标题栏?
A  
从CPropertySheet派生一个自己的类,并覆盖OnInitDialog,在转到默认的情况以后,使用 ModifyStyle来删除标题栏标志WS_CAPTION。
  ModifyStyle(WS_CAPTION,0);

//-------------------------------------------------------------------
Q 如何枚举桌面项目?
A
1 得到指向IShellFolder接口的指针
2 得到指向IMalloc接口的指针
3 得到指向IEnumIDList接口的指针
4 提取枚举中下一项目的PIDL
5 测定PIDL代表的标志符的类型
6 处理该项目
7 释放PIDL分配的内存
8 重复4到7步,知道所有的项目都枚举完
9 释放IShellFolder IMalloc IEnumIDList接口的指针

LPSHELLFOLDER lpshellfolder;
LPMALLOC lpmalloc;
LPENUMIDLIST lpidlist;
m_namecount = 0;
HRESULT hr = SHGetDestopFolder(&lpshellfolder);
if(hr == NOERROR)
{
  hr = ::SHGetMalloc(&lpmalloc);
  if(hr == NOERROR)
  {  
  hr = lpshellfolder->EnumObject(NULL,SHCONTF_FOLDERS | SHCONTF_NONFOLDERS,&lpidlist);
  if(hr == NOERROR)
  ProcessFolder(lpshellfolder,lpmalloc,lpidlist);//custom deal function
  lpmalloc->Release();
  lpidlist->Release();
  InValidate();
  }
  lpshellfolder->Release();
}

void ***::ProcessFolder(LPSHELLFOLDER lpshellfolder,LPMALLOC lpmalloc,LPENUMIDLIST lpidlist)
{
  STRRET strret;
  ULONG numfetch;
  LPITEMIDLIST lpitemlist;
  HRESULT hr = lpidlist->Next(1,&lpitemlist,&numfetch);
  while(hr == NOERROR)
  {
  ULONG attributes = SFGAO_FOLDER;
  lpshellfolder->GetAttributes(1,(const struct _ITEMIDLIST **)&lpitemlist,&attributes);
  if(attributes & SFGAO_FOLDER)
  {
  hr = lpshellfolder->GetDiaplayNameOf(lpitemlist,SHGDN_NORMAL,&strret);
  if(m_nameCount < 20)
  m_names[m_namecount++] = strret.str;
  }
  lpmalloc->Free(lpitemlist);
  hr = lpidlist->Next(1,&lpitemlist,&numfetch);
  }
}
//-------------------------------------------------------------------
Q 如何创建桌面快捷方式?
A:
1 initialize com
2 create LShellLink Object
3 Use IShellLink interface to get the pointer about IPersistFile
4 Use IShellLink interface to initialize link
5 Use LPersistFile interface to save the link
6 Release all the com pointer
7 Com return to previous status

1  
HRESULT hr = CoInitialize(NULL);
if(hr == S_OK)
{
  ...//Continue
}

2
IShellLink *pshelllink;
pshelllink = CoCreateInstance(CLSID_ShellLink,NULL,CLSCTX_INPROC_SERVER,IID_IShellLink,(void **)&pshelllink);

3
IPersistFile *persistfile;
persistfile = pshelllink->QueryInterface(IID_IPersistFile,(void **)&persistfile);

4
pshelllink->SetPath("C:\\config.sys");  
pshelllink->SetDescription("ShortCut to config.sys");

5
char path[MAX_PATH];
GetWindowsDirectory(path,MAX_PATH);
int len = strlen(path);
strcpy(&path[len],"\\desktop\\config.lik");
//change the char from ANSI to UNICODE
OLECHAR widepath[MAX_PATH];
MultiByteToWideChar(CP_ACP,0,path,-1,widepath,MAX_PATH);
persistfile->Save(widepath,TRUE);

6
pshelllink->Release();
psersistfile->Release();

7
CoUnInitialize();

有部分代码有点错误,但修改容易

usidc52011-10-06 20:11一、新建一个类,已经保存,为何有时候在"ClassView"里看不到这个类?
答:菜单[工程]->[添加到工程...]->[文件...]添加XXXClass.h和.cpp文件,如果在"ClassView"里还是看不到,"FileView"里找到XXXClass.h文件,在class CXXXClass的CXXXClass这个地方任意改变类名,此时就看到了,之后再改变为CXXXClass.

二、怎么改变对话框上各控件的tab顺序?
答:在资源视图,对话框IDD_DIALOG1下,按Ctrl+D,看到各个控件前有个数字,这个数字便是TAB键顺序,按照你要求重新排序这些数字便可。

三、在使用COMBOBOX时,怎么设置当弹出下拉列表框时的高度?
答:在资源视图,找到IDC_COMBOBOX1,单击“箭头”,此时调整个高度便是下拉列表框可见时的高度,如果是动态create出来的,参数rect的高便是下拉列表框可见时的高,而CComboBox::SetItemHeight(-1, 15)为设置组合框高度。

四、在IDC_LIST1与CListCtrl m_list关联后,有时输入m_list.后并没有智能提示出CListCtrl的函数是怎么回事?
答:是VC开发环境的BUG,我个人的解决方法是先用CXXXClass::出现提示后定位选择函数,再把CXXXClass::换成变量名.这个问题希望有朋友可以解答下。

五、在用TabCtrl控件时,为何不可以按标签页来定制某页上的控件?
答:VC的TabCtrl不支持这样的操作,方法是先建立一对话框资源IDD_DIALOGX(跟工程无关的),把某标签页上所有控件调整大小并排版,然后全选粘贴在工程中正式的对话框IDD_DIALOG1中,反复这样的操作,tabctrl有几页就招待几次,最后再添加TabCtrl在IDD_DIALOG1中,然后通过将IDC_TABCTRL1与CTabCtrl m_tab1关联,之后添加IDC_TABCTRL1的TCN_SELCHANGE消息处理函数,便可用int nPage = m_tab1.GetCurSel()取得当前标签号,接着再用switch(nPage)来选择处理各页显示的控件,控件显示可用ShowWindow(SW_SHOW/SW_HIDE)来控制,小技巧,在第一步时,可有意识的将第某页上的控件ID设为IDC_TAB1_NAME,IDC_TAB2_NAME,这样的形式,便于清楚的知道哪个控件是第几页中的。

usidc52011-10-06 20:12明明某个函数在某个头文件里面,但是将该头文件includ进来后编译仍然提示函数未定义

一般是由于该函数要求在较高windows版本上才能运行,解决方法是修改 WINVER 宏的定义
在引用该头文件前加上
#define WINVER 0x501  

============================================
Minimum system required Macros to define  
Windows Vista _WIN32_WINNT>=0x0600  
WINVER>=0x0600  
Windows Server 2003 _WIN32_WINNT>=0x0502  
WINVER>=0x0502  
Windows XP _WIN32_WINNT>=0x0501  
WINVER>=0x0501  
Windows 2000 _WIN32_WINNT>=0x0500  
WINVER>=0x0500  
Windows NT 4.0 _WIN32_WINNT>=0x0400  
WINVER>=0x0400  
Windows Me _WIN32_WINDOWS=0x0500  
WINVER>=0x0500  
Windows 98 _WIN32_WINDOWS>=0x0410  
WINVER>=0x0410  
Windows 95 _WIN32_WINDOWS>=0x0400  
WINVER>=0x0400  

usidc52011-10-06 20:13为什么要使用GetSafeHwnd()函数


当我们想得到一个窗口对象(CWnd的派生对象)指针的句柄(HWND)时,最安全的方法是使用GetSafeHwnd()函数,通过下面的例子来看其理由:

CWnd *pwnd = FindWindow(“ExploreWClass”,NULL); //希望找到资源管理器

HWND hwnd = pwnd->m_hwnd; //得到它的HWND

这样的代码当开始得到的pwnd为空的时候就会出现一个“General protection error”,并关闭应用程序,因为一般不能对一个NULL指针访问其成员,如果用下面的代码:

CWnd *pwnd = FindWindow(“ExploreWClass”,NULL); //希望找到资源管理器

HWND hwnd = pwnd->GetSafeHwnd(); //得到它的HWND

就不会出现问题,因为尽管当pwnd是NULL时,GetSafeHwnd仍然可以用,只是返回NULL,通过GetSafeHwnd()的实现代码就更清楚了:

_AFXWIN_INLINE HWND CWnd::GetSafeHwnd() const

{

  return this == NULL?NULL:m_hWnd;

}

3. 如何使程序处于极小状态


如果我们不想让程序的窗口被别人看见,就可以让它保持在极小状态:在恢复程序窗口的时候,Window会发送WM_QUERYOPEN消息,只要在其消息处理函数里返回false就可以了。

BOOL CmainFrame::OnQueryOpen()

{

return false;

}

4. 如何禁止和能用关闭按钮


Cmenu *pmenu = AfxGetMainWnd()->GetSystemMenu(FALSE);

if(pmenu)

{

pmenu->EnableMenuItem(SC_CLOSE,MF_BYCOMMAND|MF_GRAYED);

}

恢复时只需将MF_GRAYED改为MF_ENABLED

5. 如何在程序中延时


方法一:


使用sleep函数,如延时2秒,用sleep(2000);

方法二:


使用sleep函数的不利在于延时期间不能处理其他的消息,如果时间太长,就好象死机一样,利用ColeDateTime类和ColeDateTimeSpan类实现延时就不会出现那样的问题:

ColeDateTime start_time = ColeDateTime::GetCurrentTime();

ColeDateTimeSpan end_time = ColeDateTime::GetCurrentTime()-start_time;

While(end_time.GetTotalSeconds() <= 2)

{

MSG msg;

GetMessage(&msg,NULL,0,0);

PreTranslateMessage(&msg);

End_time = ColeDateTime::GetCurrentTime-start_time;

}

这样在延时的时候我们也能够处理其他的消息。




看了论坛里的一些总结文章很好,我把里面没有怎么看到的也写了一点点出来,不知道有没有重复,希望能有些微的作用.



本文引用通告地址:
http://blog.csdn.net/laiyiling/services/trackbacks/22293.aspx  

6. 如何创建可伸缩的对话框


在进行对话框的设计时,有时候我们需要设计可伸缩的对话框,当用户按下某个按钮时弹出或隐藏对话框的下半部分。

(1)、首先在对话框中建立一个图片控件把ID设为IDC_DIVIDER,Type设置为矩形,Color设置为黑色,并将其设定为一线状,拖放在适当的位置做为伸缩对话框的分割线,属性设为不可见。

(2)、实现的原理:先获取对话框的尺寸大小,然后根据的位置来确定缩减后的对话框大小,其实对话框伸缩的变化就是的值,在缩减对话框后,我们要使不可见的部分控件被禁止,以禁止加速键和TAB键对其的操作,在扩展对话框后,原来被禁止的控件又要使能。

先在对话框上的伸缩按钮添加单击消息处理函数:

void C***Dlg::OnButtonExpand()

{

static int bexpand = FALSE; //设初始时为已扩展的

ExpandDialog(IDC_DIVIDER,bexpand);//调用扩展或缩减处理函数

Bexpand = !bexpand;//状态取反,为下次的单击处理准备

}

//在对话框中添加一个成员函数ExpandDialog,用于扩展或缩减

void C***Dlg::ExpandDialog(int nResourceID,BOOL bexpand)

{

//参数nResourceID表示分割线的ID

//参数bexpand为TRUE时表示要扩展对话框,否则缩减对话框

static CRect rcLarge;

static CRect rcSmall;

if(rcLarge.IsRectNULL()) //首次使用时记下对话框的最大、最小尺寸

{

  CRect rcLandmark;

  CWnd *pwndLand = GetDlgItem(nResourceID);

  ASSERT(pwndLand);

  GetWindowRect(rcLarge);

  pwndLand->GetWindowRect(rcLandmark);

  rcSmall = rcLarge;

  rcSmall.bottom = rcLandmark.bottom;

}

if(bexpand)

{

  SetWindowPos(NULL,0,0,rcLarge.Width(),rcLarge.Height(),

SWP_NOMOVE|SWP_NOZORDER);

EnableVisible();

}

else

{

  SetWindowPos(NULL,0,0,rcSmall.Width(),rcSmall.Height(),

SWP_NOMOVE|SWP_NOZORDER);

EnableVisible();

}

}



//在对话框中添加一个成员函数EnableVisible,用于能用和禁止部分控件

void C***Dlg:: EnableVisible()

{

CWnd *pwnd = GetDlgItem(GW_CHILD);

CRect retest;

CRect rcControl;

CRect rcShow;

GetWindowRect(rcShow);

While(pwnd != NULL)

{

pwnd->GetWindowRect(rcControl);

if(rcTest.IntersectRect(rcShow,rcControl))

  pwnd->EnableWindow(TRUE);

else

  pwnd->EnableWindow(FALSE);

pwnd = pwnd->GetWindow(GW_HWNDNEXT);

  }

}

7. 为什么有RichEdit控件的对话框无法显示

如果在对话框上放一个RichEdit控件,往往发现对话框却无法正常显示,这是因为应用程序还没有为RichEdit控件的编辑功能做好准备,解决办法就是在应用程序的InitInstance()函数调用AfxInitRichEdit()函数初始化RichEdit控件

usidc52011-10-06 20:13一、打开CD-ROM
mciSendString("Set cdAudio door open wait",NULL,0,NULL);


二、关闭CD_ROM
mciSendString("Set cdAudio door closed wait",NULL,0,NULL);


三、关闭计算机
OSVERSIONINFO OsVersionInfo; //包含操作系统版本信息的数据结构
OsVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx(&OsVersionInfo); //获取操作系统版本信息
if(OsVersionInfo.dwPlatformId == VER_PLATFORM_WIN32_Windows)
{
//Windows98,调用ExitWindowsEx()函数重新启动计算机
DWORD dwReserved;
ExitWindowsEx(EWX_REBOOT,dwReserved); //可以改变第一个参数,实现注销用户、
//关机、关闭电源等操作

// 退出前的一些处理程序
}


四、重启计算机
typedef int (CALLBACK *SHUTDOWNDLG)(int); //显示关机对话框函数的指针
HINSTANCE hInst = LoadLibrary("shell32.dll"); //装入shell32.dll
SHUTDOWNDLG ShutDownDialog; //指向shell32.dll库中显示关机对话框函数的指针
if(hInst != NULL)
{
//获得函数的地址并调用之
ShutDownDialog = (SHUTDOWNDLG)GetProcAddress(hInst,(LPSTR)60);
(*ShutDownDialog)(0);
}


五、枚举所有字体
LOGFONT lf;
lf.lfCharSet = DEFAULT_CHARSET; // Initialize the LOGFONT structure
strcpy(lf.lfFaceName,"");
CClientDC dc (this);

//Enumerate the font families
::EnumFontFamiliesEx((HDC) dc,&lf,
(FONTENUMPROC) EnumFontFamProc,(LPARAM) this,0);

//枚举函数
int CALLBACK EnumFontFamProc(LPENUMLOGFONT lpelf,LPNEWTEXTMETRIC lpntm,DWORD nFontType,long lparam)
{
// Create a pointer to the dialog window
CDay7Dlg* pWnd = (CDay7Dlg*) lparam;
// add the font name to the list box

pWnd ->m_ctlFontList.AddString(lpelf ->elfLogFont.lfFaceName);

// Return 1 to continue font enumeration
return 1;
}
其中m_ctlFontList是一个列表控件变量


六、一次只运行一个程序实例,如果已运行则退出
if( FindWindow(NULL,"程序标题")) exit(0);


七、得到当前鼠标所在位置
CPoint pt;
GetCursorPos(&pt); //得到位置


八、上下文菜单事件触发事件:
OnContextMenu事件


九、显示和隐藏程序菜单
CWnd *pWnd=AfxGetMainWnd();
if(b_m) //隐藏菜单
{
pWnd->SetMenu(NULL);
pWnd->DrawMenuBar();
b_m=false;
}
else
{
CMenu menu;
menu.LoadMenu(IDR_MAINFRAME); ////显示菜单 也可改变菜单项
pWnd->SetMenu(&menu);
pWnd->DrawMenuBar();
b_m=true;
menu.Detach();
}

usidc52011-10-06 20:14十、获取可执行文件的图标
HICON hIcon=::ExtractIcon(AfxGetInstanceHandle(),_T("NotePad.exe"),0);
if (hIcon &&hIcon!=(HICON)-1)
{
pDC->DrawIcon(10,10,hIcon);
}
DestroyIcon(hIcon);
十一、窗口自动靠边程序演示
BOOL AdjustPos(CRect* lpRect)
{
//自动靠边
int iSX=GetSystemMetrics(SM_CXFULLSCREEN);
int iSY=GetSystemMetrics(SM_CYFULLSCREEN);

RECT rWorkArea;
BOOL bResult = SystemParametersInfo(SPI_GETWORKAREA, sizeof(RECT), &rWorkArea, 0);

CRect rcWA;
if(!bResult)
{
//如果调用不成功就利用GetSystemMetrics获取屏幕面积
rcWA=CRect(0,0,iSX,iSY);
}
else
rcWA=rWorkArea;

int iX=lpRect->left;
int iY=lpRect->top;
if(iX < rcWA.left + DETASTEP && iX!=rcWA.left)
{
//调整左
//pWnd->SetWindowPos(NULL,rcWA.left,iY,0,0,SWP_NOSIZE);
lpRect->OffsetRect(rcWA.left-iX,0);
AdjustPos(lpRect);
return TRUE;
}
if(iY < rcWA.top + DETASTEP && iY!=rcWA.top)
{
//调整上
//pWnd->SetWindowPos(NULL ,iX,rcWA.top,0,0,SWP_NOSIZE);
lpRect->OffsetRect(0,rcWA.top-iY);
AdjustPos(lpRect);
return TRUE;
}
if(iX + lpRect->Width() > rcWA.right - DETASTEP && iX !=rcWA.right-lpRect->Width())
{
//调整右
//pWnd->SetWindowPos(NULL ,rcWA.right-rcW.Width(),iY,0,0,SWP_NOSIZE);
lpRect->OffsetRect(rcWA.right-lpRect->right,0);
AdjustPos(lpRect);
return TRUE;
}
if(iY + lpRect->Height() > rcWA.bottom - DETASTEP && iY !=rcWA.bottom-lpRect->Height())
{
//调整下
//pWnd->SetWindowPos(NULL ,iX,rcWA.bottom-rcW.Height(),0,0,SWP_NOSIZE);
lpRect->OffsetRect(0,rcWA.bottom-lpRect->bottom);
return TRUE;
}
return FALSE;
}
//然后在ONMOVEING事件中使用所下过程调用
CRect r=*pRect;
AdjustPos(&r);
*pRect=(RECT)r;


十二、给系统菜单添加一个菜单项
给系统菜单添加一个菜单项需要进行下述三个步骤:
首先,使用Resource Symbols对话(在View菜单中选择Resource Symbols...可以显示该对话)定义菜单项ID,该ID应大于

0x0F而小于0xF000;
其次,调用CWnd::GetSystemMenu获取系统菜单的指针并调用CWnd:: Appendmenu将菜单项添加到菜单中。下例给系统菜单添加

两个新的菜单项。
int CMainFrame:: OnCreate (LPCREATESTRUCT lpCreateStruct)
{

//Make sure system menu item is in the right range.
ASSERT(IDM_MYSYSITEM<0xF000);
//Get pointer to system menu.
CMenu* pSysMenu=GetSystemMenu(FALSE);
ASSERT_VALID(pSysMenu);
//Add a separator and our menu item to system menu.
CString StrMenuItem(_T ("New menu item"));
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_MYSYSITEM, StrMenuItem);

}

十三、运行其它程序
//1、运行EMAIL或网址
char szMailAddress[80];
strcpy(szMailAddress,"mailto:netVC@21cn.com");
ShellExecute(NULL, "open", szMailAddress, NULL, NULL, SW_SHOWNORMAL);

//2、运行可执行程序
WinExec("notepad.exe",SW_SHOW); //运行计事本


十四、动态增加或删除菜单
1、 增加菜单
//添加
CMenu *mainmenu;
mainmenu=AfxGetMainWnd()->GetMenu(); //得到主菜单
(mainmenu->GetSubMenu (0))->AppendMenu (MF_SEPARATOR);//添加分隔符
(mainmenu->GetSubMenu (0))->AppendMenu(MF_STRING,ID_APP_ABOUT,_T("Always on &Top")); //添加新的菜单项
DrawMenuBar(); //重画菜单

2、 删除菜单
//删除
CMenu *mainmenu;
mainmenu=AfxGetMainWnd()->GetMenu(); //得到主菜单
CString str ;
for(int i=(mainmenu->GetSubMenu (0))->GetMenuItemCount()-1;i>=0;i--) //取得菜单的项数。
{
(mainmenu->GetSubMenu (0))->GetMenuString(i,str,MF_BYPOSITION);
//将指定菜单项的标签拷贝到指定的缓冲区。MF_BYPOSITION的解释见上。
if(str=="Always on &Top") //如果是刚才我们增加的菜单项,则删除。
{
(mainmenu->GetSubMenu (0))->DeleteMenu(i,MF_BYPOSITION);
break;
}
}
十五、改变应用程序的图标



十六、另一种改变窗口标题的方法
使用语句 CWnd* m_pCWnd = AfxGetMainWnd( ),然后,再以如下形式调用SetWindowText()函数:
SetWindowText( *m_pCWnd,(LPCTSTR)m_WindowText);// m_WindowText可以是一个CString类的变量。


十七、剪切板上通过增强元文件拷贝图像数据
下面代码拷贝通过元文件拷贝图像数据到任何应用程序,其可以放置在CView派生类的函数中。
CMetaFileDC * m_pMetaDC = new CMetaFileDC();
m_pMetaDC->CreateEnhanced(GetDC(),NULL,NULL,"whatever");
//draw meta file
//do what ever you want to do: bitmaps, lines, text...
//close meta file dc and prepare for cliPBoard;
HENHMETAFILE hMF = m_pMetaDC->CloseEnhanced();

//copy to clipboard
OpenClipboard();
EmptyClipboard();
::SetClipboardData(CF_ENHMETAFILE,hMF);
CloseClipboard();
//DeleteMetaFile(hMF);
delete m_pMetaDC;


十八、剪切板上文本数据的传送
把文本放置到剪接板上:
CString source;
//put your text in source
if(OpenClipboard())
{
HGLOBAL clipbuffer;
char * buffer;
EmptyClipboard();
clipbuffer = GlobalAlloc(GMEM_DDESHARE, source.GetLength()+1);
buffer = (char*)GlobalLock(clipbuffer);
strcpy(buffer, LPCSTR(source));
GlobalUnlock(clipbuffer);
SetClipboardData(CF_TEXT,clipbuffer);
CloseClipboard();
}

从剪接板上获取文本:
char * buffer;
if(OpenClipboard())
{
buffer = (char*)GetClipboardData(CF_TEXT);
//do something with buffer here
//before it goes out of scope
}
CloseClipboard();


十九、将捕捉屏幕图像到剪切版中
void CShowBmpInDlgDlg::OnCutScreen()
{
ShowWindow(SW_HIDE);
RECT r_bmp={0,0,::GetSystemMetrics(SM_CXSCREEN),
::GetSystemMetrics(SM_CYSCREEN)};
HBITMAP hBitmap = CopyScreenToBitmap(&r_bmp);

//hWnd为程序窗口句柄
if (OpenClipboard())
{
EmptyClipboard();
SetClipboardData(CF_BITMAP, hBitmap);
CloseClipboard();
}
ShowWindow(SW_SHOW);
}

HBITMAP CShowBmpInDlgDlg::CopyScreenToBitmap(LPRECT lpRect)//lpRect 代表选定区域
{
HDC hScrDC, hMemDC; // 屏幕和内存设备描述表
HBITMAP hBitmap, hOldBitmap; // 位图句柄
int nX, nY, nX2, nY2; // 选定区域坐标
int nWidth, nHeight; // 位图宽度和高度
int xScrn, yScrn; // 屏幕分辨率

if (IsRectEmpty(lpRect)) // 确保选定区域不为空矩形
return NULL;

//为屏幕创建设备描述表
hScrDC = CreateDC("DISPLAY", NULL, NULL, NULL);

//为屏幕设备描述表创建兼容的内存设备描述表
hMemDC = CreateCompatibleDC(hScrDC);

// 获得选定区域坐标
nX = lpRect->left;
nY = lpRect->top;
nX2 = lpRect->right;
nY2 = lpRect->bottom;

// 获得屏幕分辨率
xScrn = GetDeviceCaps(hScrDC, HORZRES);
yScrn = GetDeviceCaps(hScrDC, VERTRES);

//确保选定区域是可见的
if (nX<0)
nX = 0;
if (nY<0)
nY = 0;
if (nX2>xScrn)
nX2 = xScrn;
if (nY2>yScrn)
nY2 = yScrn;

nWidth = nX2 - nX;
nHeight = nY2 - nY;

// 创建一个与屏幕设备描述表兼容的位图
hBitmap = CreateCompatibleBitmap(hScrDC, nWidth, nHeight);

// 把新位图选到内存设备描述表中
hOldBitmap =(HBITMAP)SelectObject(hMemDC, hBitmap);

// 把屏幕设备描述表拷贝到内存设备描述表中
BitBlt(hMemDC, 0, 0, nWidth, nHeight,
hScrDC, nX, nY, SRCCOPY);

//得到屏幕位图的句柄
hBitmap = (HBITMAP)SelectObject(hMemDC, hOldBitmap);

//清除
DeleteDC(hScrDC);
DeleteDC(hMemDC);

// 返回位图句柄
return hBitmap;
}


二十、如何将位图缩放显示在Static控件中
//在Staic控件内显示位图
void CShowBmpInDlgDlg::ShowBmpInStaic()
{
CBitmap hbmp;
HBITMAP hbitmap;

//将pStatic指向要显示的地方
CStatic *pStaic=(CStatic*)GetDlgItem(IDC_IMAGE);

//装载资源 MM.bmp是我的一个文件名,用你的替换
hbitmap=(HBITMAP)::LoadImage (::AfxGetInstanceHandle(),"MM.bmp",
IMAGE_BITMAP,0,0,LR_LOADFROMFILE|LR_CREATEDIBSECTION);
hbmp.Attach(hbitmap);

//获取图片格式
BITMAP bm;
hbmp.GetBitmap(&bm);

CDC dcMem;
dcMem.CreateCompatibleDC(GetDC());
CBitmap *poldBitmap=(CBitmap*)dcMem.SelectObject(hbmp);

CRect lRect;
pStaic->GetClientRect(&lRect);
lRect.NormalizeRect();

//显示位图
pStaic->GetDC()->StretchBlt(lRect.left ,lRect.top ,lRect.Width(),lRect.Height(),
&dcMem,0 ,0,bm.bmWidth,bm.bmHeight,SRCCOPY);
dcMem.SelectObject(&poldBitmap);
}

usidc52011-10-06 20:15如何在MFC 中插入图片背景来改变默认灰色的界面,,,,方法如下:


1、把你的图包含到程序的“Bitmap资源”里(菜单->Insert->resource->bitmap->import->选择自己的位图就好了),提示说该资源使用了大于256色的调色板,在VC里无法编辑等等,点确定就可以 。

2、定位到 void CXXXDlg::OnPaint(),在if()...else()中的else()下添加如下代码  
  else  
  {  
  //CDialog::OnPaint();//要禁止这个调用  
  CPaintDC dc(this);  
  CRect rect;  
  GetClientRect(&rect);  

  CDC dcMem;  
  dcMem.CreateCompatibleDC(&dc);  

  CBitmap bmpBackground;  
  bmpBackground.LoadBitmap(IDB_BITMAP);  
  //IDB_BITMAP是你自己的图对应的ID  

  BITMAP bitmap;  
  bmpBackground.GetBitmap(&bitmap);  
  CBitmap *pbmpOld=dcMem.SelectObject(&bmpBackground);  
  
  dc.StretchBlt(0,0,rect.Width(),rect.Height(),&dcMem,0,0,  
  bitmap.bmWidth,bitmap.bmHeight,SRCCOPY);  
  }

usidc52011-10-06 20:16CFileDialog只能选择文件。用SHBrowseForFolder可以选择文件夹。

CString strPath = "";
BROWSEINFO bInfo;
ZeroMemory(&bInfo, sizeof(bInfo));

bInfo.hwndOwner = m_hWnd;
bInfo.lpszTitle = _T("Select the path: ");
bInfo.ulFlags = BIF_RETURNONLYFSDIRS;
    
LPITEMIDLIST lpDlist;
lpDlist = SHBrowseForFolder(&bInfo);

if(lpDlist != NULL)
{
  TCHAR *chPath=new TCHAR[300];  
  SHGetPathFromIDList(lpDlist, chPath);
  strPath = chPath;  
  delete[] chPath;
}

usidc52011-10-07 19:39
CRectTracker(俗称“橡皮筋”类)是一个非常有意思的类。你在Windows中,在桌面上用鼠标拖拽,便可以看到一个虚线的矩形框,它便是橡皮筋.它可以用做显示边界,你也可以扽它的八个角用来放大缩小,做框选使用。如何通过编程来实现这种功能呢?这就是CRectTracker类的作用;
介绍橡皮筋类前,先介绍其他两个类:
Cpoint 类 或Point类,cpoint.x   cpoint.y,作为屏幕上的坐标上的x和y 轴的坐标。
CRect类,既矩形类。
crect.left   crect.bottom   crect.top   crect.right,
Crect::setrect(crect.left, crect.top, crect.right,crect.bottom);
CrectTracker 类成员:
一 数据成员:(摘自msdn 2000,省略了一些)
1.
m_rect

当前橡皮筋矩形的矩形框的位置
2. m_sizeMin
决定橡皮筋矩形的最新的长和宽
3.m_nStyle
橡皮筋矩形的形式
如:
CRectTracker::solidLine  
用实线标记矩形框
CRectTracker::dottedLine 虚线
CRectTracker::hatchedBorder 影阴线
CRectTracker::resizeInside   改变大小的句柄在橡皮筋矩形框内部(点在橡皮筋矩形框
里面来改变大小)
CRectTracker::resizeOutside 改变大小的句柄在橡皮筋矩形框外部
CRectTracker::hatchInside 影阴线布满总个矩形框
二 成员函数:
1.void Draw( CDC* pDC ) const;

这个函数用来划矩形框的边框和内部区域。
2.void GetTrueRect( LPRECT lpTrueRect ) const;
这个函数用来换回矩形框的 矩形坐标,参数为CRECT类型,返回矩形
3.int HitTest( CPoint point ) const;
当你鼠标被按下的时候,你可以调用这个函数,它将返回鼠标点在了矩形框的什么位置:可以看出,返回值如果大于等于零则在四边形区域之内。如果小于则说明不在区域范围之内。
返回值
代表的含义
-1
点在了四边形的外部
0
左上角
1
右上角
2
右下角
3
左下角(0,1,2,3顺时针转了一圈)
4
顶部
5
右部
6
底部
7
左部(还是顺时针转了一圈)
8
点在了四边形的内部,但没有击中前面的那八个点
4.BOOL SetCursor( CWnd* pWnd, UINT nHitTest ) const;
调用这个函数用来 当鼠标放在矩形框时,显示各种鼠标形象
5.BOOL Track( CWnd* pWnd, CPoint point, BOOL bAllowInvert = FALSE, CWnd* pWndClipTo = NULL );
这个函数用来显示当人们用鼠标改变矩形框大小 或 拖拽矩形框时显示矩形框动作
一般由WM_LBUTTONDOWN 消息来触发这个函数, 不需要编写MouseMove函数,矩形框它就自动的变大小了呢?这就是Track()函数的功劳,从调用它到抬起鼠标键为止,它时刻的改变四边形的大小。
6.BOOL TrackRubberBand( CWnd* pWnd, CPoint point, BOOL bAllowInvert = TRUE );
当鼠标在空区域拖拽时显示橡皮筋矩形框,让鼠标画一个橡皮筋区域,第一个参数,画“橡皮筋”的窗体的指针,当然是this ,第二个参数,画“橡皮筋”的起始点。 让我们注意第三个参数,它非常有意思。当你使用 FALSE时(TRUE 值是缺省的),你的“橡皮筋”只能从左上到右下的画,不允许反向。编译运行一下FALSE这个值。
特别值得注意的是:在TrackRubberBand的过程中是以右键的抬起为结束的,这其间并没有CViewMouseMove发生。这一点一定要记住!这时鼠标画过的区域已经记录在CrectTracker 类数据成员 m_rect里面了,既CrectTracker:: m_rect.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
下面是工程应用中的代码
void Cmfc02Dlg::OnLButtonDown(UINT nFlags, CPoint point)
{
//当鼠标左键按下时
CRectTracker temp;
temp.TrackRubberBand(this,point,TRUE);
temp.m_rect.NormalizeRect(); //标题化矩形

//获取选取的矩形
RECT rc;
temp.GetTrueRect(&rc);
//在选取的整个过程,CRectTracker.TrackRubberBand()是阻塞的,直到鼠标左键弹起才返回,并且接管了WM_LBUTTONUP消息
SendMessage(WM_LBUTTONUP);
CDialog::OnLButtonDown(nFlags, point);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
如果要自己实现的话,可以这样做
BOOL bDown = FALSE;
CPoint posLast, posDown;
void Cmfc02Dlg::OnLButtonDown(UINT nFlags, CPoint point)
{
bDown = TRUE;
posLast = point;
posDown = point;
CDialog::OnLButtonDown(nFlags, point);
}
void Cmfc02Dlg::OnLButtonUp(UINT nFlags, CPoint point)
{
if (bDown)
{
   bDown = FALSE;
   CClientDC   dc(this);  
   CRect   rect(posDown.x,   posDown.y,   posLast.x,   posLast.y);  
   rect.NormalizeRect();  
  
dc.DrawFocusRect(rect);   //异或前面画过的矩形
}
CDialog::OnLButtonUp(nFlags, point);
}
void Cmfc02Dlg::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
if (bDown)
{
   CClientDC dc(this);
  if (posLast.x != posDown.x && posLast.y != posDown.y)
   {
    CRect   rect(posDown.x,   posDown.y,   posLast.x,   posLast.y);  
    rect.NormalizeRect();
    dc.DrawFocusRect(rect);
   }
   CRect rect(posDown.x,   posDown.y,   point.x,   point.y);  
   rect.NormalizeRect();  
   dc.DrawFocusRect(rect);  
      
  posLast   =   point;  
}
CDialog::OnMouseMove(nFlags, point);
}

usidc52011-10-08 10:45
在项目属性页-->配置属性-->C/C++-->预处理器-->预处理器定义 添加:_CRT_SECURE_NO_DEPRECATE
注意:Debug和Release都要添加

微软解释说非安全库函数sprintf、sscanf、strcpy等都是不安全的,应该用sprintf_s、sscanf_s、strcpy_s取代
微软提出了如下10点建议:
      1.
不要认为 strcpy_s strncpy_s( 以及其他的字符串函数)(在空间不够的时候)会自动终止拷贝(truncate截断,不截断则意味着溢出).如果需要自动截断,请使用strncpy_s (同时使用_TRUNCATE作为长度参数)

      2.
记住fopen_s缺省是独占模式。如需共享使用文件,应该使用_sopen

      3.
别忘了_dupenv_s,它将比_getenv_s更容易使用,因为它能自动分配一个正确长度的内存(buffer)
      4.
scanf_s中小心参数顺序。
      5.
确定printf_s中格式字符串的正确。
      6.
使用_countof(x)来取代sizeof(x)/sizeof(element). _countof将会正确的计算元素个数,而且如果x是一个指针,编译器将会发出一个警告(来提醒程序员,仅针对C++编译)
      7.
记住所有的sizes(大小,非长度)都是使用characters(字符,unicode下一个字符占2byte)作为单位,而不是bytes(字节
).
      8.
记住所有的sizes(大小,非长度,缘由同上)包含了字符串结束符'\0'(即别忘了很多情况下size需要+1)

      9.
调试的时候监视数据0xfd (在调试版本下)0xfd将会被填充在数据(buffer,通常是字符串)的结尾处。如果运行非你所愿,可能会得到一个长度错误。
      10.
检查所有的错误。许多新函数相比旧函数,能返回(表示)错误信息(的数值)


usidc52011-10-09 18:03CWindowDC   dc(this);//this为当前窗口指针
HDC   hDC=dc.GetSafeHdc();



或者


HDC GetDC(
  HWND hWnd   // handle to window
);




usidc52011-10-09 18:40GetBuffer(int size)是用来返回一个你所指定大小可写内存的成员方法。它和被重载的操作符LPCTSTR还是有点本质区别的,LPCTSTR是直接返回一个只读内存的指针,而GetBuffer则是返回一个可以供调用者写入的内存,并且,你可以给定大小。下面是个简单的,但也是非常典型的例子:
    int readFile(CString& str, const CString& strPathName)
    {
        FILE* fp = fopen(strPathName, "r"); // 打开文件
        fseek(fp, 0, SEEK_END);
        int nLen = ftell(fp); // 获得文件长度
        fseek(fp, 0, SEEK_SET); // 重置读指针
        char* psz = str.GetBuffer(nLen);
        fread(psz, sizeof(char), nLen, fp); //读文件内容
        str.ReleaseBuffer(); //千万不能缺少
        fclose(fp);
    }
    上面的函数是GetBuffer函数最典型的用法了,其实它就相当于申请一块nLen大小的内存,只不过,这块内存是被引用在CString对象的内部而已,这是非常有效的一种用法,如果不直接用GetBuffer函数来申请的话,那么你必须用new操作符(或者malloc()函数)在CString的外部申请,然后再将申请的内存拷贝到CString对象中,显然这是一个非常冗余的操作
,会使你函数的效率大大下降。
    ReleaseBuffer函数是用来告诉CString对象,你的GetBuffer所引用的内存已经使用完毕,现在必须对它进行封口,否则CString将不会知道它现在所包含的字符串的长度,所以在使用完GetBuffer之后,必须立即调用ReleaseBuffer函数重置CString的内部属性,其实也就是头部信息。



成对编程的习惯很重要呀,正如楼主所言,{},括号要成对;new和delete要成对;malloc和free要成对,CString的getbuffer和releasebuffer更要成对!压一阴一阳谓之道呀!

usidc52011-10-09 18:42
无法执行添加/移除操作,因为代码元素CMainFrame是只读的

今天写在VS2005环境下添加了一大堆菜单,然后逐个给菜单添加事件处理程序,结果在添加完第三个之后就提示了“无法执行添加/移除操作,因为代码元素CMainFrame是只读的”,想必可能是相应的CPP/H文件只读,查看了文件属性,结果是正常的,把VS2005关掉重启,问题如故,因为我一直添加,不可能出现资源重复定义的情况,当然可以在BEBIN_MESSAGE_MAP中手动添加,因为菜单项太多了,太麻烦,最后,关掉VS2005,删除x.ncb文件,再重新打开程序,问题解决。
NCB:NCB是“No Compile Browser”的缩写,其中存放了供ClassView、WizardBar和Component Gallery使用的信息,由VC开发环境自动生成。




出现这种现象,多数是因为你的工程所在文件夹的属性设置为了“只读”,你可以关闭解决方案,然后重新打开,就可以了,如果以后不想出现这样的情况,把工程所在的文件夹属性中的“只读”去掉,就可以了。
解决方案:
1、重启VS2005
2、打开Resource.h文件看看 一看就知道了 有些定义重复了 可以手动改掉 保存 编辑器重新加载
3、关闭解决方案,删除ncb文件重新添加即可
4、实在不行就手动添加消息处理
5、查看.h和.cpp文件的属性,有可能是只读的,修改属性后就可以了
6、把你要添加事件的对话框相应的类文件(*.h和*.cpp)给关了就可以了
在BEGIN_MESSAGE_MAP(。。。)
//这里要删掉你原先已经增加过的消息隐射函数
END_MESSAGE_MAP()
你肯定手动删除过 又没删干净 又重新添加了


巧了,今天我也遇到了这个问题。把ncb文件删掉重新打开就解决了。
解决方案:
1、重启VC
2、打开Resource.h文件看看 一看就知道了 有些定义重复了 可以手动改掉 保存 编辑器重新加载 。。。。。。。。
3、关闭解决方案,删除ncb文件重新添加即可(在你创建的项目里,一般在我的文档/d:\我的文档\Visual Studio 2008\Projects\项目名\里)
4、实在不行就手动添加消息处理
5、查看.h和.cpp文件的属性,有可能是只读的,修改属性后就可以了
6、把你要添加事件的对话框相应的类文件(*.h和*.cpp)给关了就可以了

usidc52011-10-10 12:27问题是这样产生的.在OnEraseBkGnd中,如果你不调用原来缺省
的OnEraseBkGnd只是重画背景则不会有闪烁.而在OnPaint里面,
由于它隐含的调用了OnEraseBkGnd,而你又没有处理OnEraseBkGnd
函数,这时就和窗口缺省的背景刷相关了.缺省的
OnEraseBkGnd操作使用窗口的缺省背景刷刷新背景(一般情况
下是白刷),而随后你又自己重画背景造成屏幕闪动.
另外一个问题是OnEraseBkGnd不是每次都会被调用的.如果你
调用Invalidate的时候参数为TRUE,那么在OnPaint里面隐含
调用BeginPaint的时候就产生WM_ERASEBKGND消息,如果参数
是FALSE,则不会重刷背景.

所以解决方法有三个半:
1.用OnEraseBkGnd实现,不要调用原来的OnEraseBkGnd函数.
2.用OnPaint实现,同时重载OnEraseBkGnd,其中直接返回.
3.用OnPaint实现,创建窗口时设置背景刷为空
4.用OnPaint实现,但是要求刷新时用Invalidate(FALSE)这样
的函数.(不过这种情况下,窗口覆盖等造成的刷新还是要闪一
下,所以不是彻底的解决方法)
都挺简单的.
------------------------------------------------------
在MFC中 任何一个window组件的绘图 都是放在这两个member function中
在设定上 OnEraseBkgnd()是用来画底图的 而OnPaint()是用来画主要对象的
举例说明 一个按钮是灰色的 上面还有文字
则OnEraseBkgnd()所做的事就是把按钮画成灰色
而OnPaint()所做的事 就是画上文字

既然这两个member function都是用来画出组件的
那为何还要分OnPaint() 与 OnEraseBkgnd() 呢
其实OnPaint() 与 OnEraseBkgnd() 特性是有差的
1. OnEraseBkgnd()的要求是快速 在里面的绘图程序最好是不要太耗时间
因为 每当window组件有任何小变动 都会马上呼叫OnEraseBkgnd()
2. OnPaint() 是只有在程序有空闲的时候才会被呼叫
3. OnEraseBkgnd() 是在 OnPaint() 之前呼叫的
所以 OnPaint()被呼叫一次之前 可能会呼叫OnEraseBkgnd()好几次


如果我们是一个在做图形化使用者接口的人
常会需要把一张美美的图片设为我们dialog的底图
把绘图的程序代码放在OnPaint() 之中 可能会常碰到一些问题
比方说拖曳一个窗口在我们做的dialog上面一直移动
则dialog会变成灰色 直到动作停止才恢复
这是因为每次需要重绘的时候 程序都会马上呼叫OnEraseBkgnd()
OnEraseBkgnd()就把dialog画成灰色
而只有动作停止之后 程序才会呼叫OnPaint() 这时才会把我们要画的底图贴上去


这个问题的解法 比较差点的方法是把OnEraseBkgnd() 改写成不做事的function
如下所示
BOOL CMyDlg::OnEraseBkgnd(CDC* pDC)
{
return TRUE;
}
以上本来是会呼叫CDialog::OnEraseBkgnd() 但是如果我们不呼叫的话
程序便不会画上灰色的底色了


比较好的做法是直接将绘图的程序从OnPaint()移到OnEraseBkgnd()来做
如下所示

// m_bmpBKGND 为一CBitmap对象 且事先早已加载我们的底图
// 底图的大小与我们的窗口client大小一致


BOOL CMyDlg::OnEraseBkgnd(CDC* pDC)
{
CRect rc;
GetUpdateRect(&rc);
CDC srcDC;
srcDC.CreateCompatibleDC(pDC);
srcDC.SelectObject(m_bmpBKGND);

pDC->BitBlt(rc.left,rc.top,rc.GetWidth(),
rc.GetHeight(),&srcDC,rc.left,rc.top,SRCCOPY);
return TRUE;
}

特别要注意的是 取得重画大小是使用GetUpdateRect() 而不是GetClientRect()
如果使用GetClientRect() 会把不该重画的地方重画






OnEraseBkGnd,如果你不调用原来缺省的OnEraseBkGnd只是重画背景则不会有闪烁.OnPaint里面,由于它隐含的调用了OnEraseBkGnd,而你又没有处理OnEraseBkGnd函数,这时就和窗口缺省的背景刷相关了.缺省的 OnEraseBkGnd操作使用窗口的缺省背景刷刷新背景(一般情况下是白刷),而随后你又自己重画背景造成屏幕闪动.
OnEraseBkGnd不是每次都会被调用的.如果你调用Invalidate的时候参数为TRUE,那么在OnPaint里面隐含调用BeginPaint的时候就产生WM_ERASEBKGND消息,如果参数是FALSE,则不会重刷背景.

ZYP解释:void Invalidate( BOOL bErase = TRUE ); 该函数的作用是使整个窗口客户区无效。窗口的客户区无效意味着需要重绘,参数bEraseTRUE时,重绘区域内的背景将被重绘即擦除,否则,背景将保持不变。调用Invalidate等函数后窗口不会立即重绘,这是由于WM_PAINT消息的优先级很低,它需要等消息队列中的其它消息发送完后才能被处理。
OnPaint里面会调用BeginPaint函数自动设置显示设备内容的剪切区域而排除任何更新区域外的区域更新区域。如果更新区域被标记为可擦除的,BeginPaint发送一个WM_ERASEBKGND消息给窗口。WM_ERASEBKGND消息的响应函数既是OnEraseBkGnd()。




Q: OnEraseBkgnd函数中返回TRUE或FALSE有什么区别?
A:  
WM_ERASEBKGND
Return Values
An application should return nonzero if it erases the background; otherwise, it should return zero.

A:true表示已处理背景刷新,false表示需要在OnPaint里处理
Q:在OnEraseBkgnd中绘制对话框的背景图片和在OnPaint中绘制对话框的背景图片由什么区别,另外OnEraseBkgnd和CtlColor有什么区别?
A:  
OnEraseBkgnd
是在窗口大小发生改变等情况下发生的,它将绘制窗口背景;而OnCtlColor是当窗口的控件需要绘制时发生的,它将绘制窗口的控件。
  
A:
OnEraseBkgnd :在窗口背景需要重绘时调用.
OnPaint : 此时OnEraseBkgnd已经调用过了,所以在此响应函数体内对背景进行的操作将覆盖OnEraseBkgnd中所做的操作.
OnCtlColor : 有于在窗口将要被(第一次)绘制时响应,子窗口可以通过发关WM_CTLCOLOR请求父窗口传来一个HBRUSH.

usidc52011-10-18 13:52
在很多程序中,都可以看到程序运行中,会有一个Dos窗口,实时显示一些运行信息,这里就告诉大家是如何实现的,我们做个简单的,其实对控制台的操作还有很多,有兴趣的可以去查资料。
    用到的API函数如下:

  //创建控制台
  AllocConsole;
  //获取控制台窗口
  GetStdHandle;
  //向控制台输出信息
  WriteConsole;
  //释放控制台
  FreeConsole;




BOOL CTest_MFCDlg::OnInitDialog()
{
    CDialog::OnInitDialog();    


//略去其他代码


    // 创建控制台
    AllocConsole();


    return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE
}


//按钮点击
void CTest_MFCDlg::OnBnClickedButton1()
{
    //ON_STN_CLICKED(bbb, &CTest_MFCDlg::OnStnClickedaaaaa);
    CEdit* edit = (CEdit*)GetDlgItem(IDC_EDIT1);
    HANDLE h_Consol = GetStdHandle(STD_OUTPUT_HANDLE);
    CString s;
    edit->GetWindowTextW(s);
    DWORD num=0;
    WriteConsole(h_Consol,s,s.GetLength(),&num,NULL);
}


usidc52011-10-25 09:30我们知道在文本框等可以接收输入的组件中,我们可以看到闪烁的光标,并可以输入文字,如果我们在,比如窗体上时,因为不支持输入,也无法显示闪烁的光标,那我们 有办法做自己的输入吗?当然可以,下面我们演示在Form上来输入文字。


    用到的API函数如下





GetTextMetrics:获取程序当前的字体信息,存放到TEXTMETRIC结构中





CreateCaret:为系统插入标记创建一个新的形状,并且将插入标记的属主关系指定给特定的窗口。插入标记的形状。可以是线、块或位图





ShowCaret:显示光标





SetCaretPos:设置光标的位置





Delphi代码


view plain
unit Unit1;  
  
interface  
  
uses  
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,  
  Dialogs, StdCtrls;  
  
type  
  TForm1 = class(TForm)  
    procedure FormCreate(Sender: TObject);  
    procedure FormKeyPress(Sender: TObject; var Key: Char);  
    procedure FormPaint(Sender: TObject);  
  private  
    { Private declarations }  
    s:string;  
  public  
    { Public declarations }  
  end;  
  
var  
  Form1: TForm1;  
  
implementation  
  
{$R *.dfm}  
  
procedure TForm1.FormCreate(Sender: TObject);  
var  
  //TTextMetric存放字体信息  
  tm:TTextMetric;  
begin  
  s := '';  
  GetTextMetrics(Self.Canvas.Handle,tm);  
  {
  注意 CreateCaret 的第二个参数是HBITMAP类型,所以你可以使用自己的图形作为光标形状,这里采用默认
  后面两个参数是光标的宽度和高度,可以自定义
  }  
  CreateCaret(Self.Handle,HBITMAP(nil),tm.tmAveCharWidth div 2,tm.tmHeight);  
  ShowCaret(Self.Handle);  
  //在(10,,10)这个点上显示  
  SetCaretPos(10,10);  
end;  
  
//窗体按键事件,每次按一个键后,重写s的值,在OnPaint事件中会把s的值 画到窗体上  
procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);  
begin  
  //如果是退格键,则删除前一个字符  
  if Ord(Key) = VK_BACK then  
  begin  
    if (s <> '') then  
      Delete(s,Length(s),1);  
  end  
  else  
    s := s + Key;  
  //重绘  
  Self.Invalidate;  
end;  
  
procedure TForm1.FormPaint(Sender: TObject);  
begin  
  Self.Canvas.TextOut(10,10,s);  
  //重新设置光标位置  
  SetCaretPos(Self.Canvas.TextWidth(s)+10,10);  
end;  
  
end.  





VC代码


view plain
//全局字符串变量  
CString s;  
  
//初始化时,设置光标  
BOOL CTest_MFCDlg::OnInitDialog()  
{  
    CDialog::OnInitDialog();      
    ShowSelfCaret();  
         ......  
}  
  
//为窗体添加函数,初始化光标  
void CTest_MFCDlg::ShowSelfCaret(void)  
{  
    CClientDC dc(this);  
    TEXTMETRIC tm;  
    dc.GetTextMetrics(&tm);  
    CreateSolidCaret(tm.tmAveCharWidth/2,tm.tmHeight);  
    ShowCaret();  
    POINT p;  
    p.x = 0;  
    p.y = 0;  
    SetCaretPos(p);  
}  
  
//重载PreTranslateMessage  
BOOL CTest_MFCDlg::PreTranslateMessage(MSG* pMsg)  
{  
    //如果是按键按下  
    if (pMsg->message == WM_KEYDOWN)  
    {  
        //如果是退格键,删除末尾字符  
        if (pMsg->wParam == VK_BACK)  
        {  
            if (s.GetLength() != 0)  
            {  
                s.Delete(s.GetLength() - 1,1);  
            }  
        }  
        else  
            //追加字符  
            s.Insert(s.GetLength(),(TCHAR)pMsg->wParam);  
        Invalidate(true);  
    }  
    return CDialog::PreTranslateMessage(pMsg);  
}  
  
//自画,将s的内容画到窗体上  
void CTest_MFCDlg::OnPaint()  
{  
    CPaintDC dc(this);  
    CRect rect;  
    GetClientRect(&rect);    
    CSize size = dc.GetTextExtent(s);  
    POINT p;  
    p.x = size.cx;  
    p.y = 0;  
    SetCaretPos(p);  
    dc.DrawText(s,s.GetLength(),rect,DT_LEFT);  
}  

usidc52011-10-25 13:01一、消息钩子的概念1、基本概念 Windows 应用程序是基于消息驱动的,任何线程只要注册窗口类都会有一个消息队列用于接收用户输入的消息和系统消息。为了拦截消息, Windows 提出了钩子的概念。钩子(Hook)是Windows消息处理机制中的一个监视点,钩子提供一
一、消息钩子的概念1、基本概念Windows应用程序是基于消息驱动的,任何线程只要注册窗口类都会有一个消息队列用于接收用户输入的消息和系统消息。为了拦截消息,Windows提出了钩子的概念。钩子(Hook)是Windows消息处理机制中的一个监视点,钩子提供一个回调函数。当在某个程序中安装钩子后,它将监视该程序的消息,在指定消息还没到达窗口之前钩子程序先捕获这个消息。这样就有机会对此消息进行过滤,或者对Windows消息实现监控。


2、分类消息钩子分为局部钩子和全局钩子。局部钩子是指仅拦截指定一个进程的指定消息,全局钩子将拦截系统中所有进程的指定消息。


3、实现步骤使用钩子技术拦截消息通常分为如下几个步骤:设置钩子回调函数;(拦截到消息后所调用的函数)


安装钩子;(使用SetWindowsHookEx函数)


卸载钩子。(使用UnhookWindowsHookEx函数)


4、功能利用消息钩子可以实现特效界面、同步消息、监控消息、自启动等功效。


二、病毒对消息钩子技术的利用计算机病毒经常利用消息钩子实现两种功能:1、监控用户按键,盗取用户信息。


这样的病毒会启动一个常驻内存的EXE病毒进程,然后安装一个全局键盘消息钩子,钩子回调函数位于病毒进程中,这样系统中任何有按键操作的进程,其按键详细信息都会被病毒进程拦截记录。


2、自启动这样的病毒会将钩子回调函数放在一个DLL文件中,然后安装一个全局消息(容易触发的消息,如WH_CBT、WH_GETMESSAGE等)钩子,这样凡响应该消息的进程都会自动加载病毒的DLL,病毒也就跟着自动运行了。


三、消息钩子病毒的对抗技术(重点)


1、对抗技术原理对付消息钩子病毒方法很简单,只要将病毒安装的钩子卸载掉即可。(注意:对于系统中许多进程已经因为全局钩子而加载了病毒DLL的情况,并不需要去卸载这些DLL,只要安装的消息钩子被卸载那么对应的DLL也都会被在这些进程中自动卸载。)卸载钩子有两种方法:(1)、结束掉安装钩子的进程将设置钩子的进程结束,进程在退出之前会自行卸载掉该进程安装的所有消息钩子。这种方法很适合对付监控用户按键的病毒。


(2)、获得消息钩子句柄,然后调用UnhookWindowsHookEx函数即可将消息钩子卸载。


如果病毒单独启动了一个病毒进程安装了一个全局消息钩子,然后就常驻内存。这时我们将这个病毒进程结束掉即可。但是如果病毒在系统进程中注入代码而安装的钩子,这样钩子句柄就位于系统进程中,我们不可以结束系统进程,这时就只能获取这个消息钩子句柄,然后调用函数卸载。


2、对抗技术实现细节对于结束掉安装钩子进程从而卸载病毒消息钩子的方法很容易实现,只要找到病毒进程结束即可。而对于获取病毒消息钩子句柄,然后调用函数卸载钩子的方法比较复杂,也是本文重点讨论的内容,将在下一个标题中详细介绍。


四、查找病毒消息钩子句柄然后卸载的方法实现(重点、难点)


1、实现原理分析系统会将所有安装的钩子句柄保存在内核中,要查找病毒安装的消息钩子句柄,我们要枚举所有的消息钩子句柄。如何枚举稍后讲解,还要解决一个问题,就是在枚举过程中,我们怎么知道哪个句柄是病毒安装的呢?


通过分析病毒样本我们通常可以得到病毒安装钩子就是为了令其他合法进程加载病毒DLL,所以它会将钩子回调函数写在该DLL中。在枚举消息钩子句柄时,同时也可以得到该句柄所对应的回调函数所属的DLL模块,根据这个DLL模块是不是病毒的DLL模块即可找到病毒的消息钩子句柄,最后将其卸载即可。


关于如何枚举系统消息钩子句柄,对于不同的操作系统方法大不相同,这里介绍一种用户层读内存的方法,此方法仅在2000/XP系统下可用。


在2000/XP系统下有一个Windows用户界面相关的应用程序接口User32.dll.它用于包括Windows窗口处理,基本用户界面等特性,如创建窗口和发送消息。当它被加载到内存后,它保存了所有Windows窗口、消息相关的句柄,其中就包括消息钩子句柄。这些句柄被保存在一块共享内存段中,通常称为R3层的GUI TABLE.所以只要我们找到GUI TABLE,然后在其中的句柄中筛选出消息钩子句柄。GUI TABLE这块内存段可以被所有进程空间访问。GUI TABLE被定义成如下结构:typedef struct tagSHAREDINFO { struct tagSERVERINFO *pServerInfo;  //指向tagSERVERINFO结构的指针struct _HANDLEENTRY *pHandleEntry;  // 指向句柄表struct tagDISPLAYINFO *pDispInfo;  //指向tagDISPLAYINFO结构的指针ULONG ulSharedDelta;LPWSTR pszDllList;} SHAREDINFO, *PSHAREDINFO;tagSHAREDINFO结构体的第一个成员pServerInfo所指向的tagSERVERINFO结构体定义如下。


typedef struct tagSERVERINFO { short wRIPFlags ;short wSRVIFlags ;short wRIPPID ;short wRIPError ;ULONG cHandleEntries;          //句柄表中句柄的个数}SERVERINFO,*PSERVERINFO;可以看出通过tagSERVERINFO结构的cHandleEntries成员即可得到tagSHAREDINFO结构的pHandleEntry成员所指向的句柄表中的句柄数。


tagSHAREDINFO结构体的第二个成员pHandleEntry是指向_HANDLEENTRY结构体数组起始地址的指针,该数组的一个成员对应一个句柄。句柄结构体_HANDLEENTRY定义如下。


typedef struct _HANDLEENTRY{ PVOID  pObject;            //指向句柄所对应的内核对象ULONG  pOwner;BYTE  bType;               //句柄的类型BYTE  bFlags;short  wUniq;}HANDLEENTRY,*PHANDLEENTRY;_HANDLEENTRY结构体成员bType是句柄的类型,通过该变量的判断可以筛选消息钩子句柄。User32中保存的句柄类型通常有如下种类。


typedef enum  _HANDLE_TYPE { TYPE_FREE = 0,TYPE_WINDOW = 1 ,TYPE_MENU = 2,                     //菜单句柄TYPE_CURSOR = 3,                   //光标句柄TYPE_SETWINDOWPOS = 4,TYPE_HOOK = 5,                     //消息钩子句柄TYPE_CLIPDATA = 6  ,TYPE_CALLPROC = 7,TYPE_ACCELTABLE = 8,TYPE_DDEACCESS = 9,TYPE_DDECONV = 10,TYPE_DDEXACT = 11,TYPE_MONITOR = 12,TYPE_KBDLAYOUT = 13   ,TYPE_KBDFILE = 14    ,TYPE_WINEVENTHOOK = 15  ,TYPE_TIMER = 16,TYPE_INPUTCONTEXT = 17  ,TYPE_CTYPES = 18         ,TYPE_GENERIC = 255 }HANDLE_TYPE;_HANDLEENTRY结构体的成员pObject是指向句柄对应的内核对象的指针。


这样只要通过pObject就可以得到句柄的详细信息(其中包括创建进程,线程、回调函数等信息),通过bType就可以的值句柄的类型。


_HANDLEENTRY结构体的其他成员可以忽略不看。


(知识要点补充:如何在用户层程序中读取内核内存)


需要注意的是,pObject指针指向的是内核内存,不可以在用户层直接访问内核内存。后面还有些地方也同样是内核内存,需要加以注意。应该把内核内存的数据读取到用户层内存才可以访问。且不可以直接访问,毕竟不是在驱动中。


在用户层读取内核内存使用ZwSystemDebugControl函数,它是一个Native API.其原型如下。


NTSYSAPI NTSTATUS NTAPI ZwSystemDebugControl(IN DEBUG_CONTROL_CODE ControlCode,//控制代码IN PVOID InputBuffer OPTIONAL,    //输入内存IN ULONG InputBufferLength,    //输入内存长度OUT PVOID OutputBuffer OPTIONAL,  //输出内存IN ULONG OutputBufferLength,    //输出内存长度OUT PULONG ReturnLength OPTIONAL  //实际输出的长度);ZwSystemDebugControl函数可以用于读/写内核空间、读/写MSR、读/写物理内存、读/写IO端口、读/写总线数据、KdVersionBlock等。由第一个参数ControlCode控制其功能,可以取如下枚举值。


typedef enum _SYSDBG_COMMAND { //以下5个在Windows NT各个版本上都有SysDbgGetTraceInformation = 1,SysDbgSetInternalBreakpoint = 2,SysDbgSetSpecialCall = 3,SysDbgClearSpecialCalls = 4,SysDbgQuerySpecialCalls = 5,// 以下是NT 5.1 新增的SysDbgDbgBreakPointWithStatus = 6,//获取KdVersionBlock SysDbgSysGetVersion = 7,//从内核空间复制到用户空间,或者从用户空间复制到用户空间//但是不能从用户空间复制到内核空间SysDbgCopyMemoryChunks_0 = 8,//SysDbgReadVirtualMemory = 8,//从用户空间复制到内核空间,或者从用户空间复制到用户空间//但是不能从内核空间复制到用户空间SysDbgCopyMemoryChunks_1 = 9,//SysDbgWriteVirtualMemory = 9,//从物理地址复制到用户空间,不能写到内核空间SysDbgCopyMemoryChunks_2 = 10,//SysDbgReadVirtualMemory = 10,//从用户空间复制到物理地址,不能读取内核空间SysDbgCopyMemoryChunks_3 = 11,//SysDbgWriteVirtualMemory = 11,//读/写处理器相关控制块SysDbgSysReadControlSpace = 12,SysDbgSysWriteControlSpace = 13,//读/写端口SysDbgSysReadIoSpace = 14,SysDbgSysWriteIoSpace = 15,//分别调用RDMSR@4和_WRMSR@12 SysDbgSysReadMsr = 16,SysDbgSysWriteMsr = 17,//读/写总线数据SysDbgSysReadBusData = 18,SysDbgSysWriteBusData = 19,SysDbgSysCheckLowMemory = 20,// 以下是NT 5.2 新增的//分别调用_KdEnableDebugger@0和_KdDisableDebugger@0 SysDbgEnableDebugger = 21,SysDbgDisableDebugger = 22,//获取和设置一些调试相关的变量SysDbgGetAutoEnableOnEvent = 23,SysDbgSetAutoEnableOnEvent = 24,SysDbgGetPitchDebugger = 25,SysDbgSetDbgPrintBufferSize = 26,SysDbgGetIgnoreUmExceptions = 27,SysDbgSetIgnoreUmExceptions = 28 } SYSDBG_COMMAND, *PSYSDBG_COMMAND;我们这里要读取内核内存,所以参数ControlCode应取值为SysDbgReadVirtualMemory.当ControlCode取值为SysDbgReadVirtualMemory时,ZwSystemDebugControl函数的第4个参数和第5个参数被忽略,使用时传入0即可。第二个参数InputBuffer是一个指向结构体_MEMORY_CHUNKS的指针,该结构体定义如下。


typedef struct _MEMORY_CHUNKS { ULONG Address;      //内核内存地址指针(要读的数据)


PVOID Data;         //用户层内存地址指针(存放读出的数据)


ULONG Length;      //读取的长度}MEMORY_CHUNKS, *PMEMORY_CHUNKS;第三个参数InputBufferLength是_MEMORY_CHUNKS结构体的大小。使用sizeof运算符得到即可。


SysDbgReadVirtualMemory函数执行成功将返回0.否则返回错误代码。


为了方便使用,我们可以封装一个读取内核内存的函数GetKernelMemory,实现如下:#define SysDbgReadVirtualMemory 8 //定义ZwSystemDebugControl函数指针类型typedef DWORD (WINAPI *ZWSYSTEMDEBUGCONTROL)(DWORD,PVOID,DWORD,PVOID,DWORD,PVOID);BOOL GetKernelMemory(PVOID pKernelAddr, PBYTE pBuffer, ULONG uLength)


{ MEMORY_CHUNKS mc ;ULONG uReaded = 0;mc.Address=(ULONG)pKernelAddr;  //内核内存地址mc.pData = pBuffer;//用户层内存地址mc.Length = uLength;       //读取内存的长度ULONG st  = -1 ;//获得ZwSystemDebugControl函数地址ZWSYSTEMDEBUGCONTROL ZwSystemDebugControl = (ZWSYSTEMDEBUGCONTROL) GetProcAddress(GetModuleHandle("ntdll.dll"), "ZwSystemDebugControl");//读取内核内存数据到用户层st = ZwSystemDebugControl(SysDbgReadVirtualMemory, &mc, sizeof(mc), 0, 0, &uReaded);return st == 0;}


对于不同类型的句柄,其内核对象所属内存对应的结构体不同,对于消息钩子句柄,它的内核对象所属内存对应的结构体实际上是_HOOK_INFO类型,其定义如下。


typedef struct _HOOK_INFO { HANDLE hHandle; //钩子的句柄DWORD Unknown1;PVOID Win32Thread; //一个指向 win32k!_W32THREAD 结构体的指针PVOID Unknown2;PVOID SelfHook; //指向结构体的首地址PVOID NextHook; //指向下一个钩子结构体int iHookType; //钩子的类型。


DWORD OffPfn; //钩子函数的地址偏移,相对于所在模块的偏移int iHookFlags; //钩子标志int iMod; //钩子函数做在模块的索引号码,利用它可以得到模块基址PVOID Win32ThreadHooked; //被钩的线程结构指针} HOOK_INFO,*PHOOK_INFO;由上可以看出,得到钩子内核对象数据后,该数据对应HOOK_INFO结构体信息。其中:hHandle是钩子句柄,使用它就可以卸载钩子。


iHookType是钩子的类型,消息钩子类型定义如下。


typedef enum  _HOOK_TYPE{ MY_WH_MSGFILTER = -1,MY_WH_JOURNALRECORD = 0,MY_WH_JOURNALPLAYBACK = 1,MY_WH_KEYBOARD = 2,MY_WH_GETMESSAGE = 3,MY_WH_CALLWNDPROC = 4,MY_WH_CBT = 5,MY_WH_SYSMSGFILTER = 6,MY_WH_MOUSE = 7,MY_WH_HARDWARE = 8,MY_WH_DEBUG = 9,MY_WH_SHELL = 10,MY_WH_FOREGROUNDIDLE = 11,MY_WH_CALLWNDPROCRET = 12,MY_WH_KEYBOARD_LL = 13,MY_WH_MOUSE_LL = 14 }HOOK_TYPE;OffPfn是钩子回调函数的偏移地址,该偏移地址是相对于钩子函数所在模块基址的偏移。


Win32Thread是指向_W32THREAD结构体的指针,通过这个结构体可以获得钩子所在进程ID和线程ID.该结构体定义如下。


typedef struct _W32THREAD { PVOID    pEThread ;    //该指针用以获得进程ID和线程ID ULONG   RefCount ;ULONG  ptlW32 ;ULONG  pgdiDcattr ;ULONG   pgdiBrushAttr ;ULONG   pUMPDObjs ;ULONG    pUMPDHeap ;ULONG    dwEngAcquireCount ;ULONG    pSemTable ;ULONG    pUMPDObj ;PVOID ptl;PVOID ppi;            //该指针用以获得模块基址}W32THREAD, *PW32THREAD;_W32THREAD结构体第一个参数pEThread指向的内存偏移0x01EC处分别保存着进程ID和线程ID.注意pEThread指针指向的内存是内核内存。


_W32THREAD结构体最后一个参数ppi指向的内存偏移0xA8处是所有模块基址的地址表,   _HOOK_INFO结构体的iMod成员就标识了本钩子所属模块基址在此地址表中的位置。(每个地址占4个字节)所以通常使用ppi+0xa8+iMod*4定位模块基址的地址。注意ppi指向的内存是内核内存。


2、实现细节首先编写程序枚举消息钩子句柄,需要得到GUI TABLE,它的地址实际上存储于User32.dll的一个全局变量中,该模块导出的函数UserRegisterWowHandlers将返回该全局变量的值。所以我们只要调用这个函数就能够得到GUI TABLE.然而UserRegisterWowHandlers是一个未公开的函数,不确定它的函数原型,需要反汇编猜出它的原型。笔者反汇编后得到的原型如下。


typedef PSHAREDINFO (__stdcall *USERREGISTERWOWHANDLERS) (PBYTE ,PBYTE );仅知道它两个参数是两个指针,但是不知道它的两个参数的含义,所以我们无法构造出合理的参数。如果随便构造参数传进去又会导致user32.dll模块发生错误。所以通过调用这个函数接收其返回值的方法就不能用了。再次反汇编该函数的实现可以看出,在不同操作系统下该函数的最后三行代码如下。


2K系统:(5.0.2195.7032)


:77E3565D B880D2E477 mov eax, 77E4D280:77E35662 C20800 ret 0008 XP系统:(5.1.2600.2180)


:77D535F5 B88000D777 mov eax, 77D70080:77D535FA 5D pop ebp:77D535FB C20800 ret 0008 2003系统:(5.2.3790.1830)


:77E514D9 B8C024E777 mov eax, 77E724C0:77E514DE C9 leave:77E514DF C2080000 ret 0008可以看到共同点,该函数的倒数第三行代码就是将保存GUI TABLE指针的全局变量值赋值给寄存器EAX,只要我们想办法搜索到这个值即可。能够看出无论是哪个版本的函数实现中,都有 C20800代码,含义是ret 0008.我们可以自UserRegisterWowHandlers函数的入口地址开始一直搜索到C20800,找到它以后再向前搜索B8指令,搜到以后B8指令后面的四个字节数据就是我们需要的数据。代码如下。


//获得UserRegisterWowHandlers函数的入口地址DWORD UserRegisterWowHandlers = (DWORD) GetProcAddress(LoadLibrary("user32.dll"), "UserRegisterWowHandlers");PSHAREDINFO pGUITable;  //保存GUITable地址的指针for(DWORD i=UserRegisterWowHandlers; i<UserRegisterWowHandlers+1000; i++)


{ if((*(USHORT*)i==0x08c2)&&*(BYTE *)(i+2)== 0x00)


{     //已找到ret 0008指令,然后往回搜索B8 for (int j=i; j>UserRegisterWowHandlers; j——)


{   //找到B8它后面四个字节保存的数值即为GUITable地址if (*(BYTE *)j == 0xB8)


{ pGUITable = (PSHAREDINFO)*(DWORD *)(j+1);break;} }break;}得到SHAREDINFO结构指针后,它的成员pServerInfo的成员cHandleEntries就是句柄的总个数,然后循环遍历每一个句柄,找到属于指定模块的消息钩子句柄。代码如下。


int iHandleCount = pGUITable->pServerInfo->cHandleEntries;HOOK_INFO HookInfo;DWORD dwModuleBase;struct TINFO { DWORD dwProcessID;DWORD dwThreadID;};char cModuleName[256] = {0};for (i=0; i<iHandleCount; i++)


{              //判断句柄类型是否为消息钩子句柄if (pGUITable->pHandleEntry.bType == TYPE_HOOK)


{ DWORD dwValue = (DWORD)pGUITable->pHandleEntry.pObject;//获得消息钩子内核对象数据GetKernelMemory(pGUITable->pHandleEntry.pObject, (BYTE *)&HookInfo, sizeof(HookInfo));W32THREAD w32thd;if( GetKernelMemory(HookInfo.pWin32Thread,(BYTE *)&w32thd , sizeof(w32thd)) )


{  //获取钩子函数所在模块的基址if (!GetKernelMemory((PVOID)((ULONG)w32thd.ppi+0xA8+4*HookInfo.iMod),(BYTE *)&dwModuleBase,   sizeof(dwModuleBase)))


{ continue;} TINFO tInfo;//获取钩子所属进程ID和线程ID if (!GetKernelMemory((PVOID)((ULONG)w32thd.pEThread+0x1ec),(BYTE *)&tInfo,   sizeof(tInfo)))


{ continue;} HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, tInfo.dwProcessID);if (hProcess == INVALID_HANDLE_VALUE)


{ continue;} //根据模块基址,获取钩子函数所属模块的名称if (GetModuleFileNameEx(hProcess, (HMODULE)dwModuleBase, cModuleName, 256))


{ OutputDebugString(cModuleName);OutputDebugString("\r\n");}


利用上面的代码就可以找到所属病毒DLL的消息钩子句柄,然后调用UnhookWindowsHookEx函数卸载这个消息钩子就OK了。

usidc52011-10-27 15:43网上怎么到处泛滥这种错误?!!!
用API 函数GetVolumeInformation得到的不是硬盘的序列号!!!

BOOL GetVolumeInformation(
  LPCTSTR lpRootPathName,
  LPTSTR lpVolumeNameBuffer,
  DWORD nVolumeNameSize,
  LPDWORD lpVolumeSerialNumber,
  LPDWORD lpMaximumComponentLength,
  LPDWORD lpFileSystemFlags,
  LPTSTR lpFileSystemNameBuffer,
  DWORD nFileSystemNameSize
);

看看英文啊:VolumeSerialNumber!得到的只是卷区序列号!
硬盘的序列号应该是
Drive Model Number________________: WDC WD400EB-00CPF0
Drive Serial Number_______________: WD-WCAATF083586
Drive Controller Revision Number__: 06.04G06
Controller Buffer Size on Drive___: 2097152 bytes
Drive Type________________________: Fixed
Drive Size________________________: 40020664320 bytes
中:

Drive Serial Number_______________: WD-WCAATF083586 这才是硬盘Serial Number!!!!

这个号是不会因为你格式化硬盘而改动,也不是网上流传的修改工具能改的,(其实网上流传的修改工具的也不过是卷区号而已,真是哭笑不得!)

usidc52011-10-27 15:44//声明:
GetVolumeInformation(
  lpRootPathName: PChar;               {磁盘驱动器代码字符串}
  lpVolumeNameBuffer: PChar;           {磁盘驱动器卷标名称}
  nVolumeNameSize: DWORD;              {磁盘驱动器卷标名称长度}
  lpVolumeSerialNumber: PDWORD;        {磁盘驱动器卷标序列号}
  var lpMaximumComponentLength: DWORD; {系统允许的最大文件名长度}
  var lpFileSystemFlags: DWORD;        {文件系统标识}
  lpFileSystemNameBuffer: PChar;       {文件操作系统名称}
  nFileSystemNameSize: DWORD           {文件操作系统名称长度}
): BOOL;




//举例:
procedure TForm1.FormCreate(Sender: TObject);
var
  RootPath: array[0..20] of Char;
  VolName: array[0..255] of Char;
  SerialNumber: DWORD;
  MaxCLength: DWORD;
  FileSysFlag: DWORD;
  FileSysName: array[0..255] of Char;
begin
  RootPath := 'C:\';


  GetVolumeInformation(
    RootPath,
    VolName,
    255,
    @SerialNumber,
    MaxCLength,
    FileSysFlag,
    FileSysName,
    255
  );


  Memo1.Clear;
  with Memo1.Lines do
  begin
    Add(Format('磁盘驱动器代码字符串:'+ #9#9 +'%s',[RootPath]));
    Add(Format('磁盘驱动器卷标名称:'+ #9#9 +'%s',[VolName]));
    Add(Format('磁盘驱动器卷标序列号:'+ #9#9 +'%s',[IntToHex(SerialNumber,8)]));
    Add(Format('系统允许的最大文件名长度:'+ #9 +'%d',[MaxCLength]));
    Add(Format('文件系统标识:'+ #9#9#9 +'%d',[FileSysFlag]));
    Add(Format('文件系统名称:'+ #9#9#9 +'%s',[FileSysName]));
  end;
end;




//效果图:                




usidc52011-10-31 21:27大家知道,Ctrl+Alt+Del是Win2k/NT操作系统默认的系统登录/注销组合键序列,系统级别很高。在应用程序中,想要屏蔽掉该键序列的响应或得到这个 "按下 "事件,难度是相当大的。本文介绍了一种简单易行的方法,实现在用户登录成功后,按下Ctrl+Alt+Del不再弹出 "Windows安全 "对话框。  

关键词:GINA(Graphical   Identification   aNd   Authentication)   SAS(Secure   Attention   Sequence)  


一.   开发原理  


首先介绍一下Winlogon。Windows   2000/NT有三种系统状态:没有用户登录状态、用户成功登录状态以及工作站锁定状态。Winlogon是Windows   2000/NT操作系统提供交互式登录支持的组件。Winlogon有三个组成部分:可执行文件winlogon.exe,提供图形界面认证功能的动态库Gina   Dll,以及一些网络服务提供动态库Network   Provider   Dll。参考模型如下:


winlogon.exe处理一些下层导出的接口函数,而认证策略是在Gina   Dll中是独立设计的。在系统启动时,Gina   Dll被winlogon.exe装载。Microsoft提供了一个默认的Gina   Dll--Winnt\system32\msgina.dll,提供了标准的用户名、密码认证模式。Gina   Dll是可替换的,用户可以设计自己的Gina   Dll,以提供其他如智能卡、视网膜、指纹或其他一些认证机制。  


开发自定义的Gina   Dll。必须实现并导出与winlogon.exe交互的18个标准函数接口,包括WlxNegotiate、WlxInitialize、WlxLoggedOnSAS等(其他函数接口请参考Msdn)。其中WlxNegotiate是winlogon.exe调用的第一个接口函数,进行必要的版本判断,随后调用的是WlxInitialize,主要完成winlogon.exe特定版本的函数分派表向Gina   Dll的传递。笔者还要说明的是WlxLoggedOnSAS函数,这个函数主要的功能是,当winlogon在登录成功状态下,接收到SAS事件,于是调用这个函数进行SAS事件的识别以及进行各事件的相应处理。  


自定义Gina   Dll的使用。比如开发的Gina   Dll文件名为MyGina.dll。将该文件放到以下路径:Winnt\system32。并修改注册表,如下:  


Key   Name:   \HKEY_LOCAL_MACHINE\Software\Microsoft\Windows   NT\CurrentVersion\   Winlogon  


Value   Name:   GinaDLL  


Value   Type:   [REG_SZ]  


Value:   MyGina.dll  


重新启动计算机Gina.dll即投入使用。  


二.   应用实例  


应用要求:在用户登录成功状态下,按下Ctrl+Alt+Del时系统不再弹出 "Widows安全 "对话框。由于并不需要改变用户名、密码这种标准的认证模式,所以可以仍然使用msgina.dll中导出的函数接口,而对WlxLoggedOnSAS函数的实现进行必要的改变。  


开发环境:Windows   2000,PII   400  


开发工具:Microsoft   Visual   C++   6.0  


开发步骤:  


1.新建项目,选择MFC   AppWizard(dll),项目名输入为MyGina。按下 "OK "后,选择Regular   DLL   with   MFC   statically   linked,按下 "Finish "。  


2.使用View-> ClassWizard为CmyGinaApp增加InitInstance和ExitInstance两个函数的覆盖。  


3.由于要导入msgina.dll的接口函数,所以在MyGina.h中定义接口函数变量类型,如下:  


typedef   (WINAPI   *   NEGOTIATE)   (DWORD,PDWORD);

typedef   (WINAPI   *   INITIALIZE)   (LPWSTR,HANDLE,PVOID,PVOID,PVOID   *);

typedef   (WINAPI   *   ACTIVATE_USHELL)   (PVOID,PWSTR,PWSTR,PVOID);

typedef   (WINAPI   *   PARAM_PVOID)   (PVOID);

typedef   (WINAPI   *   DISP_STATUS)   (PVOID,HDESK,DWORD,PWSTR,PWSTR);

typedef   (WINAPI   *   GET_STATUS)   (PVOID,DWORD   *,PWSTR,DWORD);

typedef   (WINAPI   *   LOGON_SAS)   (PVOID,DWORD,PVOID);

typedef   (WINAPI   *   LOGOUT_SAS)   (PVOID,DWORD,PLUID,PSID,PDWORD,   PHANDLE,WLX_MPR_NOTIFY_INFO,PVOID   *);

typedef   (WINAPI   *   NETWORK_LOAD)   (PVOID,PWLX_MPR_NOTIFY_INFO);

typedef   (WINAPI   *   SCR_SAVER)   (PVOID,BOOL   *);

typedef   (WINAPI   *   SHUT_DOWN)   (PVOID,DWORD);

typedef   (WINAPI   *   START_APP)   (PVOID,PWSTR,PVOID,PWSTR);

typedef   (WINAPI   *   LOCKED_SAS)   (PVOID,DWORD);

并在类CmyGinaApp中定义成员变量,如下:

private:

HMODULE   hMsDll;

public:

NEGOTIATE   MyWlxNegotiate;

INITIALIZE   MyWlxInitialize;

ACTIVATE_USHELL   MyWlxActivateUserShell;

PARAM_PVOID   MyWlxDisplayLockedNotice;

PARAM_PVOID   MyWlxDisplaySASNotice;

DISP_STATUS   MyWlxDisplayStatusMessage;

GET_STATUS   MyWlxGetStatusMessage;

PARAM_PVOID   MyWlxIsLockOk;

PARAM_PVOID   MyWlxIsLogoffOk;

LOGON_SAS   MyWlxLoggedOnSAS;

LOGOUT_SAS   MyWlxLoggedOutSAS;

PARAM_PVOID   MyWlxLogoff;

NETWORK_LOAD   MyWlxNetworkProviderLoad;

PARAM_PVOID   MyWlxRemoveStatusMessage;

SCR_SAVER   MyWlxScreenSaverNotify;

SHUT_DOWN   MyWlxShutdown;

START_APP   MyWlxStartApplication;

LOCKED_SAS   MyWlxWkstaLockedSAS;


注意在MyGina.h中说明extern   CMyGinaApp   theApp;以便于程序其他地方对theApp的引用。  


4.在MyGina.cpp中,实现InitInstance如下:  


//   得到默认的gina   dll

if   (hMsDll   ==   NULL)

{

hMsDll   =   ::LoadLibrary( "msgina.dll ");

}

//   导入各个接口函数

if   (hMsDll   !=   NULL)

{

MyWlxNegotiate   =   (NEGOTIATE)   GetProcAddress(hMsDll, "WlxNegotiate ");

MyWlxInitialize   =   (INITIALIZE)   GetProcAddress(hMsDll, "WlxInitialize ");

MyWlxActivateUserShell=(ACTIVATE_USHELL)   GetProcAddress(hMsDll, "WlxActivateUserShell ");

MyWlxDisplayLockedNotice=(PARAM_PVOID)   GetProcAddress(hMsDll, "WlxDisplayLockedNotice ");

MyWlxDisplaySASNotice   =   (PARAM_PVOID)   GetProcAddress(hMsDll, "WlxDisplaySASNotice ");

MyWlxDisplayStatusMessage=(DISP_STATUS)   GetProcAddress(hMsDll, "WlxDisplayStatusMessage ");

MyWlxGetStatusMessage   =   (GET_STATUS)   GetProcAddress(hMsDll, "WlxGetStatusMessage ");

MyWlxIsLockOk   =   (PARAM_PVOID)   GetProcAddress(hMsDll, "WlxIsLockOk ");

MyWlxIsLogoffOk   =   (PARAM_PVOID)   GetProcAddress(hMsDll, "WlxIsLogoffOk ");

MyWlxLoggedOnSAS   =   (LOGON_SAS)   GetProcAddress(hMsDll, "WlxLoggedOnSAS ");

MyWlxLoggedOutSAS   =   (LOGOUT_SAS)   GetProcAddress(hMsDll, "WlxLoggedOutSAS ");

MyWlxLogoff   =   (PARAM_PVOID)   GetProcAddress(hMsDll, "WlxLogoff ");

MyWlxNetworkProviderLoad=(NETWORK_LOAD)GetProcAddress(hMsDll, "WlxNetworkProviderLoad ");

MyWlxRemoveStatusMessage=(PARAM_PVOID)   GetProcAddress(hMsDll, "WlxRemoveStatusMessage ");

MyWlxScreenSaverNotify   =   (SCR_SAVER)   GetProcAddress(hMsDll, "WlxScreenSaverNotify ");

MyWlxShutdown   =   (SHUT_DOWN)   GetProcAddress(hMsDll, "WlxShutdown ");

MyWlxStartApplication   =   (START_APP)   GetProcAddress(hMsDll, "WlxStartApplication ");

MyWlxWkstaLockedSAS   =   (LOCKED_SAS)   GetProcAddress(hMsDll, "WlxWkstaLockedSAS ");

}

实现ExitInstance如下:

//   卸载dll

if   (hMsDll   !=   NULL)

{

::FreeLibrary(hMsDll);

hMsDll   =   NULL;

}


5.实现接口函数。由于本应用仍然保持msgina.dll的大部分操作,所以MyGina.dll的接口函数的实现较为简单。重点需要注意的是WlxLoggedOnSAS函数的实现。当在成功登录状态下,不管接收到什么SAS事件,该函数直接返回WLX_SAS_ACTION_NONE而不做其他处理。由于实现的函数较多(必须的18个),笔者仅列出代表性的五个,其余的依理类推。  


//   Winlogon.exe调用的gina   dll中的第一个函数

//   使gina   dll确认是否支持当前版本的Winlogon.exe

//   传递给winlogon.exe需要那个版本的接口函数

BOOL   WINAPI   WlxNegotiate(DWORD   dwWinLogonVersion,   PDWORD   pdwDllVersion)

{

//   直接调用从msgina.dll中导入的函数

return   theApp.MyWlxNegotiate(dwWinLogonVersion,pdwDllVersion);

}


//   初始化,winlogon.exe向gina   dll传递需要版本的接口函数分配表

BOOL   WINAPI   WlxInitialize(LPWSTR   lpWinsta,

HANDLE   hWlx,

PVOID   pvReserved,

PVOID   pWinlogonFunctions,

PVOID   *   pWlxContext

)

{

//   直接调用从msgina.dll中导入的函数

return   theApp.MyWlxInitialize(lpWinsta,hWlx,pvReserved,pWinlogonFunctions,pWlxContext);

}

//   当系统处于锁定状态时,Winlogon.exe调用该函数

//   显示一些信息,如锁定者、锁定时间等

VOID   WINAPI   WlxDisplayLockedNotice(PVOID   pWlxContext)

{

theApp.MyWlxDisplayLockedNotice(pWlxContext);

}

//   在系统关闭之前,Winlogon.exe调用该函数

//   允许gina   dll处理一些系统关闭前的处理

VOID   WINAPI   WlxShutdown(PVOID   pWlxContext,   DWORD   ShutdownType)

{

theApp.MyWlxShutdown(pWlxContext,ShutdownType);

}

//   当系统处于登陆成功,没有锁定的状态下

//   Winlogon接收到SAS事件,于是调用该函数

//   现屏蔽所有事件,直接返回

int   WINAPI   WlxLoggedOnSAS(PVOID   pWlxContext,

DWORD   dwSasType,

PVOID   pReserved)

{

return   WLX_SAS_ACTION_NONE;

}


6.将MyGina.dll中实现的所有接口函数,在MyGina.def中定义导出。


源程序在:

http://www.vccode.com/file/file/20020812214426_MyGina.zip

usidc52011-10-31 21:34
使用::GetKeyState()返回一个short型的数,short型是16位有符号的数据类型,如果要查询的键被按下,返回值最高位被置1,则这个数表示负数,所以可以用<0或>0来判断。  
   0x8000是16进制数,用2进制表示为1000    0000    0000    0000,    &是按位与  
   同样,如果键被按下,返回值最高位为1,则1xxx    xxxx    xxxx    xxxx    &    1000    0000    0000    0000得到的结果为1,否则为0,同样可以判断最高位的值。  
   需要说明的是,::GetKeyState()只能在键盘消息处理程序中使用,因为它只有在线程从消息队列中读取键盘消息时才会报告被查询键的状态,如果需要在键盘消息处理程序以外查询按键状态,则需要使用::GetAsyncKeyState()来代替.

GetAsyncKeyState查询指定键的实时状态
    
使用后发现两者的区别(以组合键CTRL+A为例):
      if (nChar ==0x041)&& (GetKeyState(VK_CONTROL)&0x8000)   //ctrl+A
     {
                //执行相应的操作
      }
      的效果是按下CTRL和A,实现指定的操作;(只有先按下CTRL然后按A或者同时按下二者,才能执行指定的操作)
     而如果用
     if (nChar ==0x041)&& GetAsyncKeyState(VK_CONTROL))   //ctrl+A
     {
                //执行相应的操作
      }
    得到的效果将是:按下CTRL,然后按下A,可以执行指定的操作,但是与上述的区别在于:
    按下CTRL后隔了一段时间(可能是几秒或更长时间),然后再回过来按A键也会执行指定的操作(这样不是期望的效果)。

usidc52011-12-18 21:36内存的读取速度显然较硬盘要快的多,当做程序时遇到大规模数据的频繁存取的时候,开辟内存控件就更显得重要了!一般来说,我们所用的内存有栈和堆之分,其它的我们很少控制,栈的速度快,但是控件小,不灵活;而堆的控件几乎可以满座任何要求,灵活,但是相对的速度要慢了很多,并且在vc中堆时人为控制的,new了就要delete,否则很容易产生内存泄露等问题。
将程序栈空间定义得大一点,VC++默认的栈空间是1M,有两个方法更改
a. link时用/STACK指定它的大小,或者在.def中使用STACKSIZE指定它的大小
b. 使用控制台命令“EDITBIN”更改exe的栈空间大小。
例如:打开工程,依次操作菜单如下:Project->Setting->Link,在Category 中选中Output,然后在Reserve中设定堆栈的最大值和commit。
注意:reserve最小值为4Byte;commit是保留在虚拟内存的页文件%

方法一:STACKSIZE   定义.def文件
     语法:STACKSIZE reserve[,commit]
     reserve:栈的大小;commit:可选项,与操作系统有关,在NT上只一次分配物理内存的大小
方法二:设定/STACK
     打开工程,依次操作菜单如下:Project->Setting->Link,在Category 中选中Output,然后
在Reserve中设定堆栈的最大值和commit。
注意:reserve默认值为1MB,最小值为4Byte;commit是保留在虚拟内存的页文件里面,它设置的较
大会使栈开辟较大的值,可能增加内存的开销和启动时间

usidc52012-02-05 20:00今天遇到奇怪的现象,日文系统下在控件上按下Ctrl+C,再粘贴出来得到乱码,非得切换到日文输入法再进行粘贴不可.经过代码分析,得出结论是剪切板SetClipboardData()时自动内存转换惹的祸.这家伙默认状态下并不能识别双字节的字符.
所以修改的思路是,先统统转成宽字符,再按照宽字符进行拷贝.


之前的代码:


BOOL    CopyStringToClipBoard( HWND hOwner, CString strSource )
{
    if( ::OpenClipboard(hOwner) )
    {
        HANDLE clipbuffer ;
        char *buffer ;
        ::EmptyClipboard() ;
        clipbuffer = ::GlobalAlloc( GMEM_DDESHARE,strSource.GetLength()+1 ) ;
        buffer = (char*)::GlobalLock(clipbuffer) ;
        strcpy( buffer, LPCSTR(strSource) ) ;
        ::GlobalUnlock(clipbuffer) ;
        ::SetClipboardData(CF_TEXT, clipbuffer) ;
        ::CloseClipboard() ;
    }
    return FALSE ;
}
修改之后的代码:


BOOL    CopyStringToClipBoard( HWND hOwner, CString strSource )
{
    if( ::OpenClipboard(hOwner) )
    {
        int buff_size = strSource.GetLength() ;
        CStringW strWide = CStringW(strSource);
        int nLen = strWide.GetLength();
        HANDLE clipbuffer ;
        char* buffer;
        ::EmptyClipboard() ;
        clipbuffer = ::GlobalAlloc( GMEM_DDESHARE, (nLen + 1) * 2 ) ;
        buffer = (char*)::GlobalLock(clipbuffer) ;
        memset(buffer, 0, (nLen + 1) * 2);
        memcpy_s(buffer, nLen * 2, strWide.GetBuffer(0), nLen* 2 );
        strWide.ReleaseBuffer();
        ::GlobalUnlock(clipbuffer) ;
        ::SetClipboardData(CF_UNICODETEXT, clipbuffer) ;
        ::CloseClipboard() ;
    }
    return FALSE ;
}

usidc52012-02-18 12:12.APS:存放二进制资源的中间文件,VC把当前资源文件转换成二进制格式,并存放在APS文件中,以加快资源装载速度。资源辅助文件。


.BMP:位图资源文件。
.BSC:浏览信息文件,由浏览信息维护工具(BSCMAKE)从原始浏览信息文件(.SBR)中生成,BSC文件可以用来在源代码编辑窗口中进行快速定位。用于浏览项目信息的,如果用source brower的话就必须有这个文件。可以在project options里去掉Generate Browse Info File,这样可以加快编译进度。
.C:用C语言编写的源代码文件。
.CLW:ClassWizard生成的用来存放类信息的文件。classwizard信息文件,ini文件的格式。
.CNT:用来定义帮助文件中“Contents”的结构。
.CPP或.CXX:用C++语言编写的源代码文件。
.CUR:光标资源文件。
.DEF:模块定义文件,供生成动态链接库时使用。
.DLG:定义对话框资源的独立文件。这种文件对于VC工程来说并非必需,因为VC一般把对话框资源放在.RC资源定义文件中。
.DSP:VC开发环境生成的工程文件,VC4及以前版本使用MAK文件来定义工程。项目文件,文本格式。
.DSW:VC开发环境生成的WorkSpace文件,用来把多个工程组织到一个WorkSpace中。工作区文件,与.dsp差不多。
.EXP:由LIB工具从DEF文件生成的输出文件,其中包含了函数和数据项目的输出信息,LINK工具将使用EXP文件来创建动态链接库。只有在编译DLL时才会生成,记录了DLL文件中的一些信息。
.H、.HPP或.HXX:用C/C++语言编写的头文件,通常用来定义数据类型,声明变量、函数、结构和类。
.HLP:Windows帮助文件。
.HM:在Help工程中,该文件定义了帮助文件与对话框、菜单或其它资源之间ID值的对应关系。
.HPJ:由Help Workshop生成的Help工程文件,用来控制Help文件的生成过程。
.HPG:生成帮助的文件的工程。
.ICO:图标资源文件。
.ILK:连接过程中生成的一种中间文件,只供LINK工具使用。
.INI:配置文件。
.LIB:库文件,LINK工具将使用它来连接各种输入库,以便最终生成EXE文件。
.LIC:用户许可证书文件,使用某些ActiveX控件时需要该文件。
.MAK:即MAKE文件,VC4及以前版本使用的工程文件,用来指定如何建立一个工程,VC6把MAK文件转换成DSP文件来处理。
.MAP:由LINK工具生成的一种文本文件,其中包含有被连接的程序的某些信息,例如程序中的组信息和公共符号信息等。执行文件的映像信息记录文件。
.MDP:旧版本的项目文件,相当于.dsp
.NCB:NCB是“No Compile Browser”的缩写,其中存放了供ClassView、WizardBar和Component Gallery使用的信息,由VC开发环境自动生成。无编译浏览文件。当自动完成功能出问题时可以删除此文件。编译工程后会自动生成。
.OBJ:由编译器或汇编工具生成的目标文件,是模块的二进制中间文件。
.ODL:用对象描述语言编写的源代码文件,VC用它来生成TLB文件。
.OLB:带有类型库资源的一种特殊的动态链接库,也叫对象库文件。
.OPT:VC开发环境自动生成的用来存放WorkSpace中各种选项的文件。工程关于开发环境的参数文件。如工具条位置信息等。


.PBI、.PBO和.PBT:由VC的性能分析工具PROFILE生成并使用的三种文件。
.PCH:预编译头文件,比较大,由编译器在建立工程时自动生成,其中存放有工程中已经编译的部分代码,在以后建立工程时不再重新编译这些代码,以便加快整个编译过程的速度。
.PDB:程序数据库文件,在建立工程时自动生成,其中存放程序的各种信息,用来加快调试过程的速度。记录了程序有关的一些数据和调试信息。
.PLG:编译信息文件,编译时的error和warning信息文件。
.RC:资源定义文件。
.RC2:资源定义文件,供一些特殊情况下使用。
.REG:注册表信息文件。
.RES:二进制资源文件,资源编译器编译资源定义文件后即生成RES文件。
.RTF:Rich Text Format(丰富文本格式)文档,可由Word或写字板来创建,常被用来生成Help文件。
.SBR:VC编译器为每个OBJ文件生成的原始浏览信息文件,浏览信息维护工具(BSCMAKE)将利用SBR文件来生成BSC文件。
.TLB:OLE库文件,其中存放了OLE自动化对象的数据类型、模块和接口定义,自动化服务器通过TLB文件就能了解自动化对象的使用方法。
.WAV:声音资源文件。

usidc52012-02-20 16:52用WM_COPYDATA的前提:
1,知道接收消息进程的句柄。
2,接收消息进程重载了WM_COPYDATA消息映射,能对其做出反应(否则不是发送端自作多情了?)
看过前提,的出结论:在自己写的两个进程间用WM_COPYDATA再好不过。
下面CODE几行就说明了一切。
获得句柄的方法,最简单的方法就是使用FindWindow,找窗口类,或者名,如果你觉得这样不把握,那就利用SetProp个窗口做个记号....(不说这些,跑踢儿了都)
OK,开始写发送端代码:
HWND hWnd = FindWindow(NULL,"MyApp");
if(hWnd!=NULL)
{
      COPYDATASTRUCT cpd; /*给COPYDATASTRUCT结构赋值*/
      cpd.dwData = 0;
      cpd.cbData = strlen("字符串");
      cpd.lpData = (void*)"字符串";
      ::SendMessage(hWnd,WM_COPYDATA,NULL,(LPARAM)&cpd);//发送!
      /*完事儿了!!*/
}
接收端重载ON_WM_COPYDATA消息映射函数(下面是手工所要加的,你最好还是用ClassWizard)
afx_msg BOOL OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct);
ON_WM_COPYDATA()/*消息映射*/

BOOL CMainFrame::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct)
{
        AfxMessageBox((LPCSTR)(pCopyDataStruct->lpData));/*利用对话框表示收到消息*/
        return CWnd::OnCopyData(pWnd, pCopyDataStruct);
}

进程通信还有其他一些手段,相对来说比较麻烦,但局限性要比WM_COPYDATA小。当然你也可以两端都注册一个消息来通信。

usidc52012-03-15 21:22
注:以下内容部分引自CSND中相关讨论的帖子,并结合自己的理解整理而成。仅供参考。

1)运行时库就是 C run-time library,是 C 而非 C++ 语言世界的概念:取这个名字就是因为你的 C 程序运行时需要这些库中的函数.
2)C 语言是所谓的“小内核”语言,就其语言本身来说很小(不多的关键字,程序流程控制,数据类型等);所以,C 语言内核开发出来之后,Dennis Ritchie 和 Brian Kernighan 就用 C 本身重写了 90% 以上的 UNIX 系统函数,并且把其中最常用的部分独立出来,形成头文件和对应的 LIBRARY,C run-time library 就是这样形成的。
3)随后,随着 C 语言的流行,各个 C 编译器的生产商/个体/团体都遵循老的传统,在不同平台上都有相对应的 Standard Library,但大部分实现都是与各个平台有关的。由于各个 C 编译器对 C 的支持和理解有很多分歧和微妙的差别,所以就有了 ANSI C;ANSI C (主观意图上)详细的规定了 C 语言各个要素的具体含义和编译器实现要求,引进了新的函数声明方式,同时订立了 Standard Library 的标准形式。所以C运行时库由编译器生产商提供。至于由其他厂商/个人/团体提供的头文件和库函数,应当称为第三方 C 运行库(Third party C run-time libraries)。
4)C run-time library里面含有初始化代码,还有错误处理代码(例如divide by zero处理)。你写的程序可以没有math库,程序照样运行,只是不能处理复杂的数学运算,不过如果没有了C run-time库,main()就不会被调用,exit()也不能被响应。因为C run-time library包含了C程序运行的最基本和最常用的函数。

5)到了 C++ 世界里,有另外一个概念:Standard C++ Library,它包括了上面所说的 C run-time library 和 STL。包含 C run-time library 的原因很明显,C++ 是 C 的超集,没有理由再重新来一个 C++ run-time library. VC针对C++ 加入的Standard C++ Library主要包括:LIBCP.LIB, LIBCPMT.LIB和 MSVCPRT.LIB
6)
Windows环境下,VC提供的 C run-time library又分为动态运行时库和静态运行时库。
动态运行时库主要是DLL库文件msvcrt.dll(or MSVCRTD.DLL for debug build),对应的Import library文件是MSVCRT.LIB(MSVCRTD.LIB for debug build)
静态运行时库(release版)对应的主要文件是:
LIBC.LIB (Single thread static library, retail version)
LIBCMT.LIB (Multithread static library, retail version)
msvcrt.dll提供几千个C函数,即使是像printf这么低级的函数都在msvcrt.dll里。其实你的程序运行时,很大一部分时间时在这些运行库里运行。在你的程序(release版)被编译时,VC会根据你的编译选项(单线程、多线程或DLL)自动将相应的运行时库文件(libc.lib,libcmt.lib或Import library msvcrt.lib)链接进来。
编译时到底哪个C run-time library联入你的程序取决于编译选项:
/MD, /ML, /MT, /LD   (Use Run-Time Library)
你可以VC中通过以下方法设置选择哪个C run-time library联入你的程序:
To find these options in the development environment, click Settings on the Project menu. Then click the C/C++ tab, and click Code Generation in the Category box. See the Use Run-Time Library drop-down box.
从程序可移植性考虑,如果两函数都可完成一种功能,选运行时库函数好,因为各个 C 编译器的生产商对标准C Run-time library提供了统一的支持.

usidc52012-03-15 21:23
/////////////////////////////////////////////////////////////////////////////////

1.基于对话框(/单文档/多文档)的MFC程序

预编译头文件stdafx.h:

#define VC_EXTRALEAN     // Exclude rarely-used stuff from Windows headers


// afxwin.h中声明了MFC封装的一些很基本的类(CWnd、CView、CButton、CDC等)

#include <afxwin.h>         // MFC core and standard components

// afxext.h中声明了MFC的一些扩展类(CBitmapButton、CControlBar、CSplitterWnd等)

#include <afxext.h>         // MFC extensions

// afxdisp.h中声明了Ole的几个类(COleException、COleVariant等)

#include <afxdisp.h>        // MFC Automation classes

// afxdtctl.h中声明了几个控件类(CImageList、CMonthCalCtrl、CDateTimeCtrl等)

#include <afxdtctl.h>       // MFC support for Internet Explorer 4 Common Controls


#ifndef _AFX_NO_AFXCMN_SUPPORT

// afxcmn.h中声明了MFC常用的一些控件类(CListCtrl、CProgressCtrl、CToolTipCtrl等)

#include <afxcmn.h>            // MFC support for Windows Common Controls

#endif // _AFX_NO_AFXCMN_SUPPORT


(1.1)Use MFC in a Shared DLL

Debug版本:

预定义:WIN32,_DEBUG,_WINDOWS,_AFXDLL,_MBCS

编译参数:/nologo /MDd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_AFXDLL" /D "_MBCS" /FR"Debug/" /Fp"Debug/ExeDlg.pch" /Yu"stdafx.h" /Fo"Debug/" /Fd"Debug/" /FD /GZ   /c

连接参数:/nologo /subsystem:windows /incremental:yes /pdb:"Debug/ExeDlg.pdb" /debug /machine:I386 /out:"Debug/ExeDlg.exe" /pdbtype:sept


Release版本:

预定义:与Debug版本相比,将_DEBUG替换成了NDEBUG

编译参数:/nologo /MD /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_AFXDLL" /D "_MBCS" /Fp"Release/ExeDlg.pch" /Yu"stdafx.h" /Fo"Release/" /Fd"Release/" /FD /c

连接参数:/nologo /subsystem:windows /incremental:no /pdb:"Release/ExeDlg.pdb" /machine:I386 /out:"Release/ExeDlg.exe"


(1.2)Use MFC in a Static Library

Debug版本:

预定义:与(1.1)相比,少了_AFXDLL

编译参数:将/MDd(使用Run-time library: Debug Multithreaded DLL)换成了/MTd(使用Run-time library: Debug Multithreaded)

连接参数:与(1.1)相同


Release版本:

编译参数/MD(使用Run-time library: Multithreaded DLL)换成了MT(使用Run-time library: Multithreaded)


***备注:以上编译/连接参数含义如下(更多的,请参考Msdn):

/nologo:抑制信息在编译或者连接时在Output Window输出;   /MD:运行时库使用MSVCRT.DLL;   /W3:编译时显示为Warning的级别为3;   /Gm:Enable Minimal Rebuild,一种减少重编译的选项;  /GX:Enable Exception Handling;     /ZI:设置Debug信息保存的数据库文件.PDB中;    /Od:Disable代码优化;     /FR:生成.SBR文件,包含有符号信息;       /Fp:命名生成的预编译头文件;     /Yu:指定预编译头文件。





/////////////////////////////////////////////////////////////////////////////////

2.MFC DLL项目

预编译头文件stdafx.h:

#define VC_EXTRALEAN        // Exclude rarely-used stuff from Windows headers


#include <afxwin.h>         // MFC core and standard components

#include <afxext.h>         // MFC extensions


#ifndef _AFX_NO_OLE_SUPPORT

#include <afxole.h>         // MFC OLE classes

#include <afxodlgs.h>       // MFC OLE dialog classes

#include <afxdisp.h>        // MFC Automation classes

#endif // _AFX_NO_OLE_SUPPORT



#ifndef _AFX_NO_DB_SUPPORT

#include <afxdb.h>            // MFC ODBC database classes

#endif // _AFX_NO_DB_SUPPORT


#ifndef _AFX_NO_DAO_SUPPORT

#include <afxdao.h>            // MFC DAO database classes

#endif // _AFX_NO_DAO_SUPPORT


#include <afxdtctl.h>        // MFC support for Internet Explorer 4 Common Controls

#ifndef _AFX_NO_AFXCMN_SUPPORT

#include <afxcmn.h>            // MFC support for Windows Common Controls

#endif // _AFX_NO_AFXCMN_SUPPORT

增加了两个OLE的头文件和两个数据库的头文件


(2.1) Use MFC in a Shared DLL

Debug版本:

预定义:WIN32,_DEBUG,_WINDOWS,_WINDLL,_AFXDLL,_MBCS,_USRDLL,与MFC Exe程序相比,增加了_WINDLL和_USRDLL

编译参数:与MFC Exe没有太大区别

连接参数:/nologo /subsystem:windows /dll /incremental:yes /pdb:"Debug/MFCDll.pdb" /debug /machine:I386 /def:"./MFCDll.def" /out:"Debug/MFCDll.dll" /implib:"Debug/MFCDll.lib" /pdbtype:sept

与MFC Exe相比,增加了/dll定义,以及/def:"./MFCDll.def"和/implib:"Debug/MFCDll.lib"。注意:其中MFCDll是测试的项目名字,非标准DLL名字。

从项目的文件上看,这个项目比MFC Exe多产生一个.def的文件用于定义导出函数。


Release版本与Debug版本的区别类似项目1中的比较(上了_AFXDLL定义)。


(2.2) Use MFC in a Static DLL

与(2.1)的区别,主要在使用的Run-time library类型上,与项目1中的比较。





/////////////////////////////////////////////////////////////////////////////////

3.MFC Extension DLL项目

预编译头文件stdafx.h内容与项目2相同。


(3.1) Use MFC in a Shared DLL

Debug版本:

预定义:WIN32,_DEBUG,_WINDOWS,_MBCS,_AFXEXT,_WINDLL,_AFXDLL,与项目2相比,将_USRDLL换成了_AFXEXT。

编译参数:与上述项目没有太大区别

连接参数:与MFC DLL项目相似


Release版本与Debug版本的区别类似项目1中的比较(上了_AFXDLL定义)。


(3.2) Use MFC in a Static DLL

类似以上项目的比较。





(注:以下项目均以Debug版本论述。)

/////////////////////////////////////////////////////////////////////////////////

4.Win32 DLL项目

预编译头文件stdafx.h:

#define WIN32_LEAN_AND_MEAN        // Exclude rarely-used stuff from Windows headers


#include <windows.h>


出现项目入口函数DllMain的实现。


(4.1) Not Using MFC

预定义:WIN32,_DEBUG,_WINDOWS,_MBCS,_USRDLL,WIN32DLLDEMO_EXPORTS,与项目2(MFC DLL)相比,少了_WINDLL,_AFXDLL,而仅保留了_USRDLL。另外,WIN32DLLDEMO_EXPORTS自定义的导出宏。

编译参数:没有太大区别。

连接参数:kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /incremental:yes /pdb:"Debug/Win32DllDemo.pdb" /debug /machine:I386 /out:"Debug/Win32DllDemo.dll" /implib:"Debug/Win32DllDemo.lib" /pdbtype:sept

与MFC DLL项目相比,多了很多库的连接,少了/subsystem:windows的定义。


(4.2) Use MFC in a Shared DLL

预定义:与(4.1)相比,增加了_WINDLL,_AFXDLL的定义

编译参数:没有太大区别。

连接参数:/nologo /dll /incremental:yes /pdb:"Debug/Win32DllDemo.pdb" /debug /machine:I386 /out:"Debug/Win32DllDemo.dll" /implib:"Debug/Win32DllDemo.lib" /pdbtype:sept

可以看出,(4.1) 里连接的很多库消失了,这时,项目4变成了类似于项目2中的设置。

***但是,编程时需要注意,项目4的stdafx.h仅包含了<windows.h>,此时如果要用MFC的类,需要自己加入MFC的几个头文件(<afxwin.h>、<afxext.h>等),并且将#include <windows.h>和DllMain入口函数注释掉!


(4.3) Use MFC in a Static DLL

使用MFC DLL的方式Shared和Static之间的区别与上述项目类似,不再做比较。




/////////////////////////////////////////////////////////////////////////////////

5.Win32 Static Library项目

预编译头文件stdafx.h(可能没有这个文件):

如果不使用MFC的话,仅仅#define WIN32_LEAN_AND_MEAN,而如果使用MFC,内容如下:

#define VC_EXTRALEAN        // Exclude rarely-used stuff from Windows headers


#include <afx.h>

#include <afxwin.h>


(5.1) Not Using MFC    

预定义:WIN32,_DEBUG,_MBCS,_LIB,新出现了符号_LIB

编译参数:/nologo /MLd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /Fp"Debug/W32StaPrehead.pch" /Yu"stdafx.h" /Fo"Debug/" /Fd"Debug/" /FD /GZ  /c

注意到使用的Run-time library参数为/MLd。

库参数:这个项目没有Link设置页,代替的是Library页,参数如下:/nologo /out:"Debug/W32StaPrehead.lib"


(5.2) Use MFC in a Shared DLL

预定义:WIN32,_DEBUG,_WINDOWS,_MBCS,_AFXDLL,与项目1(MFC Exe)设置相同。

编译参数:注意使用的Run-time library参数为/MDd。

库参数:没有太大区别。


(5.3) Use MFC in a Static DLL

编译参数:注意使用的Run-time library参数为/MTd。




/////////////////////////////////////////////////////////////////////////////////

6.Win32 Application项目

预编译头文件stdafx.h

#define WIN32_LEAN_AND_MEAN    // Exclude rarely-used stuff from Windows headers


// Windows Header Files:

#include <windows.h>


// C RunTime Header Files

#include <stdlib.h>

#include <malloc.h>

#include <memory.h>

#include <tchar.h>


出现了Win32程序的入口函数WinMain。


这个项目,Project->settings->General中设置使用MFC的方式,一般设为Not Using MFC,否则程序结构的改动比较大。从Not Using MFC到使用MFC的改变,对连接的库的影响类似于项目4(Win32 DLL)。


(6.1) Not Using MFC

预定义:WIN32,_DEBUG,_WINDOWS,_MBCS

编译参数:/nologo /MLd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /Fp"Debug/W32StaPrehead.pch" /Yu"stdafx.h" /Fo"Debug/" /Fd"Debug/" /FD /GZ  /c

注意到使用的Run-time library参数为/MLd。

连接参数:kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /incremental:yes /pdb:"Debug/W32AppDemo.pdb" /debug /machine:I386 /out:"Debug/W32AppDemo.exe" /pdbtype:sept


(6.2) Use MFC in a Shared DLL

编译参数:注意使用的Run-time library参数为/MDd。


(6.3) Use MFC in a Static DLL

编译参数:注意使用的Run-time library参数为/MTd。





小结:

1.MFC的使用方式对默认情况选择的Run-time library的影响(以Debug版本为例):

Not Using MFC ---〉/MLd: Debug Single-Threaded(静态连接LIBCD.LIB库)

Use MFC in a Shared DLL ---〉/MDd: Debug Multithreaded DLL(动态连接MSVCRTD.DLL库)

Use MFC in a Static DLL ---〉/MTd: Debug Multithreaded(静态连接LIBCMTD.LIB库)


2.如果不使用MFC,在Link一栏一般会连接一系列Windows API的库文件;如果使用MFC,这些连接库就会“消失”。


3.Debug版本一般会有_DEBUG的预定义,而Release版本则定义NDEBUG。


4.使用Shared MFC和Static MFC相比,前者一般多一个_AFXDLL的定义。默认使用的Run-time Library也不一样,前者为/MDd,后者为/MTd。


5.MFC的普通DLL项目比MFC的EXE项目,一般多_WINDLL和_USRDLL预定义;连接参数多一个/dll定义。而MFC扩展DLL项目与MFC普通DLL项目相比,预定义将_USRDLL换成了_AFXEXT。


6.不使用MFC的Win32 DLL与MFC DLL相比,预定义少了_WINDLL和_AFXDLL,而仅保留了_USRDLL。


7.不使用MFC的静态库有_LIB的预定义。


8.#include <afxwin.h> 和#include <windows.h>不能重复包含,前者用于MFC程序,后者用于程序。


9.为了去掉Windows头文件中很少用到的定义,一般在stdafx.h中,Win32程序会定义#define WIN32_LEAN_AND_MEAN,而MFC程序会定义#define VC_EXTRALEAN。


10.作为本文的应用,改变项目参数设置,实现不同类型项目之间的项目转换,如下:

MFC Exe   <======> MFC DLL

  ||                  ||

  ||                  ||

  ||                  ||

Win32 Exe <======> Win32 DLL

usidc52012-03-15 21:24使用库时常出现的错误, 提示: 基本是库和程序选择的run-time ibrary不同引起的.

LIBCMT.lib(crt0dat.obj) : error LNK2005: _exit already defined in MSVCRTD.lib

(MSVCR80D.dll)
LIBCMT.lib(crt0dat.obj) : error LNK2005: __exit already defined in MSVCRTD.lib

(MSVCR80D.dll)
LIBCMT.lib(crt0dat.obj) : error LNK2005: __cexit already defined in MSVCRTD.lib

(MSVCR80D.dll)

使用第三方的库造成的。这种情况主要是C运行期函数库和MFC的库冲突造成的。具体的办法就是将那

个提示出错的库放到另外一个库的前面。另外选择不同的C 函数库,可能会引起这个错误。微软和C有

两种C运行期函数库,一种是普通的函数库:LIBC.LIB,不支持多线程。另外一种是支持多线程的:

msvcrt.lib。如果一个工程里,这两种函数库混合使用,可能会引起这个错误,一般情况下它需要MFC

的库先于C运行期函数库被链接,因此建议使用支持多线程的msvcrt.lib。所以在使用第三方的库之前

首先要知道它链接的是什么库,否则就可能造成LNK2005错误。如果不得不使用第三方的库,可以尝试

按下面所说的方法修改,但不能保证一定能解决问题,前两种方法是微软提供的:
A、选择VC菜单Project->Settings->Link->Catagory选择Input,再在Ignore   libraries   的Edit

栏中填入你需要忽略的库,如:Nafxcwd.lib;Libcmtd.lib。然后在Object/library   Modules的Edit

栏中填入正确的库的顺序,这里需要你能确定什么是正确的顺序.(最好不要这么做)
B、选择VC菜单Project->Settings->Link页,然后在Project   Options的Edit栏中输入/verbose:lib

,这样就可以在编译链接程序过程中在输出窗口看到链接的顺序了。
C、选择VC菜单Project->Settings->C/C++页,Catagory选择Code   Generation后再在User  

Runtime   libraray中选择MultiThread   DLL等其他库,逐一尝试。
关于编译器的相关处理过程,参考:
http://www.donews.net/xzwenlan/archive/2004/12/23/211668.aspx


warning LNK4098: defaultlib "LIBCMT" conflicts with use of other libs ;use

/NODEFAULTLIB:library(出现警告最好调整程序或库使用相同的运行库,要不就忽略一些,不处理)

The table below shows which libraries should be ignored depending on which run-time

library you want to use.(下面这张表显示了不同运行库使用的lib和要忽视的lib).

To use this run-time library(使用的库)               Ignore these libraries(要忽略的库)
Single-threaded (libc.lib)                                   libcmt.lib, msvcrt.lib,

libcd.lib, libcmtd.lib, msvcrtd.lib
Multithreaded (libcmt.lib)                                   libc.lib, msvcrt.lib,

libcd.lib, libcmtd.lib, msvcrtd.lib
Multithreaded using DLL (msvcrt.lib)                libc.lib, libcmt.lib, libcd.lib,

libcmtd.lib, msvcrtd.lib
Debug Single-threaded (libcd.lib)                      libc.lib, libcmt.lib, msvcrt.lib,

libcmtd.lib, msvcrtd.lib
Debug Multithreaded (libcmtd.lib)                      libc.lib, libcmt.lib, msvcrt.lib,

libcd.lib, msvcrtd.lib
Debug Multithreaded using DLL (msvcrtd.lib)    libc.lib, libcmt.lib, msvcrt.lib,

libcd.lib, libcmtd.lib


nafxcwd.lib(thrdcore.obj) : error LNK2001: unresolved external symbol __beginthreadex
nafxcwd.lib(thrdcore.obj) : error LNK2001: unresolved external symbol __endthreadex
这是因为MFC要使用多线程时库, 需要更改设置:
[Project] --> [Settings] --> 选择"C/C++"属性页,
在Category中选择Code Generation,
再在Use run-time library中选择Debug Multithreaded或者multithreaded
其中,

Single-Threaded 单线程静态链接库(release版本)
Multithreaded 多线程静态链接库(release版本)
multithreaded DLL 多线程动态链接库(release版本)
Debug Single-Threaded 单线程静态链接库(debug版本)
Debug Multithreaded 多线程静态链接库(debug版本)
Debug Multithreaded DLL 多线程动态链接库(debug版本)
单线程: 不需要多线程调用时, 多用在DOS环境下
多线程: 可以并发运行
静态库: 直接将库与程序Link, 可以脱离MFC库运行
动态库: 需要相应的DLL动态库, 程序才能运行
release版本: 正式发布时使用
debug版本: 调试阶段使用

usidc52012-03-15 21:24链接时出现:LIBCMTD.lib(crt0dat.obj) : error LNK2005: _exit 已经在 MSVCRTD.lib(MSVCR71D.dll) 中定义 等类似错误

原因:
Run-Time Library
•Run-Time Library是编译器提供的标准库,提供一些基本的库函数和系统调用。
我们一般使用的Run-Time Library是C Run-Time Libraries。当然也有Standard C++ libraries。
C Run-Time Libraries实现ANSI C的标准库。VC安装目录的CRT目录有C Run-Time库的大部分源代码。 C Run-Time Libraries有静态库版本,也有动态链接库版本;有单线程版本,也有多线程版本;还有调试和非调试版本。
•动态链接库版本:
/MD Multithreaded DLL 使用导入库MSVCRT.LIB
/MDd Debug Multithreaded DLL 使用导入库MSVCRTD.LIB
•静态库版本:
/ML Single-Threaded 使用静态库LIBC.LIB
/MLd Debug Single-Threaded 使用静态库LIBCD.LIB
/MT Multithreaded 使用静态库LIBCMT.LIB
/MTd Debug Multithreaded 使用静态库LIBCMTD.LIB
(有/MT,/MTd,/Md,/MDd四个选项,你必须让所有使用的库都使用相同的配置,否则就会有相应的提示,甚至可能会出现无法解析的函数。有时我们使用的库不是自己可以控制的,那么就只能把工程属性设置成和你使用的库相同的选项。)
若要使用此运行时库
请忽略这些库
单线程 (libc.lib)
libcmt.lib、msvcrt.lib、libcd.lib、libcmtd.lib、msvcrtd.lib
多线程 (libcmt.lib)
libc.lib、msvcrt.lib、libcd.lib、libcmtd.lib、msvcrtd.lib
使用 DLL 的多线程 (msvcrt.lib)
libc.lib、libcmt.lib、libcd.lib、libcmtd.lib、msvcrtd.lib
调试单线程 (libcd.lib)
libc.lib、libcmt.lib、msvcrt.lib、libcmtd.lib、msvcrtd.lib
调试多线程 (libcmtd.lib)
libc.lib、libcmt.lib、msvcrt.lib、libcd.lib、msvcrtd.lib
使用 DLL 的调试多线程 (msvcrtd.lib)
libc.lib、libcmt.lib、msvcrt.lib、libcd.lib、libcmtd.lib
解决方法:
属性,链接器,输入,忽略指定库 libc.lib、libcmt.lib、msvcrt.lib、libcd.lib、libcmtd.lib (这是我需要忽略的,你可以根据你工程的实际情况选择。)

update(20060205):
5. 链接是出现不能打开mfc4xx.lib的错误时,这是因为VC7对MFC的dll进行了升级。

解决办法:
属性,链接器,输入,附加依赖项 中 添加mfc71d.lib。
================================================================
线程运行时库设置错误, 提示:
nafxcwd.lib(thrdcore.obj) : error LNK2001: unresolved external symbol __beginthreadex
nafxcwd.lib(thrdcore.obj) : error LNK2001: unresolved external symbol __endthreadex
这是因为MFC要使用多线程时库, 需要更改设置:
[Project] --> [Settings] --> 选择"C/C++"属性页,
在Category中选择Code Generation,
再在Use run-time library中选择Debug Multithreaded或者multithreaded
其中,

Single-Threaded 单线程静态链接库(release版本)
Multithreaded 多线程静态链接库(release版本)
multithreaded DLL 多线程动态链接库(release版本)
Debug Single-Threaded 单线程静态链接库(debug版本)
Debug Multithreaded 多线程静态链接库(debug版本)
Debug Multithreaded DLL 多线程动态链接库(debug版本)
单线程: 不需要多线程调用时, 多用在DOS环境下
多线程: 可以并发运行
静态库: 直接将库与程序Link, 可以脱离MFC库运行
动态库: 需要相应的DLL动态库, 程序才能运行
release版本: 正式发布时使用
debug版本: 调试阶段使用

usidc52012-03-15 21:251、Run-Time Library

Run-Time Library是编译器提供的标准库,提供一些基本的库函数和系统调用。
我们一般使用的Run-Time Library是C Run-Time Libraries。当然也有Standard C++ libraries。
C Run-Time Libraries实现ANSI C的标准库。VC安装目录的CRT目录有C Run-Time库的大部分源代码。
C Run-Time Libraries有静态库版本,也有动态链接库版本;有单线程版本,也有多线程版本;还有调试和非调试版本。
可以在"project"-"settings"-"C/C++"-"Code Generation"中选择Run-Time Library的版本。

动态链接库版本:
/MD Multithreaded DLL 使用导入库MSVCRT.LIB
/MDd Debug Multithreaded DLL 使用导入库MSVCRTD.LIB

静态库版本:
/ML Single-Threaded 使用静态库LIBC.LIB
/MLd Debug Single-Threaded 使用静态库LIBCD.LIB
/MT Multithreaded 使用静态库LIBCMT.LIB
/MTd Debug Multithreaded 使用静态库LIBCMTD.LIB

C Run-Time Library的标准io部分与操作系统的关系很密切,在Windows上,CRT的io部分代码只是一个包装,底层要用到操作系统内核kernel32.dll中的函数,在编译时使用导入库kernel32.lib。这也就是为什么在嵌入式环境中,我们一般不能直接使用C标准库。
在Linux环境当然也有C标准库,例如:
ld -o output /lib/crt0.o hello.o -lc
参数"-lc"就是在引用C标准库libc.a。猜一猜"-lm"引用哪个库文件?

2、常见的编译参数

VC建立项目时总会定义"Win32"。控制台程序会定义"_CONSOLE",否则会定义"_WINDOWS"。Debug版定义"_DEBUG",Release版定义"NDEBUG"
与MFC DLL有关的编译常数包括:
_WINDLL 表示要做一个用到MFC的DLL
_USRDLL 表示做一个用户DLL(相对MFC扩展DLL而言)
_AFXDLL 表示使用MFC动态链接库
_AFXEXT 表示要做一个MFC扩展DLL
所以:
Regular, statically linked to MFC _WINDLL,_USRDLL
Regular, using the shared MFC DLL _WINDLL,_USRDLL,_AFXDLL
Extension DLL _WINDLL,_AFXDLL,_AFXEXT

CL.EXE编译所有源文件,LINK.EXE链接EXE和DLL,LIB.EXE产生静态库。

3、subsystem和可执行文件的启动

LINK的时候需要指定/subsystem,这个链接选项告诉Windows如何运行可执行文件。
控制台程序是/subsystem:"console"
其它程序一般都是/subsystem:"windows "
将 subsystem 选成"console"后,Windows在进入可执行文件的代码前(如mainCRTStartup),就会产生一个控制台窗口。
如果选择"windows",操作系统就不产生console窗口,该类型应用程序的窗口由用户自己创建。

可执行文件都有一个Entry Point,LINK时可以用/entry指定。缺省情况下,如果subsystem是“console”,Entry Point是 mainCRTStartup(ANSI)或wmainCRTStartuup(UNICODE),即:
/subsystem:"console" /entry:"mainCRTStartup" (ANSI)
/subsystem:"console" /entry:"wmainCRTStartuup" (UNICODE)
mainCRTStartup 或 wmainCRTStartuup 会调用main或wmain。
值得一提的是,在进入应用程序的Entry Point前,Windows的装载器已经做过C变量的初始化,有初值的全局变量拥有了它们的初值,没有初值的变量被设为0。

如果subsystem是“windows”,Entry Point是WinMain(ANSI)或wWinMain(UINCODE),即:
/subsystem:"windows" /entry:"WinMainCRTStartup" (ANSI)
/sbusystem:"windows" /entry:"wWinMainCRTStartup" (UINCODE)
WinMainCRTStartup 或 wWinMainCRTStartup 会调用 WinMain 或 wWinMain。

这些入口点函数,在CRT目录都可以看到源代码,例如(为了简洁,我删除了原代码的一些条件编译):

void mainCRTStartup(void)
{
        int mainret;

        /* Get the full Win32 version */
        _osver = GetVersion();
        _winminor = (_osver >> 8) & 0x00FF ;
        _winmajor = _osver & 0x00FF ;
        _winver = (_winmajor << 8) + _winminor;
        _osver = (_osver >> 16) & 0x00FFFF ;

#ifdef _MT
        if ( !_heap_init(1) )               /* initialize heap */
#else  /* _MT */
        if ( !_heap_init(0) )               /* initialize heap */
#endif  /* _MT */
            fast_error_exit(_RT_HEAPINIT);  /* write message and die */

#ifdef _MT
        if( !_mtinit() )                    /* initialize multi-thread */
            fast_error_exit(_RT_THREAD);    /* write message and die */
#endif  /* _MT */

        __try {
            _ioinit();                      /* initialize lowio */
            _acmdln = (char *)GetCommandLineA();        /* get cmd line info */
            _aenvptr = (char *)__crtGetEnvironmentStringsA();        /* get environ info */
            _setargv();
            _setenvp();
            __initenv = _environ;
            mainret = main(__argc, __argv, _environ);
            exit(mainret);
        }
        __except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) )
        {
            _exit( GetExceptionCode() );        /* Should never reach here */
        } /* end of try - except */
}  
如果使用MFC框架,WinMain也会被埋藏在MFC库中(APPMODUL.CPP):
extern "C" int WINAPI
_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)
{
// call shared/exported WinMain
return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}
对于ANSI版本,"_tWinMain"就是"WinMain";对于UINCODE版本,"_tWinMain"就是"wWinMain"。可参见afx.h:

#ifdef _UNICODE
#define _tmain wmain
#define _tWinMain wWinMain
#else
#define _tmain main
#define _tWinMain WinMain
#endif

全局C++对象的构造函数是在什么地方调用的?答案是在进入应用程序的Entry Point后,在调用main函数前的初始化操作中。所以MFC的theApp的构造函数是在_tWinMain之前调用的。

4、不显示Console窗口的Console程序

在默认情况下/subsystem 和/entry开关是匹配的,也就是:
"console"对应"mainCRTStartup"或者"wmainCRTStartup"
"windows"对应"WinMain"或者"wWinMain"
我们可以通过手动修改的方法使他们不匹配。例如:
#include "windows.h"
#pragma comment( linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"" ) // 设置入口地址
void main(void)
{
MessageBox(NULL, "hello", "Notice", MB_OK);
}

这个Console程序就不会显示Console窗口。如果选/MLd的话,这个程序只需要链接LIBCD.LIB user32.lib kernel32.lib。

其实如果不想看到Console窗口,还有一个更直接的方法:那就是直接在EXE文件中将PE文件头的Subsystem从3改成2。在EXE文件中,PE文件头的偏移地址是0x3c,Subsystem是一个WORD,它在PE文件头中的偏移是0x5c。

5、MFC的库文件

MFC的库可以静态链接,也可以动态链接。静态库和动态库又有Debug和Release,ANSI和Unicode版本之分。
静态MFC库主要有:
ANSI Debug NAFXCWD.LIB
ANSI Release NAFXCW.LIB
Unicode Debug UAFXCWD.LIB
Unicode Release UAFXCW.LIB

动态链接库主要有;
ANSI Debug MFCxxD.LIB (core,MFCxxD.DLL),
MFCOxxD.LIB (OLE,MFCOxxD.DLL),
MFCDxxD.LIB (database,MFCDxxD.DLL),
MFCNxxD.LIB (network,MFCNxxD.DLL),
MFCSxxD.LIB (static)

ANSI Release MFCxx.LIB (combined,MFCxx.DLL)
MFCSxx.LIB (static)

Unicode Debug MFCxxUD.LIB (core,MFCxxUD.DLL),
MFCOxxUD.LIB (OLE,MFCOxxUD.DLL),
MFCDxxUD.LIB (database,MFCDxxUD.DLL),
MFCNxxUD.LIB (network,MFCNxxUD.DLL),
MFCSxxUD.LIB (static)

Unicode Release MFCxxU.DLL (combined,MFCxxU.DLL),
MFCSxxU.LIB (static)

上面的LIB文件除了MFCSxx(D、U、UD).LIB以外都是导入库。
MFC动态链接库版本也需要静态链接一些文件,这些文件就放在MFCSxx(D、U、UD).LIB中。例如包含_tWinMain的appmodul.cpp。

6、结束语

研究这些问题的动机是想弄清楚我们的程序是如何装载、运行的。但是,由于Windows不是开源平台,我也只能研究到PE文件(Windows上可执行文件的格式)。entry point、subsystem都是PE文件头的一部分。

Windows在进入PE文件的entry point之前做了些什么,就看不到了,只能大概推测:应该是创建一个进程,装载PE文件和所有需要的DLL,初始化C变量,然后从某个起点函数开始运行。不同的subsystem,应该有不同的起点。调用这个起点函数时应该传入PE文件的entry point地址。

usidc52012-03-15 21:25许多Visual C++的使用者都碰到过LNK2005:symbol already defined和LNK1169:one or more multiply defined symbols found这样的链接错误,而且通常是在使用第三方库时遇到的。对于这个问题,有的朋友可能不知其然,而有的朋友可能知其然却不知其所以然,那么本文就试图为大家彻底解开关于它的种种疑惑。



大家都知道,从C/C++源程序到可执行文件要经历两个阶段:(1)编译器将源文件编译成汇编代码,然后由汇编器(assembler)翻译成机器指令(再加上其它相关信息)后输出到一个个目标文件(object file,VC的编译器编译出的目标文件默认的后缀名是.obj)中;(2)链接器(linker)将一个个的目标文件(或许还会有若干程序库)链接在一起生成一个完整的可执行文件。



编译器编译源文件时会把源文件的全局符号(global symbol)分成强(strong)和弱(weak)两类传给汇编器,而随后汇编器则将强弱信息编码并保存在目标文件的符号表中。那么何谓强弱呢?编译器认为函数与初始化了的全局变量都是强符号,而未初始化的全局变量则成了弱符号。比如有这么个源文件:



extern int errorno;

int buf[2] = {1,2};

int *p;



int main()

{

   return 0;

}



其中main、buf是强符号,p是弱符号,而errorno则非强非弱,因为它只是个外部变量的使用声明。



有了强弱符号的概念,我们就可以看看链接器是如何处理与选择被多次定义过的全局符号:



规则1: 不允许强符号被多次定义(即不同的目标文件中不能有同名的强符号);





规则2: 如果一个符号在某个目标文件中是强符号,在其它文件中都是弱符号,那么选择强符号;





规则3: 如果一个符号在所有目标文件中都是弱符号,那么选择其中任意一个;





由上可知多个目标文件不能重复定义同名的函数与初始化了的全局变量,否则必然导致LNK2005和LNK1169两种链接错误。可是,有的时候我们并没有在自己的程序中发现这样的重定义现象,却也遇到了此种链接错误,这又是何解?嗯,问题稍微有点儿复杂,容我慢慢道来。





众所周知,ANSI C/C++ 定义了相当多的标准函数,而它们又分布在许多不同的目标文件中,如果直接以目标文件的形式提供给程序员使用的话,就需要他们确切地知道哪个函数存在于哪个目标文件中,并且在链接时显式地指定目标文件名才能成功地生成可执行文件,显然这是一个巨大的负担。所以C语言提供了一种将多个目标文件打包成一个文件的机制,这就是静态程序库(static library)。开发者在链接时只需指定程序库的文件名,链接器就会自动到程序库中寻找那些应用程序确实用到的目标模块,并把(且只把)它们从库中拷贝出来参与构建可执行文件。几乎所有的C/C++开发系统都会把标准函数打包成标准库提供给开发者使用(有不这么做的吗?)。



程序库为开发者带来了方便,但同时也是某些混乱的根源。我们来看看链接器是如何解析(resolve)对程序库的引用的。

  

在符号解析(symbol resolution)阶段,链接器按照所有目标文件和库文件出现在命令行中的顺序从左至右依次扫描它们,在此期间它要维护若干个集合:(1)集合E是将被合并到一起组成可执行文件的所有目标文件集合;(2)集合U是未解析符号(unresolved symbols,比如已经被引用但是还未被定义的符号)的集合;(3)集合D是所有之前已被加入到E的目标文件定义的符号集合。一开始,E、U、D都是空的。



(1): 对命令行中的每一个输入文件f,链接器确定它是目标文件还是库文件,如果它是目标文件,就把f加入到E,并把f中未解析的符号和已定义的符号分别加入到U、D集合中,然后处理下一个输入文件。



(2): 如果f是一个库文件,链接器会尝试把U中的所有未解析符号与f中各目标模块定义的符号进行匹配。如果某个目标模块m定义了一个U中的未解析符号,那么就把m加入到E中,并把m中未解析的符号和已定义的符号分别加入到U、D集合中。不断地对f中的所有目标模块重复这个过程直至到达一个不动点(fixed point),此时U和D不再变化。而那些未加入到E中的f里的目标模块就被简单地丢弃,链接器继续处理下一输入文件。



(3): 如果处理过程中往D加入一个已存在的符号,或者当扫描完所有输入文件时U非空,链接器报错并停止动作。否则,它把E中的所有目标文件合并在一起生成可执行文件。



VC带的编译器名字叫cl.exe,它有这么几个与标准程序库有关的选项: /ML、/MLd、/MT、/MTd、/MD、/MDd。这些选项告诉编译器应用程序想使用什么版本的C标准程序库。/ML(缺省选项)对应单线程静态版的标准程序库(libc.lib);/MT对应多线程静态版标准库(libcmt.lib),此时编译器会自动定义_MT宏;/MD对应多线程DLL版(导入库msvcrt.lib,DLL是msvcrt.dll),编译器自动定义_MT和_DLL两个宏。后面加d的选项都会让编译器自动多定义一个_DEBUG宏,表示要使用对应标准库的调试版,因此/MLd对应调试版单线程静态标准库(libcd.lib),/MTd对应调试版多线程静态标准库(libcmtd.lib),/MDd对应调试版多线程DLL标准库(导入库msvcrtd.lib,DLL是msvcrtd.dll)。虽然我们的确在编译时明白无误地告诉了编译器应用程序希望使用什么版本的标准库,可是当编译器干完了活,轮到链接器开工时它又如何得知一个个目标文件到底在思念谁?为了传递相思,我们的编译器就干了点秘密的勾当。在cl编译出的目标文件中会有一个专门的区域(关心这个区域到底在文件中什么地方的朋友可以参考COFF和PE文件格式)存放一些指导链接器如何工作的信息,其中有一种就叫缺省库(default library),这些信息指定了一个或多个库文件名,告诉链接器在扫描的时候也把它们加入到输入文件列表中(当然顺序位于在命令行中被指定的输入文件之后)。说到这里,我们先来做个小实验。写个顶顶简单的程序,然后保存为main.c :



/* main.c */

int main() { return 0; }



用下面这个命令编译main.c(什么?你从不用命令行来编译程序?这个......) :



cl /c main.c



/c是告诉cl只编译源文件,不用链接。因为/ML是缺省选项,所以上述命令也相当于: cl /c /ML main.c 。如果没什么问题的话(要出了问题才是活见鬼!当然除非你的环境变量没有设置好,这时你应该去VC的bin目录下找到vcvars32.bat文件然后运行它。),当前目录下会出现一个main.obj文件,这就是我们可爱的目标文件。随便用一个文本编辑器打开它(是的,文本编辑器,大胆地去做别害怕),搜索"defaultlib"字符串,通常你就会看到这样的东西: "-defaultlib:LIBC -defaultlib:OLDNAMES"。啊哈,没错,这就

是保存在目标文件中的缺省库信息。我们的目标文件显然指定了两个缺省库,一个是单线程静态版标准库libc.lib(这与/ML选项相符),另外一个是oldnames.lib(它是为了兼容微软以前的C/C++开发系统)。



VC的链接器是link.exe,因为main.obj保存了缺省库信息,所以可以用



link main.obj libc.lib



或者



link main.obj



来生成可执行文件main.exe,这两个命令是等价的。但是如果你用



link main.obj libcd.lib



的话,链接器会给出一个警告: "warning LNK4098: defaultlib "LIBC" conflicts with use of other libs; use /NODEFAULTLIB:library",因为你显式指定的标准库版本与目标文件的缺省值不一致。通常来说,应该保证链接器合并的所有目标文件指定的缺省标准库版本一致,否则编译器一定会给出上面的警告,而LNK2005和LNK1169链接错误则有时会出现有时不会。那么这个有时到底是什么时候?呵呵,别着急,下面的一切正是为喜欢追根究底的你准备的。



建一个源文件,就叫mylib.c,内容如下:



/* mylib.c */

#include



void foo()

{

   printf("%s","I am from mylib!/n");

}







cl /c /MLd mylib.c





命令编译,注意/MLd选项是指定libcd.lib为默认标准库。lib.exe是VC自带的用于将目标文件打包成程序库的命令,所以我们可以用



lib /OUT:my.lib mylib.obj



将mylib.obj打包成库,输出的库文件名是my.lib。接下来把main.c改成:



/* main.c */

void foo();



int main()

{

   foo();

   return 0;

}







cl /c main.c





编译,然后用



link main.obj my.lib



进行链接。这个命令能够成功地生成main.exe而不会产生LNK2005和LNK1169链接错误,你仅仅是得到了一条警告信息:"warning LNK4098: defaultlib "LIBCD" conflicts with use of other libs; use /NODEFAULTLIB:library"。我们根据前文所述的扫描规则来分析一下链接器此时做了些啥。



一开始E、U、D都是空集,链接器首先扫描到main.obj,把它加入E集合,同时把未解析的foo加入U,把main加入D,而且因为main.obj的默认标准库是libc.lib,所以它被加入到当前输入文件列表的末尾。接着扫描my.lib,因为这是个库,所以会拿当前U中的所有符号(当然现在就一个foo)与my.lib中的所有目标模块(当然也只有一个mylib.obj)依次匹配,看是否有模块定义了U中的符号。结果mylib.obj确实定义了foo,于是它被加入到E,foo从U转移到D,mylib.obj引用的printf加入到U,同样地,mylib.obj指定的默认标准库是libcd.lib,它也被加到当前输入文件列表的末尾(在libc.lib的后面)。不断地在my.lib库的各模块上进行迭代以匹配U中的符号,直到U、D都不再变化。很明显,现在就已经到达了这么一个不动点,所以接着扫描下一个输入文件,就是libc.lib。



链接器发现libc.lib里的printf.obj里定义有printf,于是printf从U移到D,而printf.obj被加入到E,它定义的所有符号加入到D,它里头的未解析符号加入到U。链接器还会把每个程序都要用到的一些初始化操作所在的目标模块(比如crt0.obj等)及它们所引用的模块(比如malloc.obj、free.obj等)自动加入到E中,并更新U和D以反应这个变化。事实上,标准库各目标模块里的未解析符号都可以在库内其它模块中找到定义,因此当链接器处理完libc.lib时,U一定是空的。最后处理libcd.lib,因为此时U已经为空,所以链接器会抛弃它里面的所有目标模块从而结束扫描,然后合并E中的目标模块并输出可执行文件。





上文描述了虽然各目标模块指定了不同版本的缺省标准库但仍然链接成功的例子,接下来你将目睹因为这种不严谨而导致的悲惨失败。





修改mylib.c成这个样子:



#include



void foo()

{

   // just a test , don't care memory leak

   _malloc_dbg( 1, _NORMAL_BLOCK, __FILE__, __LINE__ );

}



其中_malloc_dbg不是ANSI C的标准库函数,它是VC标准库提供的malloc的调试版,与相关函数配套能帮助开发者抓各种内存错误。使用它一定要定义_DEBUG宏,否则预处理器会把它自动转为malloc。继续用



cl /c /MLd mylib.c

lib /OUT:my.lib mylib.obj



编译打包。当再次用



link main.obj my.lib



进行链接时,我们看到了什么?天哪,一堆的LNK2005加上个贵为"fatal error"的LNK1169垫底,当然还少不了那个LNK4098。链接器是不是疯了?不,你冤枉可怜的链接器了,我拍胸脯保证它可是一直在尽心尽责地照章办事。



一开始E、U、D为空,链接器扫描main.obj,把它加入E,把foo加入U,把main加入D,把libc.lib加入到当前输入文件列表的末尾。接着扫描my.lib,foo从U转移到D,_malloc_dbg加入到U,libcd.lib加到当前输入文件列表的尾部。然后扫描libc.lib,这时会发现libc.lib里任何一个目标模块都没有定义_malloc_dbg(它只在调试版的标准库中存在),所以不会有任何一个模块因为_malloc_dbg而加入E,但是每个程序都要用到的初始化模块(如crt0.obj等)及它们所引用的模块(比如malloc.obj、free.obj等)还是会自动加入到E中,同时U和D被更新以反应这个变化。当链接器处理完libc.lib时,U只剩_malloc_dbg这一个符号。最后处理libcd.lib,发现dbgheap.obj定义了_malloc_dbg,于是dbgheap.obj加入到E,它里头的未解析符号加入U,它定义的所有其它符号也加入D,这时灾难便来了。之前malloc等符号已经在D中(随着libc.lib里的malloc.obj加入E而加入的),而dbgheap.obj又定义了包括malloc在内的许多同名符号,这引发了重定义冲突,链接器只好中断工作并报告错误。





现在我们该知道,链接器完全没有责任,责任在我们自己的身上。是我们粗心地把缺省标准库版本不一致的目标文件(main.obj)与程序库(my.lib)链接起来,导致了大灾难。解决办法很简单,要么用/MLd选项来重编译main.c;要么用/ML选项重编译mylib.c。



在上述例子中,我们拥有库my.lib的源代码(mylib.c),所以可以用不同的选项重新编译这些源代码并再次打包。可如果使用的是第三方的库,它并没有提供源代码,那么我们就只有改变自己程序的编译选项来适应这些库了。但是如何知道库中目标模块指定的默认库呢?其实VC提供的一个小工具便可以完成任务,这就是dumpbin.exe。运行下面这个命令



dumpbin /DIRECTIVES my.lib



然后在输出中找那些"Linker Directives"引导的信息,你一定会发现每一处这样的信息都会包含若干个类似"-defaultlib:XXXX"这样的字符串,其中XXXX便代表目标模块指定的缺省库名。





知道了第三方库指定的默认标准库,再用合适的选项编译我们的应用程序,就可以避免LNK2005和LNK1169链接错误。喜欢IDE的朋友,你一样可以到 "Project属性" -> "C/C++" -> "代码生成(code generation)" -> "运行时库(run-time library)" 项下设置应用程序的默认标准库版本,这与命令行选项的效果是一样的。

usidc52012-03-17 19:36
重命名了一个MFC常规DLL的工程文件(VS C++ 2005编译环境),结果在编译时出现这样的警告:1>B.exp : warning LNK4070: .EXP 中的 /OUT:A.dll 指令与输出文件名"../outdir/Debug/B.dll"不同;忽略指令(这里假设原来的工程文件名叫A.vcproj,改名后叫B.vcproj)。后来我发现虽然输出为B.dll,但是对应的静态库B.lib被其它工程以隐式链接的方式调用时,使用的还是A.dll(这个可以使用Dependcies工具来查看),这样导致往往其它动态库不能加载成功(因为)。这下我不能把它仅仅当做warning而弃之不管了,于是上网查资料解决这个warning。查完资料,再结合自己的思考,大致明白了造成warning的原因。原来是虽然我修改了工程名,但是没有修改这个工程的def文件中LIBRARY字段的值,造成工程的输出文件和def文件的LIBRARY字段的值不一样。比如我把A.vcproj修改为B.vcproj,但在def文件还是LIBRARY "A"。这时只需将def文件中的LIBRARY字段修改为:LIBRARY "B"。这样就能完全消除这个警告。而被别的库以隐式链接调用也是以B.dll面目出现的。

usidc52012-03-28 09:55一个VC项目要增加播放wav功能,要求很低,能循环播放和停止就可以。
因为只需在Windows上执行,先想到用MCI接口。试了一下,用mciSendCommand可以实现基本的播放wav文件的功能。但循环播放wav就麻烦了,必须向窗口传送MM_MCINOTIFY消息。
google了一下,才发现原来有更简单的方法——用sndPlaySound。一条语句sndPlaySound(filename, SND_ASYNC | SND_LOOP)就可以循环播放声音文件,完全满足我的要求。简单吧。
函数定义是:
BOOL sndPlaySound(LPCSTR lpszSound, UINT fuSound);
其中,lpszSound一般是wav文件的文件名,fuSound是参数。常见的fuSound参数有:
SND_ASYNC 异步播放,即程序不等播放结束就继续执行,播放背景声。
SND_SYNC 同步播放,即播放结束才继续执行
SND_LOOP 循环播放
SND_NODEFAULT 如果找不到指定文件,保持安静。如不指定此参数,则播放系统默认警告音。如没有默认警告音,则为失败。
执行成功返回TRUE,失败返回FALSE。
要停止播放只需再执行一遍lpszSound参数为NULL的sndPlaySound函数。
要求:
程序要加入Mmsystem.h或Windows.h头文件,编译时链入Winmm.lib库。
限制:
sndPlaySound只能播放wav文件。
wav文件在播放前将被装入内存,所以不能太大。
只能同时播放一个声音。后一个声音会关闭前一个声音。
函数PlaySound是sndPlaySound的增强版,支持更多声音类型和fuSound参数,并可以播放内存和资源中的声音。

usidc52012-03-28 09:56
声音是多媒体的一个重要组成部分,在应用程序中加入声音可以使界面更友好。在
VC++
中可以根据不同的应用要求,用不同的方法实现声音的播放。


一.播放声音文件的简单方法

  在
VC++
中的多媒体动态连接库中提供了一组与音频设备有关的函数。利用这些函数可以方便地播放声音。最简单的播放声音方法就是直接调用
VC++
中提供的声音播放函数
BOOL sndPlaySound ( LPCSTR lpszSound,UINT fuSound );
BOOL PlaySound( LPCSTR lpszSound, HMODULE hmod, DWORD fuSound );
其中参数
lpszSound
是需要播放声音的
.WAV
文件的路径和文件名,
hmod
在这里为
NULL
fuSound
是播放声音的标志
,
详细说明请参考
VC++
中的帮助。
例如播放
C:soundmusic.wav
可以用
sndPlaySound ("c:\sound\music.wav",SND_ASYNC);
PlaySound("c:\sound\music.wav",NULL, SND_ASYNC|SND_NODEFAULT );
如果没有找到
music.wav
文件,第一种格式将播放系统默认的声音,第二种格式不会播放系统默认的声音。


二.将声音文件加入到程序中

  在
VC++
的程序设计中,可以利用各种标准的资源,如位图,菜单,对话框等。同时
VC++
也允许用户自定义资源,因此我们可以将声音文件作为用户自定义资源加入程序资源文件中,经过编译连接生成
EXE
文件,实现无
.WAV
文件的声音播放。

  要实现作为资源的声音文件的播放,首先要在资源管理器中加入待播放的声音文件(实现过程并不复杂,这里不在叙述)。假设生成的声音文件资源标识符为
IDR_WAVE1
。在播放时只需要调用下面的语句:

  
PlaySound(MAKEINTRESOURCE(IDR_WAVE1),AfxGetResourceHandle(),
  
SND_ASYNC|SND_RESOURCE|SND_NODEFAULT|SND_LOOP);
  其中
MAKEINTRESOURCE()
宏将整数资源标识符转变为字符串,
AfxGetResourceHandle()
函数返回包含资源的模块句柄,

SND_RESOURCE
是必须的标志。

  作为资源的声音文件的第二种播放方法是把资源读入内存后作为内存数据播放。具体步骤入下:

  
1
.获得包含资源的模块句柄:

  
HMODULE hmod=AfxGetResourceHandle();
  
2
.检索资源块信息:

  
HRSRC hSndResource=FindResource(hmod,MAKEINTRESOURCE(IDR_WAVE1),_T("WAVE"));
  
3.
装载资源数据并加锁:

  
HGLOBAL hGlobalMem=LoadResource(hmod,hSndResource);
LPCTSTR lpMemSound=(LPCSTR)LockResource(hGlobalMem);
  
4
.播放声音文件:

  
sndPlaySound(lpMemSound,SND_MEMORY))

  
5
.释放资源句柄:

  
FreeResource(hGlobalMem);

三.播放声音文件的高级方法

  在
VC++
中提供了一组对音频设备及多媒体文件直接进行操作的函数。利用这些函数可以灵活地对声音文件进行各种处理。

  首先介绍几个要用到的数据结构。
WAVEFORMATEX
结构定义了
WAVE
音频数据文件的格式。
WAVEHDR
结构定义了波形音频缓冲区。读出的数据首先要填充此缓冲区才能送音频设备播放。
WAVEOUTCAPS
结构描述了音频设备的性能。
MMCKINFO
结构包含了
RIFF
文件中一个块的信息。详细的说明请参考
VC++
中的帮助。

  下面给出程序流程简图及程序源代码清单,在
VC++
环境下可直接使用:



源程序清单如下:

LPSTR szFileName;//
声音文件名

MMCKINFO mmckinfoParent;
MMCKINFO mmckinfoSubChunk;
DWORD dwFmtSize;
HMMIO m_hmmio;//
音频文件句柄

DWORD m_WaveLong;
HPSTR lpData;//
音频数据

HANDLE m_hData;
HANDLE m_hFormat;
WAVEFORMATEX * lpFormat;
DWORD m_dwDataOffset;
DWORD m_dwDataSize;
WAVEHDR pWaveOutHdr;
WAVEOUTCAPS pwoc;
HWAVEOUT hWaveOut;
//
打开波形文件

if(!(m_hmmio=mmioOpen(szFileName,NULL,MMIO_READ|MMIO_ALLOCBUF)))
{
//File open Error
Error("Failed to open the file.");//
错误处理函数

return false;
}
//
检查打开文件是否是声音文件

mmckinfoParent.fccType =mmioFOURCC(’W’,’A’,’V’,’E’);
if(mmioDescend(m_hmmio,(LPMMCKINFO)&mmckinfoParent,NULL,MMIO_FINDRIFF))
{
//NOT WAVE FILE AND QUIT
}
//
寻找
’fmt’

mmckinfoSubChunk.ckid =mmioFOURCC(’f’,’m’,’t’,’ ’);
if(mmioDescend(m_hmmio,&mmckinfoSubChunk,&mmckinfoParent,MMIO_FINDCHUNK))
{
//Can’t find ’fmt’ chunk
}
//
获得
’fmt ’
块的大小,申请内存

dwFmtSize=mmckinfoSubChunk.cksize ;
m_hFormat=LocalAlloc(LMEM_MOVEABLE,LOWORD(dwFmtSize));
if(!m_hFormat)
{
//failed alloc memory
}
lpFormat=(WAVEFORMATEX*)LocalLock(m_hFormat);
if(!lpFormat)
{
//failed to lock the memory
}
if((unsigned long)mmioRead(m_hmmio,(HPSTR)lpFormat,dwFmtSize)!=dwFmtSize)
{
//failed to read format chunk
}
//
离开
fmt

mmioAscend(m_hmmio,&mmckinfoSubChunk,0);
//
寻找
’data’

mmckinfoSubChunk.ckid=mmioFOURCC(’d’,’a’,’t’,’a’);
if(mmioDescend(m_hmmio,&mmckinfoSubChunk,&mmckinfoParent,MMIO_FINDCHUNK))
{
//Can’t find ’data’ chunk
}
//
获得
’data’
块的大小

m_dwDataSize=mmckinfoSubChunk.cksize ;
m_dwDataOffset =mmckinfoSubChunk.dwDataOffset ;
if(m_dwDataSize==0L)
{
//no data in the ’data’ chunk
}
//
为音频数据分配内存

lpData=new char[m_dwDataSize];
if(!lpData)
{
//faile
}
if(mmioSeek(m_hmmio,SoundOffset,SEEK_SET)<0)
{
//Failed to read the data chunk
}
m_WaveLong=mmioRead(m_hmmio,lpData,SoundLong);
if(m_WaveLong<0)
{
//Failed to read the data chunk
}
//
检查音频设备,返回音频输出设备的性能

if(waveOutGetDeVCaps(WAVE_MAPPER,&pwoc,sizeof(WAVEOUTCAPS))!=0)
{
//Unable to allocate or lock memory
}
//
检查音频输出设备是否能播放指定的音频文件

if(waveOutOpen(&hWaveOut,DevsNum,lpFormat,NULL,NULL,CALLBACK_NULL)!=0)
{
//Failed to OPEN the wave out devices
}
//
准备待播放的数据

pWaveOutHdr.lpData =(HPSTR)lpData;
pWaveOutHdr.dwBufferLength =m_WaveLong;
pWaveOutHdr.dwFlags =0;
if(waveOutPrepareHeader(hWaveOut,&pWaveOutHdr,sizeof(WAVEHDR))!=0)
{
//Failed to prepare the wave data buffer
}
//
播放音频数据文件

if(waveOutWrite(hWaveOut,&pWaveOutHdr,sizeof(WAVEHDR))!=0)
{
//Failed to write the wave data buffer
}
//
关闭音频输出设备
,
释放内存

waveOutReset(hWaveOut);
waveOutClose(hWaveOut);
LocalUnlock(m_hFormat);
LocalFree(m_hFormat);
delete [] lpData;
说明:
1
)以上使用的音频设备和声音文件操作函数的声明包含在
mmsystem.h
头文件中,因此在程序中必须用
#include "mmsystem.h"
语句加入头文件。同时在编译时要加入动态连接导入库
winmm.lib
,具体实现方法是从
Developer Studio
Project
菜单中选择
Settings,
然后在
Link
选项卡上的
Object/Library Modules
控制中加入
winmm.lib
2
)在
pWaveOutHdr.lpData
中指定不同的数据,可以播放音频数据文件中任意指定位置的声音。
3)
以上程序均在
VC++6.0
中调试通过,在文中省略了对错误及异常情况的处理,在实际应用中必须加入。

四.结论

VC++
中可以根据应用需要采用不同的方法播放声音文件。简单应用可以直接调用声音播放函数。第二种方法可以把声音作为资源加入可执行文件中。如果在播放之前要对声音数据进行处理,可用第三种方法。


参考书目:

1.
Paul Perry
陈向群
等译《多媒体开发指南》
清华大学出版社

2.
Peter Norton, Rob McGregor
孙凤英
等译《
MFC
开发
Windows95/NT4
应用程序》
清华大学出版社
1998
3.
周敬利
《多媒体声卡技术及应用》
电子工业出版社
1998
0 0
原创粉丝点击