﻿<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>学习日记 &#187; Windows api</title>
	<atom:link href="https://www.softwareace.cn/?cat=21&#038;feed=rss2" rel="self" type="application/rss+xml" />
	<link>https://www.softwareace.cn</link>
	<description>时刻想着为自己的产品多做一些对他好的事情</description>
	<lastBuildDate>Fri, 20 Mar 2026 06:58:28 +0000</lastBuildDate>
	<language>zh-CN</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	
	<item>
		<title>让EXE导出函数</title>
		<link>https://www.softwareace.cn/?p=1687</link>
		<comments>https://www.softwareace.cn/?p=1687#comments</comments>
		<pubDate>Fri, 18 Aug 2017 01:09:57 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[Windows api]]></category>

		<guid isPermaLink="false">http://www.softwareace.cn/?p=1687</guid>
		<description><![CDATA[[crayon-69e22ec645b92517697573/] http://bbs.pediy.com/t [&#8230;]]]></description>
				<content:encoded><![CDATA[<p></p><pre class="crayon-plain-tag">初步搞定。

问题来源：
偶然发现OllyDBG.exe导出了一堆函数，这些函数都是供其插件调用的。对这种体系结构很感
兴趣，想弄清楚它的实现原理。后来又看到梁肇新的书《编程高手箴言》第278页提到的调用
门，觉得都应该差不多。

三种不同的解决办法（原理可能是一样的，:)）：

1）在导出函数声明之前加上__declspec(dllexport)。例：
__declspec(dllexport) int Add(int a, int b);
__declspec(dllexport) int Sub(int a, int b);
__declspec(dllexport) int Mul(int a, int b);
__declspec(dllexport) int Div(int a, int b);

2）在链接器参数中设置。例：
#pragma comment(linker, "/EXPORT:_Add,@1,NONAME")
#pragma comment(linker, "/EXPORT:_Sub,@2,NONAME")
#pragma comment(linker, "/EXPORT:_Mul,@3,NONAME")
#pragma comment(linker, "/EXPORT:_Div,@4,NONAME")

3）添加一个def文件，例：
EXPORTS
        Add       @1  NONAME
        Sub       @2  NONAME
        Mul       @3  NONAME
        Div       @4  NONAME
另需要在链接器命令行参数中指定def文件名：
/DEF:Callee.def
注意：在def文件中不要有
LIBRARY [library][BASE=address]
这样的语句。

相比较而言，后两种方法可以设置更多的参数。

函数举例：

extern "C" 
{

        int Add(int a, int b)
        {
                return (a + b);
        }

        int Sub(int a, int b)
        {
                return (a - b);
        }

        int Mul(int a, int b)
        {
                return (a * b);
        }

        int Div(int a, int b)
        {
                if (b == 0)
                        return 0;
                else
                        return (a / b);
        }

}

编译时会自动生成相应的导出库（lib）文件，供调用者使用。

调用方法和普通的动态链接库调用一样。
调用者必须能够找到被调用者的位置，否则报错，被调用者是否运行不影响。

调用代码举例：

extern "C"
{
        int Add(int a, int b);
        int Sub(int a, int b);
        int Mul(int a, int b);
        int Div(int a, int b);
}

#pragma comment (lib, "Callee.lib")

void CCallerDlg::OnBnClickedCalculate()
{
        // TODO: Add your control notification handler code here
        UpdateData(TRUE);

        switch (((CComboBox *)GetDlgItem(IDC_COMBO_OPERATOR))-&gt;GetCurSel())
        {
        case ADD:
                {
                        m_iResult = Add(m_iNum1, m_iNum2);
                        break;
                }
        case SUB:
                {
                        m_iResult = Sub(m_iNum1, m_iNum2);
                        break;
                }
        ...
        ...

我在OD中跟了一下，发现这跟调用动态链接库也差不多。
不过那几个函数被映射到下面的地址处：

003810F0 &gt;  8B4424 08                 mov     eax, dword ptr [esp+8]
003810F4    8B4C24 04                 mov     ecx, dword ptr [esp+4]
003810F8    03C1                      add     eax, ecx
003810FA    C3                        retn
003810FB    CC                        int3
003810FC    CC                        int3
003810FD    CC                        int3
003810FE    CC                        int3
003810FF    CC                        int3
00381100 &gt;  8B4424 04                 mov     eax, dword ptr [esp+4]
00381104    2B4424 08                 sub     eax, dword ptr [esp+8]
00381108    C3                        retn
00381109    CC                        int3
0038110A    CC                        int3
0038110B    CC                        int3
0038110C    CC                        int3
0038110D    CC                        int3
0038110E    CC                        int3
0038110F    CC                        int3
00381110 &gt;  8B4424 04                 mov     eax, dword ptr [esp+4]
00381114    0FAF4424 08               imul    eax, dword ptr [esp+8]
00381119    C3                        retn
0038111A    CC                        int3
0038111B    CC                        int3
0038111C    CC                        int3
0038111D    CC                        int3
0038111E    CC                        int3
0038111F    CC                        int3
00381120 &gt;  8B4C24 08                 mov     ecx, dword ptr [esp+8]
00381124    85C9                      test    ecx, ecx
00381126    75 03                     jnz     short 0038112B
00381128    33C0                      xor     eax, eax
0038112A    C3                        retn

跟常规的动态链接库被映射到高地址处略有不同。
还不知道是什么原因。

结论：
EXE完全可以和DLL一样导出函数，一样被调用。

进一步的工作：
我发现这个例子跟OllyDbg.exe还是有些不同，跟“调用门”的说法也有不同。这里实际上还是
跟DLL差不多的原理。下一步争取实现一个跟OllyDbg.exe差不多的例子。</pre><p></p>
<p>http://bbs.pediy.com/thread-56840.htm</p>
]]></content:encoded>
			<wfw:commentRss>https://www.softwareace.cn/?feed=rss2&#038;p=1687</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>win7下默认显示托盘图标的</title>
		<link>https://www.softwareace.cn/?p=1684</link>
		<comments>https://www.softwareace.cn/?p=1684#comments</comments>
		<pubDate>Thu, 17 Aug 2017 08:55:32 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[Windows api]]></category>

		<guid isPermaLink="false">http://www.softwareace.cn/?p=1684</guid>
		<description><![CDATA[[crayon-69e22ec6464a0271417913/] 之前之后 http://bbs.pediy. [&#8230;]]]></description>
				<content:encoded><![CDATA[<p></p><pre class="crayon-plain-tag">#include "stdafx.h"
 
#include &lt;conio.h&gt;
#include &lt;windows.h&gt;
#include &lt;Shlwapi.h&gt;
 
#pragma comment(lib, "shlwapi.lib")
 
typedef struct tagNOTIFYITEM
{
    PWSTR pszExeName;
    PWSTR pszTip;
    HICON hIcon;
    HWND hWnd;
    DWORD dwPreference;
    UINT uID;
    GUID guidItem;
} NOTIFYITEM, *PNOTIFYITEM;
 
class __declspec(uuid("D782CCBA-AFB0-43F1-94DB-FDA3779EACCB")) INotificationCB : public IUnknown
{
    virtual HRESULT __stdcall Notify (ULONG, NOTIFYITEM *) = 0;
};
 
class __declspec(uuid("FB852B2C-6BAD-4605-9551-F15F87830935")) ITrayNotify : public IUnknown
{
public:
    virtual HRESULT __stdcall RegisterCallback(INotificationCB* callback) = 0;
    virtual HRESULT __stdcall SetPreference(const NOTIFYITEM* notify_item) = 0;
    virtual HRESULT __stdcall EnableAutoTray(BOOL enabled) = 0;
};
 
class __declspec(uuid("D133CE13-3537-48BA-93A7-AFCD5D2053B4")) ITrayNotifyWindows8 : public IUnknown
{
public:
    virtual HRESULT __stdcall RegisterCallback(INotificationCB* callback, unsigned long*) = 0;
    virtual HRESULT __stdcall UnregisterCallback(unsigned long*) = 0;
    virtual HRESULT __stdcall SetPreference(NOTIFYITEM const*) = 0;
    virtual HRESULT __stdcall EnableAutoTray(BOOL) = 0;
    virtual HRESULT __stdcall DoAction(BOOL) = 0;
};
 
const CLSID CLSID_TrayNotify = {0x25DEAD04, 0x1EAC, 0x4911, {0x9E, 0x3A, 0xAD, 0x0A, 0x4A, 0xB5, 0x60, 0xFD}};
 
class NotificationMgr : public INotificationCB
{
public:
    NotificationMgr(){
        m_pTrayNotify = NULL;
    }
 
    HRESULT __stdcall QueryInterface (
        REFIID riid,
        PVOID *ppv)
    {
        if (ppv == NULL)
            return E_POINTER;
 
        if (riid == __uuidof (INotificationCB)) {
            *ppv = (INotificationCB *) this;
        }else if (riid == IID_IUnknown) {
            *ppv = (IUnknown *) this;
        }else{
            return E_NOINTERFACE;
        }
 
        ((IUnknown *) *ppv) -&gt; AddRef ();
 
        return S_OK;
    }
 
    ULONG __stdcall AddRef (VOID)
    {
        return 1;
    }
 
    ULONG __stdcall Release (VOID)
    {
        return 1;
    }
    HRESULT __stdcall Notify (ULONG Event, NOTIFYITEM * NotifyItem){
        printf("event:%d Preference:%d id:%d path:%ls\n", Event, NotifyItem-&gt;dwPreference, NotifyItem-&gt;uID, NotifyItem-&gt;pszExeName);
        if (StrStrIW(NotifyItem-&gt;pszExeName, L"qq.exe")){
            if (m_pTrayNotify){
                NotifyItem-&gt;dwPreference = 2;
                m_pTrayNotify-&gt;SetPreference(NotifyItem);
            }
        }
        return S_OK;
    }
 
public:
    ITrayNotify * m_pTrayNotify;
};
 
int _tmain(int argc, _TCHAR* argv[])
{
    ITrayNotify *pTrayNotify;
    NOTIFYITEM NotifyItem = {0};
    NotificationMgr NotiMgr;
 
    CoInitialize(NULL);
 
    //
    // for win7
    //
     
    HRESULT hr = CoCreateInstance (
       CLSID_TrayNotify,
       NULL,
       CLSCTX_LOCAL_SERVER,
       __uuidof(ITrayNotify),
       (PVOID *)&amp;pTrayNotify);
     
    if (!SUCCEEDED(hr)){
        printf("create instance of ITrayNotify error\n");
        return -1;
    }
     
    //
    // register callback
    //
     
    NotiMgr.m_pTrayNotify = pTrayNotify;
    hr = pTrayNotify-&gt;RegisterCallback(&amp;NotiMgr);
     
    printf("over\n");
 
    pTrayNotify-&gt;RegisterCallback(NULL);
    pTrayNotify-&gt;Release();
     
    _getch();
 
    return 0;
}</pre><p><span style="color: #0b151d;">之前</span><br style="color: #0b151d;" /><img style="color: #0b151d;" src="http://bbs.pediy.com/upload/attach/201507/240026_2ep5sqn0kpgycrc.png" alt="" width="468" height="436" /><br style="color: #0b151d;" /><br style="color: #0b151d;" /><span style="color: #0b151d;">之后</span><br style="color: #0b151d;" /><img style="color: #0b151d;" src="http://bbs.pediy.com/upload/attach/201507/240026_4lh79s9ykutn8oz.png" alt="" width="524" height="389" /></p>
<p>http://bbs.pediy.com/thread-202658.htm</p>
]]></content:encoded>
			<wfw:commentRss>https://www.softwareace.cn/?feed=rss2&#038;p=1684</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>如何获取可执行文件的 Authenticode 签名信息</title>
		<link>https://www.softwareace.cn/?p=1479</link>
		<comments>https://www.softwareace.cn/?p=1479#comments</comments>
		<pubDate>Wed, 03 Aug 2016 02:39:25 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[Windows api]]></category>

		<guid isPermaLink="false">http://www.softwareace.cn/?p=1479</guid>
		<description><![CDATA[https://support.microsoft.com/zh-cn/kb/323809 [crayon-6 [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>https://support.microsoft.com/zh-cn/kb/323809</p>
<p></p><pre class="crayon-plain-tag">#include &lt;windows.h&gt;
#include &lt;wincrypt.h&gt;
#include &lt;wintrust.h&gt;
#include &lt;stdio.h&gt;
#include &lt;tchar.h&gt;

#pragma comment(lib, "crypt32.lib")

#define ENCODING (X509_ASN_ENCODING | PKCS_7_ASN_ENCODING)

typedef struct {
    LPWSTR lpszProgramName;
    LPWSTR lpszPublisherLink;
    LPWSTR lpszMoreInfoLink;
} SPROG_PUBLISHERINFO, *PSPROG_PUBLISHERINFO;

BOOL GetProgAndPublisherInfo(PCMSG_SIGNER_INFO pSignerInfo,
                             PSPROG_PUBLISHERINFO Info);
BOOL GetDateOfTimeStamp(PCMSG_SIGNER_INFO pSignerInfo, SYSTEMTIME *st);
BOOL PrintCertificateInfo(PCCERT_CONTEXT pCertContext);
BOOL GetTimeStampSignerInfo(PCMSG_SIGNER_INFO pSignerInfo,
                            PCMSG_SIGNER_INFO *pCounterSignerInfo);

int _tmain(int argc, TCHAR *argv[])
{
    WCHAR szFileName[MAX_PATH]; 
    HCERTSTORE hStore = NULL;
    HCRYPTMSG hMsg = NULL; 
    PCCERT_CONTEXT pCertContext = NULL;
    BOOL fResult;   
    DWORD dwEncoding, dwContentType, dwFormatType;
    PCMSG_SIGNER_INFO pSignerInfo = NULL;
    PCMSG_SIGNER_INFO pCounterSignerInfo = NULL;
    DWORD dwSignerInfo;
    CERT_INFO CertInfo;     
    SPROG_PUBLISHERINFO ProgPubInfo;
    SYSTEMTIME st;

    ZeroMemory(&amp;ProgPubInfo, sizeof(ProgPubInfo));
    __try
    {
        if (argc != 2)
        {
            _tprintf(_T("Usage: SignedFileInfo &lt;filename&gt;\n"));
            return 0;
        }

#ifdef UNICODE
        lstrcpynW(szFileName, argv[1], MAX_PATH);
#else
        if (mbstowcs(szFileName, argv[1], MAX_PATH) == -1)
        {
            printf("Unable to convert to unicode.\n");
            __leave;
        }
#endif

        // Get message handle and store handle from the signed file.
        fResult = CryptQueryObject(CERT_QUERY_OBJECT_FILE,
                                   szFileName,
                                   CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED,
                                   CERT_QUERY_FORMAT_FLAG_BINARY,
                                   0,
                                   &amp;dwEncoding,
                                   &amp;dwContentType,
                                   &amp;dwFormatType,
                                   &amp;hStore,
                                   &amp;hMsg,
                                   NULL);
        if (!fResult)
        {
            _tprintf(_T("CryptQueryObject failed with %x\n"), GetLastError());
            __leave;
        }

        // Get signer information size.
        fResult = CryptMsgGetParam(hMsg, 
                                   CMSG_SIGNER_INFO_PARAM, 
                                   0, 
                                   NULL, 
                                   &amp;dwSignerInfo);
        if (!fResult)
        {
            _tprintf(_T("CryptMsgGetParam failed with %x\n"), GetLastError());
            __leave;
        }

        // Allocate memory for signer information.
        pSignerInfo = (PCMSG_SIGNER_INFO)LocalAlloc(LPTR, dwSignerInfo);
        if (!pSignerInfo)
        {
            _tprintf(_T("Unable to allocate memory for Signer Info.\n"));
            __leave;
        }

        // Get Signer Information.
        fResult = CryptMsgGetParam(hMsg, 
                                   CMSG_SIGNER_INFO_PARAM, 
                                   0, 
                                   (PVOID)pSignerInfo, 
                                   &amp;dwSignerInfo);
        if (!fResult)
        {
            _tprintf(_T("CryptMsgGetParam failed with %x\n"), GetLastError());
            __leave;
        }
        
        // Get program name and publisher information from 
        // signer info structure.
        if (GetProgAndPublisherInfo(pSignerInfo, &amp;ProgPubInfo))
        {
            if (ProgPubInfo.lpszProgramName != NULL)
            {
                wprintf(L"Program Name : %s\n",
                    ProgPubInfo.lpszProgramName);
            }

            if (ProgPubInfo.lpszPublisherLink != NULL)
            {
                wprintf(L"Publisher Link : %s\n",
                    ProgPubInfo.lpszPublisherLink);
            }

            if (ProgPubInfo.lpszMoreInfoLink != NULL)
            {
                wprintf(L"MoreInfo Link : %s\n",
                    ProgPubInfo.lpszMoreInfoLink);
            }
        }

        _tprintf(_T("\n"));

        // Search for the signer certificate in the temporary 
        // certificate store.
        CertInfo.Issuer = pSignerInfo-&gt;Issuer;
        CertInfo.SerialNumber = pSignerInfo-&gt;SerialNumber;

        pCertContext = CertFindCertificateInStore(hStore,
                                                  ENCODING,
                                                  0,
                                                  CERT_FIND_SUBJECT_CERT,
                                                  (PVOID)&amp;CertInfo,
                                                  NULL);
        if (!pCertContext)
        {
            _tprintf(_T("CertFindCertificateInStore failed with %x\n"),
                GetLastError());
            __leave;
        }

        // Print Signer certificate information.
        _tprintf(_T("Signer Certificate:\n\n"));        
        PrintCertificateInfo(pCertContext);
        _tprintf(_T("\n"));
        
        // Get the timestamp certificate signerinfo structure.
        if (GetTimeStampSignerInfo(pSignerInfo, &amp;pCounterSignerInfo))
        {
            // Search for Timestamp certificate in the temporary
            // certificate store.
            CertInfo.Issuer = pCounterSignerInfo-&gt;Issuer;
            CertInfo.SerialNumber = pCounterSignerInfo-&gt;SerialNumber;

            pCertContext = CertFindCertificateInStore(hStore,
                                                ENCODING,
                                                0,
                                                CERT_FIND_SUBJECT_CERT,
                                                (PVOID)&amp;CertInfo,
                                                NULL);
            if (!pCertContext)
            {
                _tprintf(_T("CertFindCertificateInStore failed with %x\n"),
                    GetLastError());
                __leave;
            }

            // Print timestamp certificate information.
            _tprintf(_T("TimeStamp Certificate:\n\n"));
            PrintCertificateInfo(pCertContext);
            _tprintf(_T("\n"));

            // Find Date of timestamp.
            if (GetDateOfTimeStamp(pCounterSignerInfo, &amp;st))
            {
                _tprintf(_T("Date of TimeStamp : %02d/%02d/%04d %02d:%02d\n"),
                                            st.wMonth,
                                            st.wDay,
                                            st.wYear,
                                            st.wHour,
                                            st.wMinute);
            }
            _tprintf(_T("\n"));
        }
    }
    __finally
    {               
        // Clean up.
        if (ProgPubInfo.lpszProgramName != NULL)
            LocalFree(ProgPubInfo.lpszProgramName);
        if (ProgPubInfo.lpszPublisherLink != NULL)
            LocalFree(ProgPubInfo.lpszPublisherLink);
        if (ProgPubInfo.lpszMoreInfoLink != NULL)
            LocalFree(ProgPubInfo.lpszMoreInfoLink);

        if (pSignerInfo != NULL) LocalFree(pSignerInfo);
        if (pCounterSignerInfo != NULL) LocalFree(pCounterSignerInfo);
        if (pCertContext != NULL) CertFreeCertificateContext(pCertContext);
        if (hStore != NULL) CertCloseStore(hStore, 0);
        if (hMsg != NULL) CryptMsgClose(hMsg);
    }
    return 0;
}

BOOL PrintCertificateInfo(PCCERT_CONTEXT pCertContext)
{
    BOOL fReturn = FALSE;
    LPTSTR szName = NULL;
    DWORD dwData;

    __try
    {
        // Print Serial Number.
        _tprintf(_T("Serial Number: "));
        dwData = pCertContext-&gt;pCertInfo-&gt;SerialNumber.cbData;
        for (DWORD n = 0; n &lt; dwData; n++)
        {
            _tprintf(_T("%02x "),
              pCertContext-&gt;pCertInfo-&gt;SerialNumber.pbData[dwData - (n + 1)]);
        }
        _tprintf(_T("\n"));

        // Get Issuer name size.
        if (!(dwData = CertGetNameString(pCertContext, 
                                         CERT_NAME_SIMPLE_DISPLAY_TYPE,
                                         CERT_NAME_ISSUER_FLAG,
                                         NULL,
                                         NULL,
                                         0)))
        {
            _tprintf(_T("CertGetNameString failed.\n"));
            __leave;
        }

        // Allocate memory for Issuer name.
        szName = (LPTSTR)LocalAlloc(LPTR, dwData * sizeof(TCHAR));
        if (!szName)
        {
            _tprintf(_T("Unable to allocate memory for issuer name.\n"));
            __leave;
        }

        // Get Issuer name.
        if (!(CertGetNameString(pCertContext, 
                                CERT_NAME_SIMPLE_DISPLAY_TYPE,
                                CERT_NAME_ISSUER_FLAG,
                                NULL,
                                szName,
                                dwData)))
        {
            _tprintf(_T("CertGetNameString failed.\n"));
            __leave;
        }

        // print Issuer name.
        _tprintf(_T("Issuer Name: %s\n"), szName);
        LocalFree(szName);
        szName = NULL;

        // Get Subject name size.
        if (!(dwData = CertGetNameString(pCertContext, 
                                         CERT_NAME_SIMPLE_DISPLAY_TYPE,
                                         0,
                                         NULL,
                                         NULL,
                                         0)))
        {
            _tprintf(_T("CertGetNameString failed.\n"));
            __leave;
        }

        // Allocate memory for subject name.
        szName = (LPTSTR)LocalAlloc(LPTR, dwData * sizeof(TCHAR));
        if (!szName)
        {
            _tprintf(_T("Unable to allocate memory for subject name.\n"));
            __leave;
        }

        // Get subject name.
        if (!(CertGetNameString(pCertContext, 
                                CERT_NAME_SIMPLE_DISPLAY_TYPE,
                                0,
                                NULL,
                                szName,
                                dwData)))
        {
            _tprintf(_T("CertGetNameString failed.\n"));
            __leave;
        }

        // Print Subject Name.
        _tprintf(_T("Subject Name: %s\n"), szName);

        fReturn = TRUE;
    }
    __finally
    {
        if (szName != NULL) LocalFree(szName);
    }

    return fReturn;
}

LPWSTR AllocateAndCopyWideString(LPCWSTR inputString)
{
    LPWSTR outputString = NULL;

    outputString = (LPWSTR)LocalAlloc(LPTR,
        (wcslen(inputString) + 1) * sizeof(WCHAR));
    if (outputString != NULL)
    {
        lstrcpyW(outputString, inputString);
    }
    return outputString;
}

BOOL GetProgAndPublisherInfo(PCMSG_SIGNER_INFO pSignerInfo,
                             PSPROG_PUBLISHERINFO Info)
{
    BOOL fReturn = FALSE;
    PSPC_SP_OPUS_INFO OpusInfo = NULL;  
    DWORD dwData;
    BOOL fResult;
    
    __try
    {
        // Loop through authenticated attributes and find
        // SPC_SP_OPUS_INFO_OBJID OID.
        for (DWORD n = 0; n &lt; pSignerInfo-&gt;AuthAttrs.cAttr; n++)
        {           
            if (lstrcmpA(SPC_SP_OPUS_INFO_OBJID, 
                        pSignerInfo-&gt;AuthAttrs.rgAttr[n].pszObjId) == 0)
            {
                // Get Size of SPC_SP_OPUS_INFO structure.
                fResult = CryptDecodeObject(ENCODING,
                            SPC_SP_OPUS_INFO_OBJID,
                            pSignerInfo-&gt;AuthAttrs.rgAttr[n].rgValue[0].pbData,
                            pSignerInfo-&gt;AuthAttrs.rgAttr[n].rgValue[0].cbData,
                            0,
                            NULL,
                            &amp;dwData);
                if (!fResult)
                {
                    _tprintf(_T("CryptDecodeObject failed with %x\n"),
                        GetLastError());
                    __leave;
                }

                // Allocate memory for SPC_SP_OPUS_INFO structure.
                OpusInfo = (PSPC_SP_OPUS_INFO)LocalAlloc(LPTR, dwData);
                if (!OpusInfo)
                {
                    _tprintf(_T("Unable to allocate memory for Publisher Info.\n"));
                    __leave;
                }

                // Decode and get SPC_SP_OPUS_INFO structure.
                fResult = CryptDecodeObject(ENCODING,
                            SPC_SP_OPUS_INFO_OBJID,
                            pSignerInfo-&gt;AuthAttrs.rgAttr[n].rgValue[0].pbData,
                            pSignerInfo-&gt;AuthAttrs.rgAttr[n].rgValue[0].cbData,
                            0,
                            OpusInfo,
                            &amp;dwData);
                if (!fResult)
                {
                    _tprintf(_T("CryptDecodeObject failed with %x\n"),
                        GetLastError());
                    __leave;
                }

                // Fill in Program Name if present.
                if (OpusInfo-&gt;pwszProgramName)
                {
                    Info-&gt;lpszProgramName =
                        AllocateAndCopyWideString(OpusInfo-&gt;pwszProgramName);
                }
                else
                    Info-&gt;lpszProgramName = NULL;

                // Fill in Publisher Information if present.
                if (OpusInfo-&gt;pPublisherInfo)
                {

                    switch (OpusInfo-&gt;pPublisherInfo-&gt;dwLinkChoice)
                    {
                        case SPC_URL_LINK_CHOICE:
                            Info-&gt;lpszPublisherLink =
                                AllocateAndCopyWideString(OpusInfo-&gt;pPublisherInfo-&gt;pwszUrl);
                            break;

                        case SPC_FILE_LINK_CHOICE:
                            Info-&gt;lpszPublisherLink =
                                AllocateAndCopyWideString(OpusInfo-&gt;pPublisherInfo-&gt;pwszFile);
                            break;

                        default:
                            Info-&gt;lpszPublisherLink = NULL;
                            break;
                    }
                }
                else
                {
                    Info-&gt;lpszPublisherLink = NULL;
                }

                // Fill in More Info if present.
                if (OpusInfo-&gt;pMoreInfo)
                {
                    switch (OpusInfo-&gt;pMoreInfo-&gt;dwLinkChoice)
                    {
                        case SPC_URL_LINK_CHOICE:
                            Info-&gt;lpszMoreInfoLink =
                                AllocateAndCopyWideString(OpusInfo-&gt;pMoreInfo-&gt;pwszUrl);
                            break;

                        case SPC_FILE_LINK_CHOICE:
                            Info-&gt;lpszMoreInfoLink =
                                AllocateAndCopyWideString(OpusInfo-&gt;pMoreInfo-&gt;pwszFile);
                            break;

                        default:
                            Info-&gt;lpszMoreInfoLink = NULL;
                            break;
                    }
                }               
                else
                {
                    Info-&gt;lpszMoreInfoLink = NULL;
                }

                fReturn = TRUE;

                break; // Break from for loop.
            } // lstrcmp SPC_SP_OPUS_INFO_OBJID                 
        } // for 
    }
    __finally
    {
        if (OpusInfo != NULL) LocalFree(OpusInfo);      
    }

    return fReturn;
}

BOOL GetDateOfTimeStamp(PCMSG_SIGNER_INFO pSignerInfo, SYSTEMTIME *st)
{   
    BOOL fResult;
    FILETIME lft, ft;   
    DWORD dwData;
    BOOL fReturn = FALSE;
    
    // Loop through authenticated attributes and find
    // szOID_RSA_signingTime OID.
    for (DWORD n = 0; n &lt; pSignerInfo-&gt;AuthAttrs.cAttr; n++)
    {           
        if (lstrcmpA(szOID_RSA_signingTime, 
                    pSignerInfo-&gt;AuthAttrs.rgAttr[n].pszObjId) == 0)
        {               
            // Decode and get FILETIME structure.
            dwData = sizeof(ft);
            fResult = CryptDecodeObject(ENCODING,
                        szOID_RSA_signingTime,
                        pSignerInfo-&gt;AuthAttrs.rgAttr[n].rgValue[0].pbData,
                        pSignerInfo-&gt;AuthAttrs.rgAttr[n].rgValue[0].cbData,
                        0,
                        (PVOID)&amp;ft,
                        &amp;dwData);
            if (!fResult)
            {
                _tprintf(_T("CryptDecodeObject failed with %x\n"),
                    GetLastError());
                break;
            }

            // Convert to local time.
            FileTimeToLocalFileTime(&amp;ft, &amp;lft);
            FileTimeToSystemTime(&amp;lft, st);

            fReturn = TRUE;

            break; // Break from for loop.
                        
        } //lstrcmp szOID_RSA_signingTime
    } // for 

    return fReturn;
}

BOOL GetTimeStampSignerInfo(PCMSG_SIGNER_INFO pSignerInfo, PCMSG_SIGNER_INFO *pCounterSignerInfo)
{   
    PCCERT_CONTEXT pCertContext = NULL;
    BOOL fReturn = FALSE;
    BOOL fResult;       
    DWORD dwSize;   

    __try
    {
        *pCounterSignerInfo = NULL;

        // Loop through unathenticated attributes for
        // szOID_RSA_counterSign OID.
        for (DWORD n = 0; n &lt; pSignerInfo-&gt;UnauthAttrs.cAttr; n++)
        {
            if (lstrcmpA(pSignerInfo-&gt;UnauthAttrs.rgAttr[n].pszObjId, 
                         szOID_RSA_counterSign) == 0)
            {
                // Get size of CMSG_SIGNER_INFO structure.
                fResult = CryptDecodeObject(ENCODING,
                           PKCS7_SIGNER_INFO,
                           pSignerInfo-&gt;UnauthAttrs.rgAttr[n].rgValue[0].pbData,
                           pSignerInfo-&gt;UnauthAttrs.rgAttr[n].rgValue[0].cbData,
                           0,
                           NULL,
                           &amp;dwSize);
                if (!fResult)
                {
                    _tprintf(_T("CryptDecodeObject failed with %x\n"),
                        GetLastError());
                    __leave;
                }

                // Allocate memory for CMSG_SIGNER_INFO.
                *pCounterSignerInfo = (PCMSG_SIGNER_INFO)LocalAlloc(LPTR, dwSize);
                if (!*pCounterSignerInfo)
                {
                    _tprintf(_T("Unable to allocate memory for timestamp info.\n"));
                    __leave;
                }

                // Decode and get CMSG_SIGNER_INFO structure
                // for timestamp certificate.
                fResult = CryptDecodeObject(ENCODING,
                           PKCS7_SIGNER_INFO,
                           pSignerInfo-&gt;UnauthAttrs.rgAttr[n].rgValue[0].pbData,
                           pSignerInfo-&gt;UnauthAttrs.rgAttr[n].rgValue[0].cbData,
                           0,
                           (PVOID)*pCounterSignerInfo,
                           &amp;dwSize);
                if (!fResult)
                {
                    _tprintf(_T("CryptDecodeObject failed with %x\n"),
                        GetLastError());
                    __leave;
                }

                fReturn = TRUE;
                
                break; // Break from for loop.
            }           
        }
    }
    __finally
    {
        // Clean up.
        if (pCertContext != NULL) CertFreeCertificateContext(pCertContext);
    }

    return fReturn;
}</pre><p>&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>https://www.softwareace.cn/?feed=rss2&#038;p=1479</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>刷新桌面图标缓存</title>
		<link>https://www.softwareace.cn/?p=1405</link>
		<comments>https://www.softwareace.cn/?p=1405#comments</comments>
		<pubDate>Tue, 13 Oct 2015 03:43:53 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[Windows api]]></category>

		<guid isPermaLink="false">http://www.softwareace.cn/?p=1405</guid>
		<description><![CDATA[[crayon-69e22ec646adf648024602/] &#160;]]></description>
				<content:encoded><![CDATA[<p></p><pre class="crayon-plain-tag">SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, 0, 0 );</pre><p>&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>https://www.softwareace.cn/?feed=rss2&#038;p=1405</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>无法解析的外部符号  [error LNK2019: 无法解析的外部符号 _closesocket@4] ——winsock.h相关</title>
		<link>https://www.softwareace.cn/?p=1332</link>
		<comments>https://www.softwareace.cn/?p=1332#comments</comments>
		<pubDate>Mon, 25 May 2015 09:25:45 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[Windows api]]></category>

		<guid isPermaLink="false">http://www.softwareace.cn/?p=1332</guid>
		<description><![CDATA[#pragma comment(lib, &#8220;Wsock32.lib&#8221;)]]></description>
				<content:encoded><![CDATA[<p>#pragma comment(lib, &#8220;Wsock32.lib&#8221;)</p>
]]></content:encoded>
			<wfw:commentRss>https://www.softwareace.cn/?feed=rss2&#038;p=1332</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>查询端口是否被占用，并找出占用端口的进程</title>
		<link>https://www.softwareace.cn/?p=1141</link>
		<comments>https://www.softwareace.cn/?p=1141#comments</comments>
		<pubDate>Thu, 29 Jan 2015 01:18:01 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[Windows api]]></category>

		<guid isPermaLink="false">http://www.softwareace.cn/?p=1141</guid>
		<description><![CDATA[windows下的一个查询端口是否被占用的函数，若端口被占用，则输出占用该端口的进程。 [crayon-69e [&#8230;]]]></description>
				<content:encoded><![CDATA[<p><span style="color: #000000;">windows下的一个查询端口是否被占用的函数，若端口被占用，则输出占用该端口的进程。</span></p><pre class="crayon-plain-tag">bool CheckPortState( IN unsigned num)
{
    PMIB_TCPTABLE_OWNER_PID pTcpTable;
    pTcpTable = new MIB_TCPTABLE_OWNER_PID;
     
    //获取所需要的内存大小
    DWORD tmpSize = sizeof(MIB_TCPTABLE_OWNER_PID); 
    GetExtendedTcpTable( pTcpTable, &amp;tmpSize,false , AF_INET,  TCP_TABLE_OWNER_PID_ALL, 0);
 
    //分配足够大小的内存并获取端口信息
    DWORD dwSize = tmpSize/sizeof(MIB_TCPTABLE_OWNER_PID);
    delete pTcpTable;
    pTcpTable = NULL;
    pTcpTable = new MIB_TCPTABLE_OWNER_PID[dwSize];
    GetExtendedTcpTable( pTcpTable, &amp;tmpSize, true, AF_INET,  TCP_TABLE_OWNER_PID_ALL, 0);
 
    //判断端口是否被占用，并找出占用端口的进程，对于某些system权限的进程需要提权
    for (int i = 0; i &lt; (int) pTcpTable-&gt;dwNumEntries; i++) {
        if ( num == ntohs( (u_short) pTcpTable-&gt;table[i].dwLocalPort ) )
        {
            HANDLE provileges = NULL;
            LUID Luid;
            //提权操作
            if ( !OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES| TOKEN_QUERY, &amp;provileges) )
            {
                long res = GetLastError();
                cout&lt;&lt;"error code "&lt;&lt;res&lt;&lt;endl;
                if (pTcpTable != NULL)
                {
                    delete []pTcpTable;
                    pTcpTable = NULL;
                }
                return false;
            }
 
            if (!LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&amp;Luid))
            {
                cout&lt;&lt;"LookupPrivilegeValue err!"&lt;&lt;endl;
                if (pTcpTable != NULL)
                {
                    delete []pTcpTable;
                    pTcpTable = NULL;
                }
                return false;
            }
 
            TOKEN_PRIVILEGES tp;
            tp.PrivilegeCount=1;
            tp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;
            tp.Privileges[0].Luid=Luid;
 
            if (!AdjustTokenPrivileges(provileges,0,&amp;tp,sizeof(TOKEN_PRIVILEGES),NULL,NULL))
            {
                cout&lt;&lt;"AdjustTokenPrivileges err!"&lt;&lt;endl;
                if (pTcpTable != NULL)
                {
                    delete []pTcpTable;
                    pTcpTable = NULL;
                }
                return false;
            }
 
            HANDLE hProcess = OpenProcess( PROCESS_ALL_ACCESS, false, pTcpTable-&gt;table[i].dwOwningPid);
            if ( hProcess == NULL )
            {
                long res = GetLastError();
                cout&lt;&lt;"error code "&lt;&lt;res&lt;&lt;endl;
                if (pTcpTable != NULL)
                {
                    delete []pTcpTable;
                    pTcpTable = NULL;
                }
                return false;
            }
            wchar_t wsProcessName[MAX_PATH + 1] = {0};
            DWORD len = MAX_PATH;
            if ( QueryFullProcessImageName(hProcess, 0, wsProcessName, &amp;len) )
            {
                wcout&lt;&lt;L"Port["&lt;&lt;num&lt;&lt;L"] is occupied "&lt;&lt;L"by process["&lt;&lt;wsProcessName&lt;&lt;L"] PID["
                    &lt;&lt;pTcpTable-&gt;table[i].dwOwningPid&lt;&lt;L"]"&lt;&lt;endl;
                CloseHandle(hProcess);
            }
            else
            {
                CloseHandle(hProcess);
                hProcess = NULL;
                if (pTcpTable != NULL)
                {
                    delete []pTcpTable;
                    pTcpTable = NULL;
                }
                return false;
            }
        }
    }
 
    if (pTcpTable != NULL)
    {
        delete []pTcpTable;
        pTcpTable = NULL;
    }
 
    return true;
}</pre><p>&nbsp;</p><pre class="crayon-plain-tag">bool CheckUdpPortState(IN unsigned num )
{
    PMIB_UDPTABLE_OWNER_PID pUdpTable;
    pUdpTable = new MIB_UDPTABLE_OWNER_PID;
 
    //获取所需要的内存大小
    DWORD tmpSize = sizeof(MIB_UDPTABLE_OWNER_PID); 
    GetExtendedUdpTable( pUdpTable, &amp;tmpSize,false , AF_INET,  UDP_TABLE_OWNER_PID, 0);
 
    //分配足够大小的内存并获取端口信息
    DWORD dwSize = tmpSize/sizeof(MIB_UDPTABLE_OWNER_PID);
    delete pUdpTable;
    pUdpTable = NULL;
    pUdpTable = new MIB_UDPTABLE_OWNER_PID[dwSize];
    GetExtendedUdpTable( pUdpTable, &amp;tmpSize, true, AF_INET,  UDP_TABLE_OWNER_PID, 0);
 
    HANDLE provileges = NULL;
    LUID Luid;
    //提权操作
    if ( !OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES| TOKEN_QUERY, &amp;provileges) )
    {
        long res = GetLastError();
        cout&lt;&lt;"error code "&lt;&lt;res&lt;&lt;endl;
        if (pUdpTable != NULL)
        {
            delete []pUdpTable;
            pUdpTable = NULL;
        }
        return false;
    }
 
    if (!LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&amp;Luid))
    {
        cout&lt;&lt;"LookupPrivilegeValue err!"&lt;&lt;endl;
        if (pUdpTable != NULL)
        {
            delete []pUdpTable;
            pUdpTable = NULL;
        }
        return false;
    }
 
    TOKEN_PRIVILEGES tp;
    tp.PrivilegeCount=1;
    tp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;
    tp.Privileges[0].Luid=Luid;
 
    if (!AdjustTokenPrivileges(provileges,0,&amp;tp,sizeof(TOKEN_PRIVILEGES),NULL,NULL))
    {
        cout&lt;&lt;"AdjustTokenPrivileges err!"&lt;&lt;endl;
        if (pUdpTable != NULL)
        {
            delete []pUdpTable;
            pUdpTable = NULL;
        }
        return false;
    }
 
    //判断端口是否被占用，并找出占用端口的进程，对于某些system权限的进程需要提权
    for (int i = 0; i &lt; (int) pUdpTable-&gt;dwNumEntries; i++) {
        if ( num == ntohs( (u_short) pUdpTable-&gt;table[i].dwLocalPort ) )
        {
            HANDLE hProcess = OpenProcess( PROCESS_ALL_ACCESS, false, pUdpTable-&gt;table[i].dwOwningPid);
            if ( hProcess == NULL )
            {
                long res = GetLastError();
                cout&lt;&lt;"error code "&lt;&lt;res&lt;&lt;endl;
                if (pUdpTable != NULL)
                {
                    delete []pUdpTable;
                    pUdpTable = NULL;
                }
                return false;
            }
            wchar_t wsProcessName[MAX_PATH + 1] = {0};
            DWORD len = MAX_PATH;
            if ( QueryFullProcessImageName(hProcess, 0, wsProcessName, &amp;len) )
            {
                wcout&lt;&lt;L"Port["&lt;&lt;num&lt;&lt;L"] is occupied "&lt;&lt;L"by process["&lt;&lt;wsProcessName&lt;&lt;L"] PID["
                    &lt;&lt;pUdpTable-&gt;table[i].dwOwningPid&lt;&lt;L"]"&lt;&lt;endl;
                CloseHandle(hProcess);
            }
            else
            {
                CloseHandle(hProcess);
                hProcess = NULL;
                if (pUdpTable != NULL)
                {
                    delete []pUdpTable;
                    pUdpTable = NULL;
                }
                return false;
            }
        }
    }
 
    if (pUdpTable != NULL)
    {
        delete []pUdpTable;
        pUdpTable = NULL;
    }
 
    return true;
}</pre><p>&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>https://www.softwareace.cn/?feed=rss2&#038;p=1141</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>计算CPU占用率 windows</title>
		<link>https://www.softwareace.cn/?p=1127</link>
		<comments>https://www.softwareace.cn/?p=1127#comments</comments>
		<pubDate>Wed, 28 Jan 2015 04:47:03 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[Windows api]]></category>

		<guid isPermaLink="false">http://www.softwareace.cn/?p=1127</guid>
		<description><![CDATA[[crayon-69e22ec646f9d117086499/]]]></description>
				<content:encoded><![CDATA[<p></p><pre class="crayon-plain-tag">#include "stdafx.h"
#include &lt;iostream&gt;
#include "CCPUPercent.h"
 
using namespace std;
 
CCPUPercent::CCPUPercent()
{
    getSysTime.Initialization();
}
 
 
bool CCPUPercent::StartCalculateCPUPercent()
{
    //  使用sleep进行间隔控制， 需要更加精确时间可以考虑使用
    //  wait for event
     
    bool bRet = getSysTime.GetSystemTimes(idleTime,kernelTime,userTime);
    if(!bRet)
        return bRet;
 
    preidleTime = idleTime;
    prekernelTime = kernelTime;
    preuserTime = userTime;
 
    while(1)
    {
        Sleep(1000);    
        bRet = getSysTime.GetSystemTimes(idleTime,kernelTime,userTime);
        if(!bRet)
            return bRet;
        LONGLONG idle = getSysTime.CalculateInterval(idleTime,preidleTime);
        LONGLONG kernel = getSysTime.CalculateInterval(kernelTime,prekernelTime);
        LONGLONG user = getSysTime.CalculateInterval( userTime,preuserTime);
     
        int cpu = ((kernel - idle +user)*100) /(kernel+user) ;
        if(kernel &lt; idle)
        {
            cout &lt;&lt; "wrong" &lt;&lt; endl;
        }
        //（总的时间-空闲时间）/总的时间=占用cpu的时间就是使用率
        cout &lt;&lt; "CPU利用率:" &lt;&lt; cpu &lt;&lt; "%" &lt;&lt; endl;
        preidleTime = idleTime;
        prekernelTime = kernelTime;
        preuserTime = userTime ;
     
    }
 
    return true;
}</pre><p><img src="http://static.oschina.net/uploads/code/201501/24141830_1brv.png" alt="" /></p>
]]></content:encoded>
			<wfw:commentRss>https://www.softwareace.cn/?feed=rss2&#038;p=1127</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>虚拟键值表, virtual key code</title>
		<link>https://www.softwareace.cn/?p=1042</link>
		<comments>https://www.softwareace.cn/?p=1042#comments</comments>
		<pubDate>Fri, 21 Nov 2014 02:21:58 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[Windows api]]></category>

		<guid isPermaLink="false">http://www.softwareace.cn/?p=1042</guid>
		<description><![CDATA[Virtual-Key Codes VK_LBUTTON (01) Left mouse button VK_ [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>Virtual-Key Codes</p>
<p>VK_LBUTTON (01)<br />
Left mouse button</p>
<p>VK_RBUTTON (02)<br />
Right mouse button</p>
<p>VK_CANCEL (03)<br />
Control-break processing</p>
<p>VK_MBUTTON (04)<br />
Middle mouse button (three-button mouse)</p>
<p>VK_XBUTTON1 (05)<br />
Windows 2000/XP: X1 mouse button</p>
<p>VK_XBUTTON2 (06)<br />
Windows 2000/XP: X2 mouse button</p>
<p>- (07)<br />
Undefined</p>
<p>VK_BACK (08)<br />
BACKSPACE key</p>
<p>VK_TAB (09)<br />
TAB key</p>
<p>- (0A-0B)<br />
Reserved</p>
<p>VK_CLEAR (0C)<br />
CLEAR key</p>
<p>VK_RETURN (0D)<br />
ENTER key</p>
<p>- (0E-0F)<br />
Undefined</p>
<p>VK_SHIFT (10)<br />
SHIFT key</p>
<p>VK_CONTROL (11)<br />
CTRL key</p>
<p>VK_MENU (12)<br />
ALT key</p>
<p>VK_PAUSE (13)<br />
PAUSE key</p>
<p>VK_CAPITAL (14)<br />
CAPS LOCK key</p>
<p>VK_KANA (15)<br />
Input Method Editor (IME) Kana mode</p>
<p>VK_HANGUEL (15)<br />
IME Hanguel mode (maintained for compatibility; use VK_HANGUL)</p>
<p>VK_HANGUL (15)<br />
IME Hangul mode</p>
<p>- (16)<br />
Undefined</p>
<p>VK_JUNJA (17)<br />
IME Junja mode</p>
<p>VK_FINAL (18)<br />
IME final mode</p>
<p>VK_HANJA (19)<br />
IME Hanja mode</p>
<p>VK_KANJI (19)<br />
IME Kanji mode</p>
<p>- (1A)<br />
Undefined</p>
<p>VK_ESCAPE (1B)<br />
ESC key</p>
<p>VK_CONVERT (1C)<br />
IME convert</p>
<p>VK_NONCONVERT (1D)<br />
IME nonconvert</p>
<p>VK_ACCEPT (1E)<br />
IME accept</p>
<p>VK_MODECHANGE (1F)<br />
IME mode change request</p>
<p>VK_SPACE (20)<br />
SPACEBAR</p>
<p>VK_PRIOR (21)<br />
PAGE UP key</p>
<p>VK_NEXT (22)<br />
PAGE DOWN key</p>
<p>VK_END (23)<br />
END key</p>
<p>VK_HOME (24)<br />
HOME key</p>
<p>VK_LEFT (25)<br />
LEFT ARROW key</p>
<p>VK_UP (26)<br />
UP ARROW key</p>
<p>VK_RIGHT (27)<br />
RIGHT ARROW key</p>
<p>VK_DOWN (28)<br />
DOWN ARROW key</p>
<p>VK_SELECT (29)<br />
SELECT key</p>
<p>VK_PRINT (2A)<br />
PRINT key</p>
<p>VK_EXECUTE (2B)<br />
EXECUTE key</p>
<p>VK_SNAPSHOT (2C)<br />
PRINT SCREEN key</p>
<p>VK_INSERT (2D)<br />
INS key</p>
<p>VK_DELETE (2E)<br />
DEL key</p>
<p>VK_HELP (2F)<br />
HELP key</p>
<p>(30)<br />
0 key</p>
<p>(31)<br />
1 key</p>
<p>(32)<br />
2 key</p>
<p>(33)<br />
3 key</p>
<p>(34)<br />
4 key</p>
<p>(35)<br />
5 key</p>
<p>(36)<br />
6 key</p>
<p>(37)<br />
7 key</p>
<p>(38)<br />
8 key</p>
<p>(39)<br />
9 key</p>
<p>- (3A-40)<br />
Undefined</p>
<p>(41)<br />
A key</p>
<p>(42)<br />
B key</p>
<p>(43)<br />
C key</p>
<p>(44)<br />
D key</p>
<p>(45)<br />
E key</p>
<p>(46)<br />
F key</p>
<p>(47)<br />
G key</p>
<p>(48)<br />
H key</p>
<p>(49)<br />
I key</p>
<p>(4A)<br />
J key</p>
<p>(4B)<br />
K key</p>
<p>(4C)<br />
L key</p>
<p>(4D)<br />
M key</p>
<p>(4E)<br />
N key</p>
<p>(4F)<br />
O key</p>
<p>(50)<br />
P key</p>
<p>(51)<br />
Q key</p>
<p>(52)<br />
R key</p>
<p>(53)<br />
S key</p>
<p>(54)<br />
T key</p>
<p>(55)<br />
U key</p>
<p>(56)<br />
V key</p>
<p>(57)<br />
W key</p>
<p>(58)<br />
X key</p>
<p>(59)<br />
Y key</p>
<p>(5A)<br />
Z key</p>
<p>VK_LWIN (5B)<br />
Left Windows key (Microsoft Natural keyboard)</p>
<p>VK_RWIN (5C)<br />
Right Windows key (Natural keyboard)</p>
<p>VK_APPS (5D)<br />
Applications key (Natural keyboard)</p>
<p>- (5E)<br />
Reserved</p>
<p>VK_SLEEP (5F)<br />
Computer Sleep key</p>
<p>VK_NUMPAD0 (60)<br />
Numeric keypad 0 key</p>
<p>VK_NUMPAD1 (61)<br />
Numeric keypad 1 key</p>
<p>VK_NUMPAD2 (62)<br />
Numeric keypad 2 key</p>
<p>VK_NUMPAD3 (63)<br />
Numeric keypad 3 key</p>
<p>VK_NUMPAD4 (64)<br />
Numeric keypad 4 key</p>
<p>VK_NUMPAD5 (65)<br />
Numeric keypad 5 key</p>
<p>VK_NUMPAD6 (66)<br />
Numeric keypad 6 key</p>
<p>VK_NUMPAD7 (67)<br />
Numeric keypad 7 key</p>
<p>VK_NUMPAD8 (68)<br />
Numeric keypad 8 key</p>
<p>VK_NUMPAD9 (69)<br />
Numeric keypad 9 key</p>
<p>VK_MULTIPLY (6A)<br />
Multiply key</p>
<p>VK_ADD (6B)<br />
Add key</p>
<p>VK_SEPARATOR (6C)<br />
Separator key</p>
<p>VK_SUBTRACT (6D)<br />
Subtract key</p>
<p>VK_DECIMAL (6E)<br />
Decimal key</p>
<p>VK_DIVIDE (6F)<br />
Divide key</p>
<p>VK_F1 (70)<br />
F1 key</p>
<p>VK_F2 (71)<br />
F2 key</p>
<p>VK_F3 (72)<br />
F3 key</p>
<p>VK_F4 (73)<br />
F4 key</p>
<p>VK_F5 (74)<br />
F5 key</p>
<p>VK_F6 (75)<br />
F6 key</p>
<p>VK_F7 (76)<br />
F7 key</p>
<p>VK_F8 (77)<br />
F8 key</p>
<p>VK_F9 (78)<br />
F9 key</p>
<p>VK_F10 (79)<br />
F10 key</p>
<p>VK_F11 (7A)<br />
F11 key</p>
<p>VK_F12 (7B)<br />
F12 key</p>
<p>VK_F13 (7C)<br />
F13 key</p>
<p>VK_F14 (7D)<br />
F14 key</p>
<p>VK_F15 (7E)<br />
F15 key</p>
<p>VK_F16 (7F)<br />
F16 key</p>
<p>VK_F17 (80H)<br />
F17 key</p>
<p>VK_F18 (81H)<br />
F18 key</p>
<p>VK_F19 (82H)<br />
F19 key</p>
<p>VK_F20 (83H)<br />
F20 key</p>
<p>VK_F21 (84H)<br />
F21 key</p>
<p>VK_F22 (85H)<br />
F22 key</p>
<p>VK_F23 (86H)<br />
F23 key</p>
<p>VK_F24 (87H)<br />
F24 key</p>
<p>- (88-8F)<br />
Unassigned</p>
<p>VK_NUMLOCK (90)<br />
NUM LOCK key</p>
<p>VK_SCROLL (91)<br />
SCROLL LOCK key</p>
<p>(92-96)<br />
OEM specific</p>
<p>- (97-9F)<br />
Unassigned</p>
<p>VK_LSHIFT (A0)<br />
Left SHIFT key</p>
<p>VK_RSHIFT (A1)<br />
Right SHIFT key</p>
<p>VK_LCONTROL (A2)<br />
Left CONTROL key</p>
<p>VK_RCONTROL (A3)<br />
Right CONTROL key</p>
<p>VK_LMENU (A4)<br />
Left MENU key</p>
<p>VK_RMENU (A5)<br />
Right MENU key</p>
<p>VK_BROWSER_BACK (A6)<br />
Windows 2000/XP: Browser Back key</p>
<p>VK_BROWSER_FORWARD (A7)<br />
Windows 2000/XP: Browser Forward key</p>
<p>VK_BROWSER_REFRESH (A8)<br />
Windows 2000/XP: Browser Refresh key</p>
<p>VK_BROWSER_STOP (A9)<br />
Windows 2000/XP: Browser Stop key</p>
<p>VK_BROWSER_SEARCH (AA)<br />
Windows 2000/XP: Browser Search key</p>
<p>VK_BROWSER_FAVORITES (AB)<br />
Windows 2000/XP: Browser Favorites key</p>
<p>VK_BROWSER_HOME (AC)<br />
Windows 2000/XP: Browser Start and Home key</p>
<p>VK_VOLUME_MUTE (AD)<br />
Windows 2000/XP: Volume Mute key</p>
<p>VK_VOLUME_DOWN (AE)<br />
Windows 2000/XP: Volume Down key</p>
<p>VK_VOLUME_UP (AF)<br />
Windows 2000/XP: Volume Up key</p>
<p>VK_MEDIA_NEXT_TRACK (B0)<br />
Windows 2000/XP: Next Track key</p>
<p>VK_MEDIA_PREV_TRACK (B1)<br />
Windows 2000/XP: Previous Track key</p>
<p>VK_MEDIA_STOP (B2)<br />
Windows 2000/XP: Stop Media key</p>
<p>VK_MEDIA_PLAY_PAUSE (B3)<br />
Windows 2000/XP: Play/Pause Media key</p>
<p>VK_LAUNCH_MAIL (B4)<br />
Windows 2000/XP: Start Mail key</p>
<p>VK_LAUNCH_MEDIA_SELECT (B5)<br />
Windows 2000/XP: Select Media key</p>
<p>VK_LAUNCH_APP1 (B6)<br />
Windows 2000/XP: Start Application 1 key</p>
<p>VK_LAUNCH_APP2 (B7)<br />
Windows 2000/XP: Start Application 2 key</p>
<p>- (B8-B9)<br />
Reserved</p>
<p>VK_OEM_1 (BA)<br />
Used for miscellaneous characters; it can vary by keyboard.<br />
Windows 2000/XP: For the US standard keyboard, the &#8216;;:&#8217; key<br />
VK_OEM_PLUS (BB)<br />
Windows 2000/XP: For any country/region, the &#8216;+&#8217; key</p>
<p>VK_OEM_COMMA (BC)<br />
Windows 2000/XP: For any country/region, the &#8216;,&#8217; key</p>
<p>VK_OEM_MINUS (BD)<br />
Windows 2000/XP: For any country/region, the &#8216;-&#8217; key</p>
<p>VK_OEM_PERIOD (BE)<br />
Windows 2000/XP: For any country/region, the &#8216;.&#8217; key</p>
<p>VK_OEM_2 (BF)<br />
Used for miscellaneous characters; it can vary by keyboard.<br />
Windows 2000/XP: For the US standard keyboard, the &#8216;/?&#8217; key<br />
VK_OEM_3 (C0)<br />
Used for miscellaneous characters; it can vary by keyboard.<br />
Windows 2000/XP: For the US standard keyboard, the &#8216;`~&#8217; key<br />
- (C1-D7)<br />
Reserved</p>
<p>- (D8-DA)<br />
Unassigned</p>
<p>VK_OEM_4 (DB)<br />
Used for miscellaneous characters; it can vary by keyboard.<br />
Windows 2000/XP: For the US standard keyboard, the &#8216;[{' key<br />
VK_OEM_5 (DC)<br />
Used for miscellaneous characters; it can vary by keyboard.<br />
Windows 2000/XP: For the US standard keyboard, the '/|' key<br />
VK_OEM_6 (DD)<br />
Used for miscellaneous characters; it can vary by keyboard.<br />
Windows 2000/XP: For the US standard keyboard, the ']}&#8217; key<br />
VK_OEM_7 (DE)<br />
Used for miscellaneous characters; it can vary by keyboard.<br />
Windows 2000/XP: For the US standard keyboard, the &#8216;single-quote/double-quote&#8217; key<br />
VK_OEM_8 (DF)<br />
Used for miscellaneous characters; it can vary by keyboard.</p>
<p>- (E0)<br />
Reserved</p>
<p>(E1)<br />
OEM specific</p>
<p>VK_OEM_102 (E2)<br />
Windows 2000/XP: Either the angle bracket key or the backslash key on the RT 102-key keyboard</p>
<p>(E3-E4)<br />
OEM specific</p>
<p>VK_PROCESSKEY (E5)<br />
Windows 95/98/Me, Windows NT 4.0, Windows 2000/XP: IME PROCESS key</p>
<p>(E6)<br />
OEM specific</p>
<p>VK_PACKET (E7)<br />
Windows 2000/XP: Used to pass Unicode characters as if they were keystrokes. The VK_PACKET key is the low word of a 32-bit Virtual Key value used for non-keyboard input methods. For more information, see Remark in KEYBDINPUT, SendInput, WM_KEYDOWN, and WM_KEYUP</p>
<p>- (E8)<br />
Unassigned</p>
<p>(E9-F5)<br />
OEM specific</p>
<p>VK_ATTN (F6)<br />
Attn key</p>
<p>VK_CRSEL (F7)<br />
CrSel key</p>
<p>VK_EXSEL (F8)<br />
ExSel key</p>
<p>VK_EREOF (F9)<br />
Erase EOF key</p>
<p>VK_PLAY (FA)<br />
Play key</p>
<p>VK_ZOOM (FB)<br />
Zoom key</p>
<p>VK_NONAME (FC)<br />
Reserved</p>
<p>VK_PA1 (FD)<br />
PA1 key</p>
<p>VK_OEM_CLEAR (FE)<br />
Clear key</p>
<p>&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>https://www.softwareace.cn/?feed=rss2&#038;p=1042</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>C++内存管理详解</title>
		<link>https://www.softwareace.cn/?p=904</link>
		<comments>https://www.softwareace.cn/?p=904#comments</comments>
		<pubDate>Wed, 13 Aug 2014 02:18:30 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[Windows api]]></category>

		<guid isPermaLink="false">http://www.softwareace.cn/?p=904</guid>
		<description><![CDATA[伟大的 Bill Gates 曾经失言： 640K ought to be enough for everyb [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>伟大的 Bill Gates 曾经失言：<br />
640K ought to be enough for everybody — Bill Gates 1981<br />
程序员们经常编写内存管理程序，往往提心吊胆。如果不想触雷，唯一的解决办法就是<br />
发现所有潜伏的地雷并且排除它们，躲是躲不了的。本文的内容比一般教科书的要深入得多，<br />
读者需细心阅读，做到真正地通晓内存管理。</p>
<p>1、内存分配方式（全局数据区、代码区（存函数）、栈、堆）<br />
内存分配方式有三种：<br />
（1）从静态存储区域分配。内存在程序编译的时候就已经分配好，这块内存在程序的<br />
整个运行期间都存在。例如全局变量，static 变量。<br />
（2）在栈上创建。在执行函数时，函数内局部变量的存储单元都可以在栈上创建，函<br />
数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中，效率很<br />
高，但是分配的内存容量有限。<br />
（3） 从堆上分配，亦称动态内存分配。程序在运行的时候用 malloc 或 new 申请任意<br />
多少的内存，程序员自己负责在何时用 free 或 delete 释放内存。动态内存的生存期由我们<br />
决定，使用非常灵活，但问题也最多。<br />
2、常见的内存错误及其对策<br />
发生内存错误是件非常麻烦的事情。编译器不能自动发现这些错误，通常是在程序运行<br />
时才能捕捉到。而这些错误大多没有明显的症状，时隐时现，增加了改错的难度。有时用户<br />
怒气冲冲地把你找来，程序却没有发生任何问题，你一走，错误又发作了。 常见的内存错<br />
误及其对策如下：<br />
* 内存分配未成功，却使用了它。<br />
编程新手常犯这种错误，因为他们没有意识到内存分配会不成功。常用解决办法是，在<br />
使用内存之前检查指针是否为 NULL。如果指针 p 是函数的参数，那么在函数的入口处用<br />
assert(p!=NULL)进行检查。如果是用 malloc 或 new 来申请内存，应该用 if(p==NULL) 或<br />
if(p!=NULL)进行防错处理。<br />
* 内存分配虽然成功，但是尚未初始化就引用它。<br />
犯这种错误主要有两个起因：一是没有初始化的观念；二是误以为内存的缺省初值全为<br />
零，导致引用初值错误（例如数组）。 内存的缺省初值究竟是什么并没有统一的标准，尽管<br />
有些时候为零值，我们宁可信其无不可信其有。所以无论用何种方式创建数组，都别忘了赋<br />
初值，即便是赋零值也不可省略，不要嫌麻烦。<br />
* 内存分配成功并且已经初始化，但操作越过了内存的边界。</p>
]]></content:encoded>
			<wfw:commentRss>https://www.softwareace.cn/?feed=rss2&#038;p=904</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>深度解析VC中的消息</title>
		<link>https://www.softwareace.cn/?p=792</link>
		<comments>https://www.softwareace.cn/?p=792#comments</comments>
		<pubDate>Tue, 06 May 2014 06:38:09 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[Windows api]]></category>

		<guid isPermaLink="false">http://www.softwareace.cn/?p=792</guid>
		<description><![CDATA[消息是指什么？ 消息系统对于一个win32程序来说十分重要，它是一个程序运行的动力源泉。一个消息，是系统定义的 [&#8230;]]]></description>
				<content:encoded><![CDATA[<p><strong>消息是指什么？</strong></p>
<p>消息系统对于一个win32程序来说十分重要，它是一个程序运行的动力源泉。一个消息，是系统定义的一个32位的值，他唯一的定义了一个事件，向Windows发出一个通知，告诉应用程序某个事情发生了。例如，单击鼠标、改变窗口尺寸、按下键盘上的一个键都会使Windows发送一个消息给应用程序。</p>
<p>消息本身是作为一个记录传递给应用程序的，这个记录中包含了消息的类型以及其他信息。例如，对于单击鼠标所产生的消息来说，这个记录中包含了单击鼠标时的坐标。这个记录类型叫做MSG，MSG含有来自windows应用程序消息队列的消息信息，它在Windows中声明如下：</p>
<div id="highlighter_236270">
<div>
<div><code>01.</code><code>typedef</code> <code>struct</code> <code>tagMsg</code></div>
<div><code>02.</code></div>
<div><code>03.</code><code>{</code></div>
<div><code>04.</code><code>HWND</code>    <code>hwnd;       接受该消息的窗口句柄</code></div>
<div><code>05.</code><code>UINT</code>    <code>message;    消息常量标识符，也就是我们通常所说的消息号</code></div>
<div><code>06.</code><code>WPARAM</code>  <code>wParam;     32位消息的特定附加信息，确切含义依赖于消息值</code></div>
<div><code>07.</code><code>LPARAM</code>  <code>lParam;     32位消息的特定附加信息，确切含义依赖于消息值</code></div>
<div><code>08.</code><code>DWORD</code>   <code>time</code><code>;       消息创建时的时间</code></div>
<div><code>09.</code><code>POINT   pt;         消息创建时的鼠标/光标在屏幕坐标系中的位置</code></div>
<div><code>10.</code><code>}MSG;</code></div>
</div>
</div>
<p>消息可以由系统或者应用程序产生。系统在发生输入事件时产生消息。举个例子, 当用户敲键, 移动鼠标或者单击控件。系统也产生消息以响应由应用程序带来的变化, 比如应用程序改变系统字体改变窗体大小。应用程序可以产生消息使窗体执行任务，或者与其他应用程序中的窗口通讯。</p>
<p><strong>消息中有什么？</strong></p>
<p>我们给出了上面的注释，是不是会对消息结构有了一个比较清楚的认识？如果还没有，那么我们再试着给出下面的解释：</p>
<p>hwnd 32位的窗口句柄。窗口可以是任何类型的屏幕对象，因为Win32能够维护大多数可视对象的句柄(窗口、对话框、按钮、编辑框等)。</p>
<p>message用于区别其他消息的常量值，这些常量可以是Windows单元中预定义的常量，也可以是自定义的常量。消息标识符以常量命名的方式指出消息的含义。当窗口过程接收到消息之后，他就会使用消息标识符来决定如何处理消息。例如、WM_PAINT告诉窗口过程窗体客户区被改变了需要重绘。符号常量指定系统消息属于的类别，其前缀指明了处理解释消息的窗体的类型。</p>
<p>wParam 通常是一个与消息有关的常量值，也可能是窗口或控件的句柄。</p>
<p>lParam 通常是一个指向内存中数据的指针。由于WParam、lParam和Pointer都是32位的，因此，它们之间可以相互转换。</p>
<p><strong>消息标识符的值</strong></p>
<p>系统保留消息标识符的值在0&#215;0000在0x03ff(WM_USER-1)范围。这些值被系统定义消息使用。 应用程序不能使用这些值给自己的消息。应用程序消息从WM_USER（0X0400）到0X7FFF，或0XC000到0XFFFF；WM_USER到0X7FFF范围的消息由应用程序自己使用；0XC000到0XFFFF范围的消息用来和其他应用程序通信，我们顺便说一下具有标志性的消息值：</p>
<p>WM_NULL&#8212;0&#215;0000    空消息。</p>
<p>0&#215;0001&#8212;-0&#215;0087    主要是窗口消息。</p>
<p>0x00A0&#8212;-0x00A9    非客户区消息</p>
<p>0&#215;0100&#8212;-0&#215;0108    键盘消息</p>
<p>0&#215;0111&#8212;-0&#215;0126    菜单消息</p>
<p>0&#215;0132&#8212;-0&#215;0138    颜色控制消息</p>
<p>0&#215;0200&#8212;-0x020A    鼠标消息</p>
<p>0&#215;0211&#8212;-0&#215;0213    菜单循环消息</p>
<p>0&#215;0220&#8212;-0&#215;0230    多文档消息</p>
<p>0x03E0&#8212;-0x03E8    DDE消息</p>
<p>0&#215;0400              WM_USER</p>
<p>0&#215;8000              WM_APP</p>
<p>0&#215;0400&#8212;-0x7FFF    应用程序自定义私有消息</p>
<p><strong>消息有哪几种？</strong></p>
<p><strong></strong>其实，windows中的消息虽然很多，但是种类并不繁杂，大体上有3种：窗口消息、命令消息和控件通知消息。</p>
<p>窗口消息大概是系统中最为常见的消息，它是指由操作系统和控制其他窗口的窗口所使用的消息。例如CreateWindow、DestroyWindow和MoveWindow等都会激发窗口消息，还有我们在上面谈到的单击鼠标所产生的消息也是一种窗口消息。</p>
<p>命令消息，这是一种特殊的窗口消息，他用来处理从一个窗口发送到另一个窗口的用户请求，例如按下一个按钮，他就会向主窗口发送一个命令消息.</p>
<p>控件通知消息，是指这样一种消息，一个窗口内的子控件发生了一些事情，需要通知父窗口。通知消息只适用于标准的窗口控件如按钮、列表框、组合框、编辑框，以及Windows公共控件如树状视图、列表视图等。例如，单击或双击一个控件、在控件中选择部分文本、操作控件的滚动条都会产生通知消息。 她类似于命令消息，当用户与控件窗口交互时，那么控件通知消息就会从控件窗口发送到它的主窗口。但是这种消息的存在并不是为了处理用户命令，而是为了让主窗口能够改变控件，例如加载、显示数据。例如按下一个按钮，他向父窗口发送的消息也可以看作是一个控件通知消息；单击鼠标所产生的消息可以由主窗口直接处理，然后交给控件窗口处理。</p>
<p>其中窗口消息及控件通知消息主要由窗口类即直接或间接由CWND类派生类处理。相对窗口消息及控件通知消息而言，命令消息的处理对象范围就广得多，它不仅可以由窗口类处理，还可以由文档类，文档模板类及应用类所处理。</p>
<p>由于控件通知消息很重要的，人们用的也比较多，但是具体的含义往往令初学者晕头转向，所以我决定把常见的几个列出来供大家参考：</p>
<p><strong>按扭控件</strong></p>
<p>BN_CLICKED        用户单击了按钮</p>
<p>BN_DISABLE        按钮被禁止</p>
<p>BN_DOUBLECLICKED  用户双击了按钮</p>
<p>BN_HILITE  用/户加亮了按钮</p>
<p>BN_PAINT  按钮应当重画</p>
<p>BN_UNHILITE 加亮应当去掉</p>
<p><strong>组合框控件</strong></p>
<p>CBN_CLOSEUP 组合框的列表框被关闭</p>
<p>CBN_DBLCLK 用户双击了一个字符串</p>
<p>CBN_DROPDOWN 组合框的列表框被拉出</p>
<p>CBN_EDITCHANGE 用户修改了编辑框中的文本</p>
<p>CBN_EDITUPDATE 编辑框内的文本即将更新</p>
<p>CBN_ERRSPACE 组合框内存不足</p>
<p>CBN_KILLFOCUS 组合框失去输入焦点</p>
<p>CBN_SELCHANGE 在组合框中选择了一项</p>
<p>CBN_SELENDCANCEL 用户的选择应当被取消</p>
<p>CBN_SELENDOK 用户的选择是合法的</p>
<p>CBN_SETFOCUS 组合框获得输入焦点</p>
<p><strong>编辑框控件</strong></p>
<p>EN_CHANGE 编辑框中的文本己更新</p>
<p>EN_ERRSPACE 编辑框内存不足</p>
<p>EN_HSCROLL 用户点击了水平滚动条</p>
<p>EN_KILLFOCUS 编辑框正在失去输入焦点</p>
<p>EN_MAXTEXT 插入的内容被截断</p>
<p>EN_SETFOCUS 编辑框获得输入焦点</p>
<p>EN_UPDATE 编辑框中的文本将要更新</p>
<p>EN_VSCROLL 用户点击了垂直滚动条消息含义</p>
<p><strong>列表框控件</strong></p>
<p>LBN_DBLCLK 用户双击了一项</p>
<p>LBN_ERRSPACE 列表框内存不够</p>
<p>LBN_KILLFOCUS 列表框正在失去输入焦点</p>
<p>LBN_SELCANCEL 选择被取消</p>
<p>LBN_SELCHANGE 选择了另一项</p>
<p>LBN_SETFOCUS 列表框获得输入焦点</p>
<p><strong>队列消息和非队列消息</strong></p>
<p>从消息的发送途径来看，消息可以分成2种：队列消息和非队列消息。消息队列由可以分成系统消息队列和线程消息队列。系统消息队列由Windows维护，线程消息队列则由每个GUI线程自己进行维护，为避免给non-GUI现成创建消息队列，所有线程产生时并没有消息队列，仅当线程第一次调用GDI函数数系统给线程创建一个消息队列。队列消息送到系统消息队列，然后到线程消息队列；非队列消息直接送给目的窗口过程。</p>
<p>对于队列消息，最常见的是鼠标和键盘触发的消息，例如WM_MOUSERMOVE,WM_CHAR等消息，还有一些其它的消息，例如：WM_PAINT、WM_TIMER和WM_QUIT。当鼠标、键盘事件被触发后，相应的鼠标或键盘驱动程序就会把这些事件转换成相应的消息，然后输送到系统消息队列，由Windows系统去进行处理。Windows系统则在适当的时机，从系统消息队列中取出一个消息，根据前面我们所说的MSG消息结构确定消息是要被送往那个窗口，然后把取出的消息送往创建窗口的线程的相应队列，下面的事情就该由线程消息队列操心了，Windows开始忙自己的事情去了。线程看到自己的消息队列中有消息，就从队列中取出来，通过操作系统发送到合适的窗口过程去处理。</p>
<p>一般来讲，系统总是将消息Post在消息队列的末尾。这样保证窗口以先进先出的顺序接受消息。然而,WM_PAINT是一个例外，同一个窗口的多个 WM_PAINT被合并成一个 WM_PAINT 消息, 合并所有的无效区域到一个无效区域。合并WM_PAIN的目的是为了减少刷新窗口的次数。</p>
<p>非队列消息将会绕过系统队列和消息队列，直接将消息发送到窗口过程，。系统发送非队列消息通知窗口，系统发送消息通知窗口。 例如,当用户激活一个窗口系统发送WM_ACTIVATE, WM_SETFOCUS, and WM_SETCURSOR。这些消息通知窗口它被激活了。非队列消息也可以由当应用程序调用系统函数产生。例如,当程序调用SetWindowPos系统发送WM_WINDOWPOSCHANGED消息。一些函数也发送非队列消息，例如下面我们要谈到的函数。</p>
<p><strong>消息的发送</strong></p>
<p>了解了上面的这些基础理论之后，我们就可以进行一下简单的消息发送与接收。</p>
<p>把一个消息发送到窗口有3种方式：发送、寄送和广播。</p>
<p>发送消息的函数有SendMessage、SendMessageCallback、SendNotifyMessage、SendMessageTimeout；寄送消息的函数主要有PostMessage、PostThreadMessage、PostQuitMessage；广播消息的函数我知道的只有BroadcastSystemMessage、BroadcastSystemMessageEx。</p>
<p>SendMessage的原型如下：LRESULT SendMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam)，这个函数主要是向一个或多个窗口发送一条消息，一直等到消息被处理之后才会返回。不过需要注意的是，如果接收消息的窗口是同一个应用程序的一部分，那么这个窗口的窗口函数就被作为一个子程序马上被调用；如果接收消息的窗口是被另外的线程所创建的，那么窗口系统就切换到相应的线程并且调用相应的窗口函数，这条消息不会被放进目标应用程序队列中。函数的返回值是由接收消息的窗口的窗口函数返回，返回的值取决于被发送的消息。</p>
<p>PostMessage的原型如下：BOOL PostMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam)，该函数把一条消息放置到创建hWnd窗口的线程的消息队列中，该函数不等消息被处理就马上将控制返回。需要注意的是，如果hWnd参数为HWND_BROADCAST，那么，消息将被寄送给系统中的所有的重叠窗口和弹出窗口，但是子窗口不会收到该消息；如果hWnd参数为NULL，则该函数类似于将dwThreadID参数设置成当前线程的标志来调用PostThreadMEssage函数。</p>
<p>从上面的这２个具有代表性的函数，我们可以看出消息的发送方式和寄送方式的区别所在：被发送的消息是否会被立即处理，函数是否立即返回。被发送的消息会被立即处理，处理完毕后函数才会返回；被寄送的消息不会被立即处理，他被放到一个先进先出的队列中，一直等到应用程序空线的时候才会被处理，不过函数放置消息后立即返回。</p>
<p>实际上，发送消息到一个窗口处理过程和直接调用窗口处理过程之间并没有太大的区别，他们直接的唯一区别就在于你可以要求操作系统截获所有被发送的消息，但是不能够截获对窗口处理过程的直接调用。</p>
<p>以寄送方式发送的消息通常是与用户输入事件相对应的，因为这些事件不是十分紧迫，可以进行缓慢的缓冲处理，例如鼠标、键盘消息会被寄送，而按钮等消息则会被发送。</p>
<p>广播消息用得比较少，BroadcastSystemMessage函数原型如下：</p>
<p>long BroadcastSystemMessage(DWORD dwFlags,LPDWORD lpdwRecipients,UINT uiMessage,WPARAM wParam,LPARAM lParam);该函数可以向指定的接收者发送一条消息，这些接收者可以是应用程序、可安装的驱动程序、网络驱动程序、系统级别的设备驱动消息和他们的任意组合。需要注意的是，如果dwFlags参数是BSF_QUERY并且至少一个接收者返回了BROADCAST_QUERY_DENY，则返回值为０，如果没有指定BSF_QUERY，则函数将消息发送给所有接收者，并且忽略其返回值。</p>
<p><strong>消息的接收</strong></p>
<p>消息的接收主要有３个函数：GetMessage、PeekMessage、WaitMessage。</p>
<p>GetMessage原型如下：BOOL GetMessage(LPMSG lpMsg,HWND hWnd,UINT wMsgFilterMin,UINT wMsgFilterMax);该函数用来获取与hWnd参数所指定的窗口相关的且wMsgFilterMin和wMsgFilterMax参数所给出的消息值范围内的消息。需要注意的是，如果hWnd为NULL，则GetMessage获取属于调用该函数应用程序的任一窗口的消息，如果wMsgFilterMin和wMsgFilterMax都是０，则GetMessage就返回所有可得到的消息。函数获取之后将删除消息队列中的除WM_PAINT消息之外的其他消息，至于WM_PAINT则只有在其处理之后才被删除。</p>
<p>PeekMessage原型如下：BOOL PeekMessage(LPMSG lpMsg,HWND hWnd,UINT wMsgFilterMin,UINT wMsgFilterMax,UINT wRemoveMsg）；该函数用于查看应用程序的消息队列，如果其中有消息就将其放入lpMsg所指的结构中，不过，与GetMessage不同的是，PeekMessage函数不会等到有消息放入队列时才返回。同样，如果hWnd为NULL，则PeekMessage获取属于调用该函数应用程序的任一窗口的消息，如果hWnd=-1，那么函数只返回把hWnd参数为NULL的PostAppMessage函数送去的消息。如果wMsgFilterMin和wMsgFilterMax都是０，则PeekMessage就返回所有可得到的消息。函数获取之后将删除消息队列中的除WM_PAINT消息之外的其他消息，至于WM_PAINT则只有在其处理之后才被删除。</p>
<p>WaitMessage原型如下：BOOL VaitMessage();当一个应用程序无事可做时，该函数就将控制权交给另外的应用程序，同时将该应用程序挂起，直到一个新的消息被放入应用程序的队列之中才返回。</p>
<p><strong>消息的处理</strong></p>
<p><strong></strong>接下来我们谈一下消息的处理，首先我们来看一下VC中的消息泵：</p>
<div id="highlighter_61666">
<div>
<div><code>1.</code><code>while</code><code>(GetMessage(&amp;msg, NULL, 0, 0))</code></div>
<div><code>2.</code><code>{</code></div>
<div><code>3.</code><code>if</code><code>(!TranslateAccelerator(msg.hWnd, hAccelTable, &amp;msg))</code></div>
<div><code>4.</code><code>{</code></div>
<div><code>5.</code><code>TranslateMessage(&amp;msg);</code></div>
<div><code>6.</code><code>DispatchMessage(&amp;msg);</code></div>
<div><code>7.</code><code>}</code></div>
<div><code>8.</code><code>}</code></div>
</div>
</div>
<p>首先，GetMessage从进程的主线程的消息队列中获取一个消息并将它复制到MSG结构，如果队列中没有消息，则GetMessage函数将等待一个消息的到来以后才返回。 如果你将一个窗口句柄作为第二个参数传入GetMessage，那么只有指定窗口的的消息可以从队列中获得。GetMessage也可以从消息队列中过滤消息只接受消息队列中落在范围内的消息。这时候就要利用GetMessage／PeekMessage指定一个消息过滤器。这个过滤器是一个消息标识符的范围或者是一个窗体句柄，或者两者同时指定。当应用程序要查找一个后入消息队列的消息是很有用。WM_KEYFIRST 和 WM_KEYLAST 常量用于接受所有的键盘消息。 WM_MOUSEFIRST 和 WM_MOUSELAST 常量用于接受所有的鼠标消息。</p>
<p>然后TranslateAccelerator判断该消息是不是一个按键消息并且是一个加速键消息，如果是，则该函数将把几个按键消息转换成一个加速键消息传递给窗口的回调函数。处理了加速键之后，函数TranslateMessage将把两个按键消息WM_KEYDOWN和WM_KEYUP转换成一个WM_CHAR，不过需要注意的是，消息WM_KEYDOWN,WM_KEYUP仍然将传递给窗口的回调函数。</p>
<p>处理完之后，DispatchMessage函数将把此消息发送给该消息指定的窗口中已设定的回调函数。如果消息是WM_QUIT，则GetMessage返回0，从而退出循环体。应用程序可以使用PostQuitMessage来结束自己的消息循环。通常在主窗口的WM_DESTROY消息中调用。</p>
<p>下面我们举一个常见的小例子来说明这个消息泵的运用：</p>
<div id="highlighter_516550">
<div>
<div><code>1.</code><code>if</code> <code>(::PeekMessage(&amp;msg, m_hWnd, WM_KEYFIRST,WM_KEYLAST, PM_REMOVE))</code></div>
<div><code>2.</code><code>{</code></div>
<div><code>3.</code><code>if</code> <code>(msg.message == WM_KEYDOWN &amp;&amp; msg.wParam == VK_ESCAPE)．．．</code></div>
<div><code>4.</code><code>}</code></div>
</div>
</div>
<p>这里我们接受所有的键盘消息，所以就用WM_KEYFIRST 和 WM_KEYLAST作为参数。最后一个参数可以是PM_NOREMOVE 或者 PM_REMOVE，表示消息信息是否应该从消息队列中删除。</p>
<p>所以这段小代码就是判断是否按下了Esc键，如果是就进行处理。</p>
<p><strong>窗口过程</strong></p>
<p>窗口过程是一个用于处理所有发送到这个窗口的消息的函数。任何一个窗口类都有一个窗口过程。同一个类的窗口使用同样的窗口过程来响应消息。 系统发送消息给窗口过程将消息数据作为参数传递给他，消息到来之后，按照消息类型排序进行处理，其中的参数则用来区分不同的消息，窗口过程使用参数产生合适行为。</p>
<p>一个窗口过程不经常忽略消息，如果他不处理，它会将消息传回到执行默认的处理。窗口过程通过调用DefWindowProc来做这个处理。窗口过程必须return一个值作为它的消息处理结果。大多数窗口只处理小部分消息和将其他的通过DefWindowProc传递给系统做默认的处理。窗口过程被所有属于同一个类的窗口共享，能为不同的窗口处理消息。下面我们来看一下具体的实例：</p>
<div id="highlighter_157396">
<div>
<div><code>01.</code><code>LRESULT</code> <code>CALLBACK WndProc(</code><code>HWND</code> <code>hWnd, </code><code>UINT</code> <code>message, </code><code>WPARAM</code> <code>wParam, </code><code>LPARAM</code> <code>lParam)</code></div>
<div><code>02.</code><code>{</code></div>
<div><code>03.</code><code>int</code> <code>wmId, wmEvent;</code></div>
<div><code>04.</code><code>PAINTSTRUCT ps;</code></div>
<div><code>05.</code><code>HDC</code> <code>hdc;</code></div>
<div><code>06.</code><code>TCHAR</code> <code>szHello[MAX_LOADSTRING];</code></div>
<div><code>07.</code><code>LoadString(hInst, IDS_HELLO, szHello, MAX_LOADSTRING);</code></div>
<div><code>08.</code></div>
<div><code>09.</code><code>switch</code> <code>(message)</code></div>
<div><code>10.</code><code>{</code></div>
<div><code>11.</code><code>case</code> <code>WM_COMMAND:</code></div>
<div><code>12.</code><code>wmId    = LOWORD(wParam);</code></div>
<div><code>13.</code><code>wmEvent = HIWORD(wParam);</code></div>
<div><code>14.</code><code>// Parse the menu selections:</code></div>
<div><code>15.</code><code>switch</code> <code>(wmId)</code></div>
<div><code>16.</code><code>{</code></div>
<div><code>17.</code><code>case</code> <code>IDM_ABOUT:</code></div>
<div><code>18.</code><code>DialogBox(hInst, (</code><code>LPCTSTR</code><code>)IDD_ABOUTBOX, hWnd, (DLGPROC)About);</code></div>
<div><code>19.</code><code>break</code><code>;</code></div>
<div><code>20.</code><code>case</code> <code>IDM_EXIT:</code></div>
<div><code>21.</code><code>DestroyWindow(hWnd);</code></div>
<div><code>22.</code><code>break</code><code>;</code></div>
<div><code>23.</code><code>default</code><code>:</code></div>
<div><code>24.</code><code>return</code> <code>DefWindowProc(hWnd, message, wParam, lParam);</code></div>
<div><code>25.</code><code>}</code></div>
<div><code>26.</code><code>break</code><code>;</code></div>
<div><code>27.</code></div>
<div><code>28.</code><code>case</code> <code>WM_PAINT:</code></div>
<div><code>29.</code><code>hdc = BeginPaint(hWnd, &amp;ps);</code></div>
<div><code>30.</code><code>// TODO: Add any drawing code here...</code></div>
<div><code>31.</code><code>RECT rt;</code></div>
<div><code>32.</code><code>GetClientRect(hWnd, &amp;rt);</code></div>
<div><code>33.</code><code>DrawText(hdc, szHello, </code><code>strlen</code><code>(szHello), &amp;rt, DT_CENTER);</code></div>
<div><code>34.</code><code>EndPaint(hWnd, &amp;ps);</code></div>
<div><code>35.</code><code>break</code><code>;</code></div>
<div><code>36.</code></div>
<div><code>37.</code><code>case</code> <code>WM_DESTROY:</code></div>
<div><code>38.</code><code>PostQuitMessage(0);</code></div>
<div><code>39.</code><code>break</code><code>;</code></div>
<div><code>40.</code><code>default</code><code>:</code></div>
<div><code>41.</code><code>return</code> <code>DefWindowProc(hWnd, message, wParam, lParam);</code></div>
<div><code>42.</code><code>}</code></div>
<div><code>43.</code><code>return</code> <code>0;</code></div>
<div><code>44.</code><code>}</code></div>
</div>
</div>
<p><strong>消息分流器</strong></p>
<p>通常的窗口过程是通过一个switch语句来实现的，这个事情很烦，有没有更简便的方法呢？有，那就是消息分流器，利用消息分流器，我们可以把switch语句分成更小的函数，每一个消息都对应一个小函数，这样做的好处就是对消息更容易管理。</p>
<p>之所以被称为消息分流器，就是因为它可以对任何消息进行分流。下面我们做一个函数就很清楚了：</p>
<div id="highlighter_804701">
<div>
<div><code>01.</code><code>void</code> <code>MsgCracker(</code><code>HWND</code> <code>hWnd,</code><code>int</code> <code>id,</code><code>HWND</code> <code>hWndCtl,</code><code>UINT</code> <code>codeNotify)</code></div>
<div><code>02.</code><code>{</code></div>
<div><code>03.</code><code>switch</code><code>(id)</code></div>
<div><code>04.</code><code>{</code></div>
<div><code>05.</code><code>case</code> <code>ID_A:</code></div>
<div><code>06.</code><code>if</code><code>(codeNotify==EN_CHANGE)...</code></div>
<div><code>07.</code><code>break</code><code>;</code></div>
<div><code>08.</code><code>case</code> <code>ID_B:</code></div>
<div><code>09.</code><code>if</code><code>(codeNotify==BN_CLICKED)...</code></div>
<div><code>10.</code><code>break</code><code>;</code></div>
<div><code>11.</code><code>....</code></div>
<div><code>12.</code><code>}</code></div>
<div><code>13.</code><code>}</code></div>
</div>
</div>
<p>然后我们修改一下窗口过程：</p>
<div id="highlighter_381995">
<div>
<div><code>01.</code><code>LRESULT</code> <code>CALLBACK WndProc(</code><code>HWND</code> <code>hWnd, </code><code>UINT</code> <code>message, </code><code>WPARAM</code> <code>wParam, </code><code>LPARAM</code> <code>lParam)</code></div>
<div><code>02.</code><code>{</code></div>
<div><code>03.</code><code>switch</code><code>(message)</code></div>
<div><code>04.</code><code>{</code></div>
<div><code>05.</code><code>HANDLE_MSG(hWnd,WM_COMMAND,MsgCracker);</code></div>
<div><code>06.</code><code>HANDLE_MSG(hWnd,WM_DESTROY,MsgCracker);</code></div>
<div><code>07.</code><code>default</code><code>:</code></div>
<div><code>08.</code><code>return</code> <code>DefWindowProc(hWnd, message, wParam, lParam);</code></div>
<div><code>09.</code><code>　}</code></div>
<div><code>10.</code><code>return</code> <code>0;</code></div>
<div><code>11.</code><code>}</code></div>
</div>
</div>
<p>在WindowsX.h中定义了如下的HANDLE_MSG宏：</p>
<p>#define HANDLE_MSG(hwnd,msg,fn) \</p>
<p>switch(msg): return HANDLE_##msg((hwnd),(wParam),(lParam),(fn));</p>
<p>实际上，HANDLE_WM_XXXX都是宏，例如：HANDLE_MSG(hWnd,WM_COMMAND,MsgCracker);</p>
<p>将被转换成如下定义：</p>
<p>#define HANDLE_WM_COMMAND(hwnd,wParam,lParam,fn)\</p>
<p>((fn)((hwnd),(int)(LOWORD(wParam)),(HWND)(lParam),(UINT)HIWORD(wParam)),0L);</p>
<p>好了，事情到了这一步，应该一切都明朗了。</p>
<p>不过，我们发现在windowsx.h里面还有一个宏：FORWARD_WM_XXXX，我们还是那WM_COMMAND为例，进行分析：</p>
<p>#define FORWARD_WM_COMMAND(hwnd, id, hwndCtl, codeNotify, fn) \</p>
<p>(void)(fn)((hwnd), WM_COMMAND, MAKEWPARAM((UINT)(id),(UINT)(codeNotify)), (LPARAM)(HWND)(hwndCtl))</p>
<p>所以实际上，FORWARD_WM_XXXX将消息参数进行了重新构造，生成了wParam &amp;&amp; lParam，然后调用了我们定义的函数。</p>
<p>好了，事情到这里也算是也段落了，下次我们在分析消息在MFC中的处理。</p>
<p>&nbsp;</p>
<p>前面，我们分析了消息的基本理论和基本的函数及用法，接下来，我们将进一步讨论消息传递在MFC中的实现。</p>
<p><strong>MFC消息的处理实现方式</strong></p>
<p>初看MFC中的各种消息，以及在头脑中根深蒂固的C++的影响，我们可能很自然的就会想到利用C++的三大特性之一：虚拟机制来实现消息的传递，但是经过分析，我们看到事情并不是想我们想象的那样，在MFC中消息是通过一种所谓的消息映射机制来处理的。</p>
<p>为什么呢？在潘爱民老师翻译的《Visual C++技术内幕》（第４版）中给出了详细的原因说明，我再简要的说一遍。在CWnd类中大约有110个消息，还有其它的MFC的类呢，算起来消息太多了，在C++中对程序中用到的每一个派生类都要有一个vtable，每一个虚函数在vtable中都要占用一个4字节大小的入口地址，这样一来，对于每个特定类型的窗口或控件，应用程序都需要一个440KB大小的表来支持虚拟消息控件函数。</p>
<p>如果说上面的窗口或控件可以勉强实现的话，那么对于菜单命令消息及按钮命令消息呢？因为不同的应用程序有不同的菜单和按钮，我们怎么处理呢？在MFC库的这种消息映射系统就避免了使用大的vtable，并且能够在处理常规Windows消息的同时处理各种各样的应用程序的命令消息。</p>
<p>说白了，MFC中的消息机制其实质是一张巨大的消息及其处理函数的一一对应表，然后加上分析处理这张表的应用框架内部的一些程序代码.这样就可以避免在SDK编程中用到的繁琐的CASE语句。</p>
<p><strong>MFC的消息映射的基类CCmdTarget</strong></p>
<p>如果你想让你的控件能够进行消息映射，就必须从CCmdTarget类中派生。CCmdTarget类是MFC处理命令消息的基础、核心。MFC为该类设计了许多成员函数和一些成员数据，基本上是为了解决消息映射问题的，所有响应消息或事件的类都从它派生，例如：应用程序类、框架类、文档类、视图类和各种各样的控件类等等，还有很多。</p>
<p>不过这个类里面有2个函数对消息映射非常重要，一个是静态成员函数DispatchCmdMsg，另一个是虚函数OnCmdMsg。</p>
<p>DispatchCmdMsg专门供MFC内部使用，用来分发Windows消息。OnCmdMsg用来传递和发送消息、更新用户界面对象的状态。<br />
CCmdTarget对OnCmdMsg的默认实现：在当前命令目标(this所指)的类和基类的消息映射数组里搜索指定命令消息的消息处理函数。</p>
<p>这里使用虚拟函数GetMessageMap得到命令目标类的消息映射入口数组_messageEntries，然后在数组里匹配命令消息ID相同、控制通知代码也相同的消息映射条目。其中GetMessageMap是虚拟函数，所以可以确认当前命令目标的确切类。</p>
<p>如果找到了一个匹配的消息映射条目，则使用DispachCmdMsg调用这个处理函数；</p>
<p>如果没有找到，则使用_GetBaseMessageMap得到基类的消息映射数组，查找，直到找到或搜寻了所有的基类（到CCmdTarget）为止；</p>
<p>如果最后没有找到，则返回FASLE。</p>
<p>每个从CCmdTarget派生的命令目标类都可以覆盖OnCmdMsg，利用它来确定是否可以处理某条命令，如果不能，就通过调用下一命令目标的OnCmdMsg，把该命令送给下一个命令目标处理。通常，派生类覆盖OnCmdMsg时，要调用基类的被覆盖的OnCmdMsg。</p>
<p>在MFC框架中，一些MFC命令目标类覆盖了OnCmdMsg，如框架窗口类覆盖了该函数，实现了MFC的标准命令消息发送路径。必要的话，应用程序也可以覆盖OnCmdMsg，改变一个或多个类中的发送规定，实现与标准框架发送规定不同的发送路径。例如，在以下情况可以作这样的处理：在要打断发送顺序的类中把命令传给一个非MFC默认对象；在新的非默认对象中或在可能要传出命令的命令目标中。</p>
<p><strong>消息映射的内容</strong></p>
<p>通过ClassWizard为我们生成的代码，我们可以看到，消息映射基本上分为2大部分：</p>
<p>在头文件(.h)中有一个宏DECLARE_MESSAGE_MAP()，他被放在了类的末尾，是一个public属性的；与之对应的是在实现部分（.cpp)增加了一章消息映射表，内容如下：</p>
<p>BEGIN_MESSAGE_MAP(当前类, 当前类的基类)</p>
<p>//{{AFX_MSG_MAP(CMainFrame)</p>
<p>消息的入口项</p>
<p>//}}AFX_MSG_MAP</p>
<p>END_MESSAGE_MAP()</p>
<p>但是仅是这两项还远不足以完成一条消息，要是一个消息工作，必须有以下3个部分去协作：</p>
<p>1.在类的定义中加入相应的函数声明；</p>
<p>2.在类的消息映射表中加入相应的消息映射入口项；</p>
<p>3.在类的实现中加入相应的函数体；</p>
<p><strong>消息的添加</strong></p>
<p><strong></strong>有了上面的这些只是作为基础，我们接下来就做我们最熟悉、最常用的工作：添加消息。MFC消息的添加主要有2种方法：自动/手动，我们就以这2种方法为例，说一下如何添加消息。</p>
<p><strong>1、利用Class Wizard实现自动添加</strong></p>
<p>在菜单中选择View&#8211;&gt;Class Wizard，也可以用单击鼠标右键，选择Class Wizard，同样可以激活Class Wizard。选择Message Map标签，从Class name组合框中选取我们想要添加消息的类。在Object IDs列表框中，选取类的名称。此时， Messages列表框显示该类的大多数(若不是全部的话)可重载成员函数和窗口消息。类重载显示在列表的上部，以实际虚构成员函数的大小写字母来表示。其他为窗口消息，以大写字母出现，描述了实际窗口所能响应的消息ID。选中我们向添加的消息，单击Add Function按钮，Class Wizard自动将该消息添加进来。</p>
<p>有时候，我们想要添加的消息本应该出现在Message列表中，可是就是找不到，怎么办？不要着急，我们可以利用Class Wizard上Class Info标签以扩展消息列表。在该页中，找到Message Filter组合框，通过它可以改变首页中Messages列表框中的选项。这里，我们选择Window，从而显示所有的窗口消息，一把情况下，你想要添加的消息就可以在Message列表框中出现了，如果还没有，那就接着往下看:)</p>
<p><strong>2、手动地添加消息处理函数</strong></p>
<p>如果在Messages列表框中仍然看不到我们想要的消息，那么该消息可能是被系统忽略掉或者是你自己创建的，在这种情况下，就必须自己手工添加。根据我们前面所说的消息工作的3个部件，我们一一进行处理：</p>
<p>1) 在类的. h文件中添加处理函数的声明，紧接在//}}AFX_MSG行之后加入声明，注意：一定要以afx_msg开头。通常，添加处理函数声明的最好的地方是源代码中Class Wizard维护的表下面，但是在它标记其领域的｛｛｝｝括弧外面。这些括弧中的任何东西都将会被Class Wizard销毁。</p>
<p>2) 接着，在用户类的.cpp文件中找到//}}AFX_MSG_MAP行，紧接在它之后加入消息入口项。同样，也是放在{ {} }的外面</p>
<p>3) 最后，在该文件中添加消息处理函数的实体。</p>
]]></content:encoded>
			<wfw:commentRss>https://www.softwareace.cn/?feed=rss2&#038;p=792</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
