[WTL/Win32]_[初级]_[管理员模式运行的界面程序不响应WM_DROPFILES消息]

场景

  1. 开发WTL时,有时候会需要界面支持拖放文件的操作,这样能更容易选择文件,比如桌面的文件。某次在运行安装包时,安装完软件之后,软件以管理员模式启动后拖放操作不生效,文件可以拖放,但是界面不响应WM_DROPFILES消息,怎么回事?

说明

  1. 拖放功能是Win32软件必不可少的功能之一,安装完软件后管理员模式启动的软件不能拖放,势必会影响用户的体验。查了下资料,是因为WIN7系统后,微软增加了UI消息隔离机制(UMI),低权限进程无法和高权限进程进行通信。而WM_DROPFILES消息是explorer.exe,即我们说的shell外壳程序发送的。explorer.exe进程并不全是管理员权限,即它是低进程,那么根据UMI。这个消息会被过滤掉,主界面不会收到这个消息。

  2. 要高权限的界面收到WM_DROPFILES消息,那么需要加例外条件。Win32函数提供了ChangeWindowMessageFilter[3]来添加消息过滤规则。Win7 SP1之后需要使用ChangeWindowMessageFilterEx函数, 因为ChangeWindowMessageFilter可能会在某个版本的系统后被废弃掉。

  3. ChangeWindowMessageFilterEx函数可以对某个窗口加消息过滤,而不是旧函数ChangeWindowMessageFilter作用于整个进程。我们可以判断是管理员模式启动的程序再进行调用,也可以不判断。

if (IsCurrentUserLocalAdministrator()) {
	ChangeWindowMessageFilterEx(m_hWnd,WM_DROPFILES,MSGFLT_ALLOW,NULL);
	ChangeWindowMessageFilterEx(m_hWnd,WM_COPYDATA - 1,MSGFLT_ALLOW,NULL);
}
  1. 这里只添加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

在这里插入图片描述

参考

  1. 解决高版本操作系统文件无法拖动问题;

  2. WM_DROPFILES消息

  3. ChangeWindowMessageFilter 函数

  4. ChangeWindowMessageFilterEx 函数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白行微

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值