天天看點

ICON檔案儲存

這兩天想做一下windows系統下圖示的修改,讓程式有更新的時候能夠更新圖示的外觀,達到提醒的作用,360,QQ經常采用這種方式進行更新的提示,也有采用彈框的方式來提示,用新版QVOD的同僚可能見到過類似的。廢話不說,我的思路是:

(1)檢測程式更新

(2)檢測windows桌面圖示中是夠含有需要的,如果沒有則需要建立,如果有則準備更新圖示

(3)根據跟新的内容,程式自己建立新的圖像,修改快捷方式的圖示

這種方法需要開機之後,存在一個系統程序來檢測這種更新,360就是這樣幹的~~還有一個弊端是程式需要檢測桌面快捷方式的資訊來判斷是不是自己想去修改的,OK ,這個問題其實可以通過LNK檔案的屬性,應用資訊,名字等問題來解決。 下面是實作過程

1 ICON檔案的操作

ICON檔案的格式和BMP檔案的格式的類似,不過增加了層數的東西,一個ICON檔案中可能有幾份資料,不同的分辨率,ICON檔案的格式主要定義如下:

//ICON檔案存儲格式
typedef struct
{
    byte        bwidth;          // width, in pixels, of the image
    byte        bheight;         // height, in pixels, of the image
    byte        bcolorcount;     // number of colors in image (0 if >=8bpp)
    byte        breserved;       // reserved ( must be 0)
    WORD        wplanes;         // color planes
    WORD        wbitcount;       // bits per pixel
    DWORD       dwbytesinres;    // how many bytes in this resource?
    DWORD       dwimageoffset;   // where in the file is this image?
} ICONDIRENTRY, *LPICONDIRENTRY;

//ICON檔案目錄
typedef struct 
{
    WORD            idReserved;   // Reserved
    WORD            idType;       // resource type (1 for icons)
    WORD            idCount;      // how many images?
    ICONDIRENTRY    idEntries[1]; // the entries for each image
} ICONDIR, *LPICONDIR;


//ICON檔案資料存儲格式
typedef struct
{
    BITMAPINFOHEADER    icheader;      // dib header
    RGBQUAD                iccolors[1];   // color table
    byte                icxor[1];      // dib bits for xor mask
    byte                icand[1];      // dib bits for and mask
} iconimage, *lpiconimage;      
(1)格式具體的講解可以參考一下兩篇文章:ICON格式解析,ICON檔案格式分析VB實作,中間涉及到BMP檔案BITMAPINFOHEADER的格式解析,      
可以到MSDN上或者百度上查找一下 很容易了解,ICON填充這個格式的時候和BMP檔案不同,biHeight參數是ICONDIRENTRY結構中bheight的2倍,      
原因是圖像資料中包含了icxor和icand部分。ICon檔案中比較糾結的還有ICONDIRENTRY結構中dwbytesinres和dwimageoffset這兩個變量的      
的計算,下文會給出具體的計算方式代碼,需要自己了解一下。      
(2)ICON檔案的操作主要涉及讀和寫兩部分,圖示的修改則直接操作記憶體資料即可,注意的是裡面如果内部包含多份資料,需要全部進行修改,      
本文主要處理ICON檔案中隻有一份資料的情況。ICON檔案讀寫可以使用C語言的FILE類型操作,C++的fstream或者ifstream以及ofstream類      
以及 檔案句柄CreateFile進行操作,這裡使用C++的方式進行操作      
(3)結構中變長變量的了解,ICONDIR結構中采用了變長數組結構,如ICONDIRENTRY idEntries[1],剛開始還有點不熟悉,這種結構預設      
結構中有一個ICONDIRENTRY 變量,但是如果有多個的話可以通過重新配置設定記憶體大小來實作,具體方式參考代碼中讀資料部分。很囧的是,      
我現在才知道C99已經支援變長數組了。      
(4)資料操作代碼(支援多分資料)      
程式裡面對ICON讀入的資料重新進行了整理,結構如下      
//圖像資料存儲
struct stImageData
{
    BITMAPINFOHEADER   icheader;              // ptr to header
    byte*          icxor ;
    byte*          icand ;

    stImageData()
    {
        icand = NULL ;
        icxor = NULL ;
    }
};

//ICON圖像整體組成
struct  stIconData
{
    int num ;
    stImageData data[1];
    stIconData()
    {
        num = 0 ; 
    }
};

struct stIcon
{
    ICONDIR *dir ;
    stIconData *icon ;

    stIcon()
    {
        dir = NULL ;
        icon = NULL ;
    }
};      
讀資料:      
bool CIconOperate::ReadIcon(const string _str)
{
    fstream fin ;
    fin.open(_str.c_str(),ios_base::binary | ios_base::in);
    if (!fin.is_open())
    {
        return false ;
    }

    ICONDIR dirTemp ;
    //reserved
    fin.read((char*)(&dirTemp.idReserved),sizeof(WORD));
    //type
    fin.read((char*)(&dirTemp.idType),sizeof(WORD));
    //num
    fin.read((char*)(&dirTemp.idCount),sizeof(WORD));

    //image data header
    byte *pBuffer = new byte[3 * sizeof(WORD) + dirTemp.idCount * sizeof(ICONDIRENTRY)] ; // 變長資料操作
    m_IconDir = (ICONDIR *)(pBuffer);
    m_IconDir->idCount = dirTemp.idCount ;
    m_IconDir->idReserved = dirTemp.idReserved ;
    m_IconDir->idType = dirTemp.idType ;
    for (int i = 0 ; i < dirTemp.idCount ; ++i)
    {
        fin.read((char*)(&m_IconDir->idEntries[i]),sizeof(ICONDIRENTRY));
    }

    //img data 
    byte *pBufferData = new byte[sizeof(int) + dirTemp.idCount * sizeof(stImageData)];
    m_IconData = (stIconData*)pBufferData ;
    m_IconData->num = m_IconDir->idCount ;
    for (int i = 0 ; i < m_IconDir->idCount ; ++ i)
    {
        //SEEK
        fin.seekg(m_IconDir->idEntries[i].dwimageoffset,ios_base::beg) ;

        //READ BITMAPINFOHEADER
        fin.read((char*)(&m_IconData->data[i].icheader),sizeof(BITMAPINFOHEADER));

        //READ XOR DATA
        int xornum = WIDTHBYTES(m_IconDir->idEntries[i].bwidth,m_IconDir->idEntries[i].wbitcount)
                        * m_IconDir->idEntries[i].bheight; 
        int andnum = WIDTHBYTES(m_IconDir->idEntries[i].bwidth,1)
            * m_IconDir->idEntries[i].bheight;

        m_IconData->data[i].icxor = new byte[xornum];
        fin.read((char*)(m_IconData->data[i].icxor),xornum);

        //READ AND DATA
        m_IconData->data[i].icand = new byte[andnum];
        fin.read((char*)(m_IconData->data[i].icand),andnum);
    }

    fin.close();

    return true ;
}      

寫資料

bool CIconOperate::SaveIcon(const string _str)
{
    if (m_IconDir == NULL || m_IconData == NULL)
        return false ;

    //int  width  = 64 ;
    //int  heigth = 64 ;
    //int  ppx = 32 ;

    fstream fout;
    fout.open(_str.c_str(),ios_base::out | ios_base::binary);  

    //reserved 
    WORD wData = 0 ;
    fout.write((char*)(&wData),sizeof(WORD));
    //type
    wData = 1 ;
    fout.write((char*)(&wData),sizeof(WORD));
    //num
    wData = m_IconDir->idCount ;
    fout.write((char*)(&wData),sizeof(WORD));

    //write ICONDIRENTRY資料
    for (int i = 0 ; i < m_IconDir->idCount ; ++i)
    {
        //ICONDIRENTRY結構
        ICONDIRENTRY iconData ;
        iconData.bwidth  = m_IconDir->idEntries[i].bwidth ;
        iconData.bheight = m_IconDir->idEntries[i].bheight ;
        iconData.bcolorcount  =  0;
        iconData.breserved    =  0 ;
        iconData.wplanes      =  1 ;
        iconData.wbitcount    =  m_IconDir->idEntries[i].wbitcount ;
        iconData.dwbytesinres = sizeof(BITMAPINFOHEADER) 
            +  iconData.bheight * WIDTHBYTES(iconData.bwidth,iconData.wbitcount) 
            +  iconData.bheight * WIDTHBYTES(iconData.bwidth,1);
        iconData.dwimageoffset =  CalculateImageOffset(i) ; 

        fout.write((char*)(&iconData),sizeof(ICONDIRENTRY));
    }


    for (int i = 0 ; i < m_IconDir->idCount ; ++i)
    {
        //BITMAPINFOHEADER結構
        BITMAPINFOHEADER tBitHeader;
        tBitHeader.biSize          = sizeof(BITMAPINFOHEADER);
        tBitHeader.biWidth         = m_IconDir->idEntries[i].bwidth ;
        tBitHeader.biHeight        = m_IconDir->idEntries[i].bheight*2 ;
        tBitHeader.biPlanes        = 1 ;
        tBitHeader.biBitCount      = m_IconDir->idEntries[i].wbitcount ;
        tBitHeader.biCompression   = 0 ;
        tBitHeader.biSizeImage     = 0;
        tBitHeader.biXPelsPerMeter = 0 ;
        tBitHeader.biYPelsPerMeter = 0 ;
        tBitHeader.biClrUsed       = 0 ;
        tBitHeader.biClrImportant  = 0 ;

        fout.write((char*)(&tBitHeader),sizeof(BITMAPINFOHEADER));

        fout.write((char*)m_IconData->data[i].icxor,tBitHeader.biHeight/2 * WIDTHBYTES(tBitHeader.biWidth,tBitHeader.biBitCount));

        fout.write((char*)m_IconData->data[i].icand,tBitHeader.biHeight/2 * WIDTHBYTES(tBitHeader.biWidth,1));

    }

    fout.close();
    
    return true ;
}
      
2快捷方式搜尋      
以前沒注意到這個問題,原來快捷方式也是有具體格式的,格式字尾為.LNK,格式的具體解析可以參考文檔:lnk檔案解析,windows快捷方式      
在程式中windows系統提供了IshellLink類進行具體的操作,OK,檔案搜尋的windows系統通過IShellForder來繼續操作,具體搜尋過程可以      
參考文章:Windows Shell提取媒體資訊,vC++實作周遊桌面和快速啟動裡的所有快捷方式, ok,這些了解之後,你就可以周遊桌面的所有快捷      
方式了。還需要了解一下window的系統資料庫操作,桌面的位置是通過系統資料庫獲得的。      
vector<string>* CFileSearch::SearchLinkByName(const CString& _str) 
{
    m_FindList.clear();
    m_strFind = _str ;

    GetPath(m_strDeskTopPath,m_strQuickLanchPath);   
    if(GetDesktopIShellFolder())   
    {   
        GetIEunmIDList(m_pIShellFolder,FALSE,m_mode);   
    } 


    fprintf(fp,"



");
    for (int i = 0 ; i < m_FindList.size() ; i++)
    {
        fprintf(fp,"%s
",m_FindList[i].c_str());
    }
    return &m_FindList ;
}

//擷取桌面檔案夾和快速啟動檔案夾的路徑    
int CFileSearch::GetPath(char *DeskTop, char* AppData)   
{   
    CRegKey m_reg;   
    if(ERROR_SUCCESS==m_reg.Open(HKEY_CURRENT_USER,"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders",KEY_READ))   
    {   
        ULONG m_ulsize1=1000;   
        ULONG m_ulsize2=1000;   
        m_reg.QueryStringValue("Desktop",DeskTop,&m_ulsize1); 
        m_reg.QueryStringValue("AppData",AppData,&m_ulsize2);   
        lstrcat(AppData,"\Microsoft\Internet Explorer\Quick Launch"); 

    }   
    return 0;   
}   

//擷取桌面檔案夾的IShellFolder接口指針    
BOOL CFileSearch::GetDesktopIShellFolder()   
{   
    m_pIShellFolder=GetIShellFolderByPath(m_strDeskTopPath);   
    //m_pAppData=GetIShellFolderByPath(m_strQuickLanchPath);   
    return TRUE;   
}   

//擷取桌面檔案夾的IEnumIDList接口    
//isQuickLanch---是否搜尋第二層檔案夾
//isRecur---是否是檔案夾
BOOL CFileSearch::GetIEunmIDList(IShellFolder * pShellFolder,BOOL isRecur,BOOL isQuickLanch)   
{   
    IEnumIDList *pIEnumFile     = NULL ;
    IEnumIDList *pIEnumFolder = NULL ;

    if(!pShellFolder) 
        return FALSE;   

    HRESULT hr=pShellFolder->EnumObjects(0,SHCONTF_NONFOLDERS,&pIEnumFile);        //不是檔案夾
    if(FAILED(hr))   
    {   
        return FALSE;   
    }   
    if(!isRecur)   
    {   
        m_pFirstLayerFile = pIEnumFile;            
    }   
    EnumAllItems(pIEnumFile,FALSE,isQuickLanch);   

    if(!isQuickLanch)                             
    {   
        HRESULT hr=pShellFolder->EnumObjects(0,SHCONTF_FOLDERS,&pIEnumFolder);      //檔案夾
        if(FAILED(hr))   
        {   
            return FALSE;   
        }   

        if(!isRecur)   
        {   
            m_pFirstLayerFolder = pIEnumFolder;  
        }   

        EnumAllItems(pIEnumFolder,TRUE,isQuickLanch);   
    }   
    return TRUE;   
}   
 
BOOL CFileSearch::EnumAllItems(IEnumIDList *m_pEnum,BOOL isFolder,BOOL isQuickLanch)   
{   
    LPITEMIDLIST m_pItem=NULL;   
    ULONG   m_ulwork= 0;     
    while(m_pEnum->Next(1,&m_pItem,&m_ulwork)==S_OK)   
    {   
        //如果是第一層,重置路徑    
        if(!isQuickLanch)   
        {   
            if((m_pFirstLayerFolder==m_pEnum) && (isFolder))   
            {   
                lstrcpy(m_strParentPath,m_strDeskTopPath);   
            }   

            if((m_pFirstLayerFile==m_pEnum) && (!isFolder))   
            {   
                lstrcpy(m_strParentPath,m_strDeskTopPath);   
            }      
        }   
        else   
        {   
        //    if((m_pFirstLayerFile==m_pEnum) && (!isFolder))   
        //    {   
        //        lstrcpy(m_strParentPath,m_strQuickLanchPath);   
        //    }   
            if((m_pFirstLayerFolder==m_pEnum) && (isFolder))   
            {   
                lstrcpy(m_strParentPath,m_strDeskTopPath);   
            }   

            if((m_pFirstLayerFile==m_pEnum) && (!isFolder))   
            {   
                lstrcpy(m_strParentPath,m_strDeskTopPath);   
            }   
        }   

        WIN32_FIND_DATA ffd;   
        SHGetDataFromIDList(m_pIShellFolder,m_pItem,SHGDFIL_FINDDATA,&ffd,sizeof(WIN32_FIND_DATA));   
        if(!isFolder)   
        {   
            CString m_strTempPath=m_strParentPath;   
            m_strTempPath+="\";   
            m_strTempPath += ffd.cFileName;   
            CString m_strCmp=".lnk";   

            fprintf(fp,"%s
",m_strTempPath.GetBuffer());
            m_strTempPath.MakeUpper();   
            m_strCmp.MakeUpper();   

            if(m_strTempPath.Right(4)==m_strCmp)       //判斷是否是連結檔案 
            {   
                if (ReadShortCut(m_strTempPath.GetBuffer()))
                {
                    string str = m_strTempPath.GetBuffer();
                    m_FindList.push_back(str);
                } 
                m_strTempPath.ReleaseBuffer();   
            }   
        }   
        else   
        {   
            lstrcat(m_strParentPath,"\");   
            lstrcat(m_strParentPath,ffd.cFileName);   
            IShellFolder *m_pITemp=GetIShellFolderByPath(m_strParentPath);   
            GetIEunmIDList(m_pITemp,TRUE,isQuickLanch);   
        }   
    }   
    return TRUE;   
}   



//擷取指定目錄的IShellFolder接口    
IShellFolder *CFileSearch::GetIShellFolderByPath(LPTSTR path)   
{   
    IShellFolder *m_ShellFolderTopMost=NULL;   
    HRESULT hr=SHGetDesktopFolder(&m_ShellFolderTopMost);   
    if(FAILED(hr))   
    {   
        return NULL;   
    }   
    IShellFolder *m_pFolder;   
    LPITEMIDLIST pidlWorkDir=NULL;     
    OLECHAR strOleFilePath[MAX_PATH];
    MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, path, -1, strOleFilePath, MAX_PATH );
    hr = m_ShellFolderTopMost->ParseDisplayName(NULL,NULL,strOleFilePath,NULL,&pidlWorkDir,NULL);      
    if(FAILED(hr))   
    {   
        return   NULL;      
    }   
    hr=m_ShellFolderTopMost->BindToObject(pidlWorkDir,NULL,IID_IShellFolder,(LPVOID*)&m_pFolder);     
    if(m_ShellFolderTopMost)m_ShellFolderTopMost->Release();   
    return m_pFolder;   
}   

//讀取快捷方式的資訊    
BOOL CFileSearch::ReadShortCut(LPTSTR linkName)   
{   
    ::CoInitialize(NULL);   
    IShellLink *m_pIShellLink=NULL;   
    IPersistFile *m_pIPersistFile=NULL;   
    HRESULT hr=::CoCreateInstance(CLSID_ShellLink,NULL,CLSCTX_INPROC_SERVER,IID_IShellLink,(LPVOID*)&m_pIShellLink);   
    if(hr==S_OK)   
    {   
        hr=m_pIShellLink->QueryInterface(IID_IPersistFile,(void **)&m_pIPersistFile); 

        if(hr==S_OK)   
        {   
            USES_CONVERSION;    
            m_pIPersistFile->Load(T2COLE(linkName),STGM_READWRITE);

            char m_strPath[MAX_PATH]={0};   
            m_pIShellLink->GetPath(m_strPath,MAX_PATH,NULL,SLGP_UNCPRIORITY); 

            CString temp = m_strPath;   
            temp.MakeUpper();   

            m_strFind.MakeUpper();

            if (strstr(temp.GetBuffer(),m_strFind.GetBuffer()))  //判斷應用程式名
            {
                //AfxMessageBox(temp);

                if(m_pIShellLink) 
                    m_pIShellLink->Release(); 

                if(m_pIPersistFile) 
                    m_pIPersistFile->Release();  

                ::CoUninitialize();   
                return TRUE;   
            }
        }   
    }   
  
    if(m_pIShellLink) 
        m_pIShellLink->Release(); 

    if(m_pIPersistFile) 
        m_pIPersistFile->Release();  

    ::CoUninitialize();   
    return FALSE;   
      
3快捷方式圖示的更新      
搜尋到快捷方式之後,IshellLink類提供了更換圖示、設定描述、設定連結的應用程式等資訊,如果要參考文檔可以參考一下:      
快捷方式操作指南,在應用程式中建立快捷方式      
bool CModifyLinkICon::ChangeLinkIcon(const string& strLnkName,const string& strIconPath)
{
    if (strLnkName.empty() || strIconPath.empty())
    {
        return false;
    }

    HRESULT hres;
    IShellLink *psl = NULL;
    IPersistFile *pPf = NULL;
    int id;
    LPITEMIDLIST pidl;
    bool bRet = false;

    do
    {
        hres = CoInitialize(NULL);
        if (FAILED(hres))
        {
            break;
        }

        hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);
        if (FAILED(hres))
        {
            break;
        }

        hres = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&pPf);
        if (FAILED(hres))
        {
            break;
        }

        wchar_t wsz[256];
        MultiByteToWideChar(CP_ACP, 0, strLnkName.c_str(), -1, wsz, MAX_PATH);

        hres = pPf->Load(wsz, STGM_READWRITE);    
        if (FAILED(hres))
        {
            break;
        }

        hres = psl->SetIconLocation(strIconPath.c_str(), 0);
        if (FAILED(hres))
        {
            break;
        }

        pPf->Save(wsz, TRUE);
        if (FAILED(hres))
        {
            break;
        }

        bRet = true;

    } while (0);

    if (pPf != NULL)
    {
        pPf->Release();
    }

    if (psl != NULL)
    {
        psl->Release();
    }

    CoUninitialize();

    AfxMessageBox("替換完成");

    return bRet;
}
      

4結論

其實這些操作估計很早之前就有,網上的資訊頁很多,我就算做一下彙總吧,最後給出我測試用的界面,很簡答,可以實作快捷的搜尋,快捷圖示的更換,圖像的讀取,寫入以及簡單的修改。