场景
- 开发
WTL
时,有时候会需要界面支持拖放文件的操作,这样能更容易选择文件,比如桌面的文件。某次在运行安装包时,安装完软件之后,软件以管理员模式启动后拖放操作不生效,文件可以拖放,但是界面不响应WM_DROPFILES
消息,怎么回事?
说明
-
拖放功能是
Win32
软件必不可少的功能之一,安装完软件后管理员模式启动的软件不能拖放,势必会影响用户的体验。查了下资料,是因为WIN7
系统后,微软增加了UI消息隔离机制(UMI)
,低权限进程无法和高权限进程进行通信。而WM_DROPFILES
消息是explorer.exe
,即我们说的shell
外壳程序发送的。explorer.exe
进程并不全是管理员权限,即它是低进程,那么根据UMI
。这个消息会被过滤掉,主界面不会收到这个消息。 -
要高权限的界面收到
WM_DROPFILES
消息,那么需要加例外条件。Win32
函数提供了ChangeWindowMessageFilter
[3]来添加消息过滤规则。Win7 SP1
之后需要使用ChangeWindowMessageFilterEx
函数, 因为ChangeWindowMessageFilter
可能会在某个版本的系统后被废弃掉。 -
ChangeWindowMessageFilterEx
函数可以对某个窗口加消息过滤,而不是旧函数ChangeWindowMessageFilter
作用于整个进程。我们可以判断是管理员模式启动的程序再进行调用,也可以不判断。
if (IsCurrentUserLocalAdministrator()) {
ChangeWindowMessageFilterEx(m_hWnd,WM_DROPFILES,MSGFLT_ALLOW,NULL);
ChangeWindowMessageFilterEx(m_hWnd,WM_COPYDATA - 1,MSGFLT_ALLOW,NULL);
}
- 这里只添加
WM_DROPFILES
是不会生效的,还有一个值是WM_COPYGLOBALDATA 0x0049
的内部消息,刚好消息的WM_COPYDATA
-1的值。
#define WM_COPYDATA 0x004A
例子
View.h
// View.h : interface of the CView class
//
/////////////////////////////////////////////////////////////////////////////
#pragma once
#include "atlcrack.h"
#include <vector>
#include <string>
class CView : public CWindowImpl<CView>
{
public:
DECLARE_WND_CLASS(NULL)
BOOL PreTranslateMessage(MSG* pMsg);
BEGIN_MSG_MAP_EX(CView)
MSG_WM_CREATE(OnCreate)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
MSG_WM_DROPFILES(OnDropFiles)
END_MSG_MAP()
// Handler prototypes (uncomment arguments if needed):
// LRESULT MessageHandler(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
// LRESULT CommandHandler(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
// LRESULT NotifyHandler(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)
LRESULT OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/);
int OnCreate(LPCREATESTRUCT lpCreateStruct);
void OnDropFiles(HDROP hDrop);
protected:
std::vector<std::wstring> files;
HBRUSH bkg_brush_ = NULL;
HFONT fontNormal_ = NULL;
};
View.cpp
// View.cpp : implementation of the CView class
//
/////////////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "resource.h"
#include "atlmisc.h"
#include "View.h"
static BOOL IsCurrentUserLocalAdministrator()
{
BOOL fReturn = FALSE;
DWORD dwStatus = 0;
DWORD dwAccessMask = 0;
DWORD dwAccessDesired = 0;
DWORD dwACLSize = 0;
DWORD dwStructureSize = sizeof(PRIVILEGE_SET);
PACL pACL = NULL;
PSID psidAdmin = NULL;
HANDLE hToken = NULL;
HANDLE hImpersonationToken = NULL;
PRIVILEGE_SET ps = {0};
GENERIC_MAPPING GenericMapping = {0};
PSECURITY_DESCRIPTOR psdAdmin = NULL;
SID_IDENTIFIER_AUTHORITY SystemSidAuthority = SECURITY_NT_AUTHORITY;
// Determine if the current thread is running as a user that is a member
// of the local admins group. To do this, create a security descriptor
// that has a DACL which has an ACE that allows only local administrators
// access. Then, call AccessCheck with the current thread's token and
// the security descriptor. It will say whether the user could access an
// object if it had that security descriptor. Note: you do not need to
// actually create the object. Just checking access against the
// security descriptor alone will be sufficient.
const DWORD ACCESS_READ = 1;
const DWORD ACCESS_WRITE = 2;
__try
{
// AccessCheck() requires an impersonation token. We first get a
// primary token and then create a duplicate impersonation token.
// The impersonation token is not actually assigned to the thread, but
// is used in the call to AccessCheck. Thus, this function itself never
// impersonates, but does use the identity of the thread. If the thread
// was impersonating already, this function uses that impersonation
// context.
if (!OpenThreadToken(GetCurrentThread(), TOKEN_DUPLICATE|TOKEN_QUERY, TRUE, &hToken))
{
if (GetLastError() != ERROR_NO_TOKEN)
__leave;
if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_DUPLICATE|TOKEN_QUERY, &hToken))
__leave;
}
if (!DuplicateToken (hToken, SecurityImpersonation, &hImpersonationToken))
__leave;
// Create the binary representation of the well-known SID that
// represents the local administrators group. Then create the
// security descriptor and DACL with an ACE that allows only local
// admins access. After that, perform the access check. This will
// determine whether the current user is a local admin.
if (!AllocateAndInitializeSid(&SystemSidAuthority, 2,
SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS,
0, 0, 0, 0, 0, 0, &psidAdmin))
__leave;
psdAdmin = LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
if (psdAdmin == NULL)
__leave;
if (!InitializeSecurityDescriptor(psdAdmin, SECURITY_DESCRIPTOR_REVISION))
__leave;
// Compute size needed for the ACL.
dwACLSize = sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) +
GetLengthSid(psidAdmin) - sizeof(DWORD);
pACL = (PACL)LocalAlloc(LPTR, dwACLSize);
if (pACL == NULL)
__leave;
if (!InitializeAcl(pACL, dwACLSize, ACL_REVISION2))
__leave;
dwAccessMask= ACCESS_READ | ACCESS_WRITE;
if (!AddAccessAllowedAce(pACL, ACL_REVISION2, dwAccessMask, psidAdmin))
__leave;
if (!SetSecurityDescriptorDacl(psdAdmin, TRUE, pACL, FALSE))
__leave;
// AccessCheck validates a security descriptor somewhat; set the group
// and owner so that enough of the security descriptor is filled out to
// make AccessCheck happy.
SetSecurityDescriptorGroup(psdAdmin, psidAdmin, FALSE);
SetSecurityDescriptorOwner(psdAdmin, psidAdmin, FALSE);
if (!IsValidSecurityDescriptor(psdAdmin))
__leave;
dwAccessDesired = ACCESS_READ;
// Initialize GenericMapping structure even though you
// do not use generic rights.
GenericMapping.GenericRead = ACCESS_READ;
GenericMapping.GenericWrite = ACCESS_WRITE;
GenericMapping.GenericExecute = 0;
GenericMapping.GenericAll = ACCESS_READ | ACCESS_WRITE;
if (!AccessCheck(psdAdmin, hImpersonationToken, dwAccessDesired,
&GenericMapping, &ps, &dwStructureSize, &dwStatus,
&fReturn))
{
fReturn = FALSE;
__leave;
}
}
__finally
{
// Clean up.
if (pACL)
LocalFree(pACL);
if (psdAdmin)
LocalFree(psdAdmin);
if (psidAdmin)
FreeSid(psidAdmin);
if (hImpersonationToken)
CloseHandle (hImpersonationToken);
if (hToken)
CloseHandle (hToken);
}
return fReturn;
}
static HFONT GetHFONT(int em,int charset,
bool bold,const wchar_t* fontName)
{
LOGFONT lf;
memset(&lf, 0, sizeof(LOGFONT)); // zero out structure
lf.lfHeight = em; // request a 8-pixel-height font
lf.lfCharSet = charset;
lstrcpy(lf.lfFaceName,fontName); // request a face name "Arial"
if(bold)
lf.lfWeight = FW_BOLD;
else
lf.lfWeight = FW_NORMAL;
HFONT font = ::CreateFontIndirect(&lf);
return font;
}
BOOL CView::PreTranslateMessage(MSG* pMsg)
{
pMsg;
return FALSE;
}
LRESULT CView::OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
CPaintDC dc(m_hWnd);
CRect client_rect;
GetClientRect(&client_rect);
CMemoryDC mdc(dc,client_rect);
mdc.FillRect(client_rect,bkg_brush_);
mdc.SelectFont(fontNormal_);
int y = 100;
for (auto i = 0; i < files.size(); ++i) {
auto& one = files.at(i);
CSize sizeOne;
mdc.GetTextExtent(one.c_str(), one.size(), &sizeOne);
CRect rectOne;
rectOne.left = 100;
rectOne.top = y;
rectOne.right = rectOne.left + sizeOne.cx;
rectOne.bottom = rectOne.top + sizeOne.cy;
mdc.DrawText(one.c_str(), one.size(), &rectOne,DT_LEFT);
y = rectOne.bottom + 8;
}
//TODO: Add your drawing code here
return 0;
}
int CView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
bkg_brush_ = CreateSolidBrush(RGB(255,255,255));
fontNormal_ = GetHFONT(22, DEFAULT_CHARSET, false, L"Arial");
// 1.添加过滤
if (IsCurrentUserLocalAdministrator()) {
ChangeWindowMessageFilterEx(m_hWnd,WM_DROPFILES,MSGFLT_ALLOW,NULL);
ChangeWindowMessageFilterEx(m_hWnd,WM_COPYDATA - 1,MSGFLT_ALLOW,NULL);
}
// 2. 允许响应文件拖放的消息WM_DROPFILES
DragAcceptFiles(TRUE);
return 0;
}
void CView::OnDropFiles(HDROP hDrop)
{
TCHAR szFilePathName[_MAX_PATH+1] = {0};
files.clear();
UINT nFileCount = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);
for (UINT nIndex = 0; nIndex < nFileCount; ++nIndex) {
DragQueryFile(hDrop, nIndex, szFilePathName, _MAX_PATH);
files.push_back(szFilePathName);
}
DragFinish(hDrop);
InvalidateRect(NULL);
}
下载地址
https://download.youkuaiyun.com/download/infoworld/90476733