存档

文章标签 ‘UWP’

UWP中为 WinRT 组件创建 NuGet 包

2026年9月6日 没有评论

这篇文章可以说是迟到了 N 多年的分享了,一直放在草稿箱里但是没有发布,今天赶紧地整理。

先搞清楚:WinRT 组件里到底有什么

假设我们有一个 WinRT 组件:Demo.winmd、Demo.dll。这两个文件虽然经常一起出现,但职责完全不同。.winmdWindows Metadata 文件,它描述的是这个 WinRT 组件对外暴露的类型信息,比如:

namespace
class
interface
method
property
enum

WinRT 的语言投影也依赖这些 metadata。比如 C++/WinRT 会根据 WinRT metadata 生成对应的 C++ 投影代码。所以可以简单理解成:Demo.winmd 告诉编译器“这个组件长什么样”,它本身并不是业务代码的实现。.dll 才是实际的 native implementation。也就是说:Demo.dll 真正执行代码。对于一个典型的 WinRT Component,这两个文件是配套的。

Visual Studio 在构建 Windows Runtime Component 时,会生成 WinRT metadata;对于 C++/WinRT 项目,工具链也是先处理 IDL 生成 .winmd,再基于 metadata 生成相应的投影代码。

不要把 .winmd 理解成普通 .NET 程序集,也不要把它和 DLL 当成两个可以随便替换的文件。它们共同描述并实现了一个 WinRT 组件。

NuGet 包里应该放什么?

对于这个例子,一个最基本的 NuGet 包可以长这样:

com.xx.demo.nupkg
│
├─ lib
│  └─ uap10.0
│     └─ Demo.winmd
├─ runtimes
│  ├─ win10-x86
│  │  └─ native
│  │     └─ Demo.dll
│  └─ win10-x64
│     └─ native
│        └─ Demo.dll
└─ build
   └─ native
      └─ com.xx.demo.targets

这里其实就是整个事情最关键的部分,.nuspec 本身反而没什么好讲的——如果你平时用 NuGet,知道怎么写一个基本的 .nuspec 就够了。

为什么 .winmd.dll 要放在不同目录?

这是很多第一次做 native NuGet 包的人容易疑惑的地方。.winmd 是给编译阶段使用的,所以我们把它放到 lib/uap10.0/,而 DLL 属于 native runtime binary,因此放到:runtimes/win10-x64/native/、runtimes/win10-x86/native/。

这样做的一个直接好处就是:同一个 NuGet 包可以同时支持多个 CPU 架构。微软的 UWP NuGet 示例也是把 WinMD 放到 lib\uap10.0,native DLL 放到 runtimes 对应的架构目录。 Microsoft Learn+1

C++ 项目为什么还需要 .targets

NuGet 对 native C++ 包的处理方式,本身就和 .NET 程序集不太一样。微软的 native NuGet 文档也明确提到,native 包通常通过 buildcontenttools 等目录以及 MSBuild 的 .props/.targets 来完成项目集成,而不是依赖 lib 直接添加 C++ Reference。 所以我们通常需要提供一个:com.xx.demo.targets。它的作用可以简单理解成:告诉 MSBuild:这个 NuGet 包里的 WinMD 和 DLL 怎么添加到当前项目。

一个简化后的思路大概是:

<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'UAP'">
    <Reference Include="
        $(MSBuildThisFileDirectory)
        ..\..\lib\uap10.0\Demo.winmd">

        <Implementation>Demo.dll</Implementation>
    </Reference>
    <ReferenceCopyLocalPaths Include="
        $(MSBuildThisFileDirectory)
        ..\..\runtimes\win10-$(Platform)\native\Demo.dll" />
</ItemGroup>

.nuspec 不需要写得很复杂

如果已经熟悉 NuGet,可以把它理解成一个文件映射:

<files>
    <file
        src=".\Demo.winmd"
        target="lib\uap10.0" />
    <file
        src=".\Demo.dll"
        target="runtimes\win10-x64\native" />
    <file
        src=".\com.xx.demo.targets"
        target="build\native" />
</files>

如果还要支持 x86,就再放一个对应的 DLL:runtimes/win10-x86/native/Demo.dll。

这里最重要的不是 XML 本身,而是记住:

WinMD
    → lib/uap10.0
Native DLL
    → runtimes/<RID>/native
MSBuild 集成
    → build/native

XML 文档要不要一起打包?

如果组件是给别人使用的,比较建议加上。里面保存 C++/C# 的 API 文档注释,这样 IDE 可以显示对应的 API 注释。这个东西不影响组件运行,但对 SDK 类组件来说体验差别还是挺大的。

分类: 日常 标签: ,

判断某个 UWP 程序是否已经安装

2021年1月20日 没有评论

在一些代码逻辑里,经常需要判断指定的 UWP程序是否已经安装,其实微软已经提供了相应的API了,无论是Win32还是原生的 UWP程序都可以直接使用,这就是 PackageManager,其文档地址见这里

但是这个类,微软给添加了一个标记 [Windows.Foundation.Metadata.WebHostHidden],所以如果是网页里就无法调用了。为了规避掉这个问题,发现了一个类似hack 的方式。

就是使用 QueryUriSupportAsync 方法,向请求的UWP程序发送一个不存在的协议字符串,然后检查其返回值。返回的是一个枚举类型,也就是说,如果是 0(已安装)或者3(不支持该协议),就表明这个程序对于当前用户来说是已安装的。

下面是一段 js 的调用示例代码:

static queryUriSupport(uri, packageName) {
    return Windows.System.Launcher.queryUriSupportAsync(
        new Windows.Foundation.Uri(uri),
        0,
        packageName
    );
}

简单、暴力、直接,恰好解决了网页里的权限问题。

分类: 日常 标签:

根据进程获取 UWP 的包信息

2020年5月15日 没有评论

最近的一个实验项目,要监控UWP程序的启动。发现了一个比较简单的方式是使用 WMI,当有新的进程被创建时,就可以收到一个消息回调,这样的方式比不断地去轮询要优雅和有效率的多。

但是我们的目标UWP,是采用 WinJS开发的,这样会带来一个问题。就是在进程列表里,它是托管在一个叫做 wwahost.exe 的进程里的。如果有多个类似的程序的话,我们无法简单地区分出来。

所以我们需要进一步获取详细信息以便做过滤,幸好微软提供了配套的东西。我们可以使用一个叫做 UserModelId的东西来对不同的UWP程序做区分。下面是来自微软官方的实例代码(https://docs.microsoft.com/zh-cn/windows/win32/api/appmodel/nf-appmodel-getapplicationusermodelid)。

#define _UNICODE 1
#define UNICODE 1

#include <Windows.h>
#include <appmodel.h>
#include <malloc.h>
#include <stdlib.h>
#include <stdio.h>

int ShowUsage();
void ShowProcessApplicationUserModelId(__in const UINT32 pid, __in HANDLE process);

int ShowUsage()
{
    wprintf(L"Usage: GetApplicationUserModelId <pid> [<pid>...]\n");
    return 1;
}

int __cdecl wmain(__in int argc, __in_ecount(argc) WCHAR * argv[])
{
    if (argc <= 1)
        return ShowUsage();

    for (int i=1; i<argc; ++i)
    {
        UINT32 pid = wcstoul(argv[i], NULL, 10);
        if (pid > 0)
        {
            HANDLE process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
            if (process == NULL)
                wprintf(L"Error %d in OpenProcess (pid=%u)\n", GetLastError(), pid);
            else
            {
                ShowProcessApplicationUserModelId(pid, process);
                CloseHandle(process);
            }
        }
    }
    return 0;
}

void ShowProcessApplicationUserModelId(__in const UINT32 pid, __in HANDLE process)
{
    wprintf(L"Process %u (handle=%p)\n", pid, process);

    UINT32 length = 0;
    LONG rc = GetApplicationUserModelId(process, &length, NULL);
    if (rc != ERROR_INSUFFICIENT_BUFFER)
    {
        if (rc == APPMODEL_ERROR_NO_APPLICATION)
            wprintf(L"Desktop application\n");
        else
            wprintf(L"Error %d in GetApplicationUserModelId\n", rc);
        return;
    }

    PWSTR fullName = (PWSTR) malloc(length * sizeof(*fullName));
    if (fullName == NULL)
    {
        wprintf(L"Error allocating memory\n");
        return;
    }

    rc = GetApplicationUserModelId(process, &length, fullName);
    if (rc != ERROR_SUCCESS)
        wprintf(L"Error %d retrieving ApplicationUserModelId\n", rc);
    else
        wprintf(L"%s\n", fullName);

    free(fullName);
}

整体的代码比较简单,没有什么特别好说的。代码中一共调用了2次GetApplicationUserModelId方法,第一次是为了计算数据的长度,然后第二次根据数据长度重新请求以得到最终的数据。

这就是我为什么讨厌C++的原因,把指针的操作搞这么恶心。

在代码验证后,又发现了另外一个API:IsImmersiveProcess ,通过该函数可以简单地判断一个进程是否是Store类型的程序,在有的场合例如唯一性校验上还是有帮助的。有兴趣可以移步 https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-isimmersiveprocess

分类: C++, 一句话, 日常 标签: , ,

Win10 UWP 默认控件样式和模板

2017年1月17日 没有评论

很多时候发现设计给出的文档,系统默认的控件并不能直接使用。对于复杂的控件,可以新写一个自定义控件,但是对于一些很简单的控件,就想在系统默认的控件基础上通过调整样式来实现。

那么我们就需要知道系统默认控件的布局和样式是如何定义的,幸好微软已经给出了 UWP 控件的默认样式和模板。

https://msdn.microsoft.com/zh-cn/library/windows/apps/mt299122.aspx

大家一定要好好保存喔,方便查询使用。

分类: 一句话 标签:

win10修复UWP程序无法联网的问题

2017年1月3日 没有评论

衣服自己洗的办公机已经有一段时间不能使用UWP程序上网了,包括微软自带的应用商店,然而win32桌面程序却没有这个问题,很是恼火。

于是自己写了一个测试程序,发现代码会崩溃在

Windows.Networking.Connectivity.NetworkInformation.GetHostNames();

后来在某一个偶然的时机,发现如果开着 Fiddler的情况下,应用商店就可以正常访问了,太神奇了。这么看起来似乎是系统代理的问题导致,但是其它的UWP程序仍然无法上网。

在网上搜索了一番,各种所谓的重置答案都试过,均没有效果。何况,工作中可是直接和UWP相关,这个出问题了那简直是要了老命了。

趁着元旦放假的工夫,想在家尝试解决,后来还是放弃了。突然灵光一闪,听说windows在每次大的更新后,都会重置系统的一些设置,就寻思着从这块下手。

好在上次下载的14393的系统镜像还在,就以升级的方式重新安装了一遍系统,各位同学这里可要记住,千万要勾选“保留个人文档和程序”选项。升级后,终于恢复正常了。剩下的例如文件关联之类的都是小问题,以后慢慢回填就可以了。

回想起整个事情,可能出现的情况,感觉问题可能是出现在科学上网上面,以前曾经把玩过一段时间的蓝灯程序,后来购买了shadowsocks后,就卸载了蓝灯。估计是卸载没有完全卸载干净导致。

虽然代价有点大,但好歹也是终于修复了这个问题。

分类: 一句话 标签:

WUP Slider控件PointerPressed 和 PointerReleased 事件无法触发

2016年11月9日 没有评论

在最近的某个界面上,我们需要添加一个滑动条Slider,并且根据业务需求获取用户点击和松开鼠标的时机。衣服自己洗发现对于通用控件的PointerPressed 和 PointerReleased 事件是无法触发的。

来自微软方面的解释是这2个事件被Slider的子控件给处理了,导致事实上这2个事件无法被我们拦截处理。

解决方法也很简单:

1,为Slider控件设置ManipulationMode属性,其枚举值为Windows.UI.Xaml.Input.ManipulationModes.TranslateRailsX。

2,为控件添加 ManipulationStarted 和ManipulationCompleted 事件,以代替 PointerPressed 和 PointerReleased 事件。

现在大家就可以解决自己的需求了。

分类: 一句话, 日常 标签: ,