存档

文章标签 ‘C#’

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 类组件来说体验差别还是挺大的。

分类: 日常 标签: ,

C# 切换多屏幕显示模式

2024年5月12日 没有评论

网上搜索了下,有2种方式。

一、直接执行系统自带的程序 DisplaySwitch.exe,它在 windows\system32 目录下。对应的 C# 代码如下:

using (var proc = new Process())
{
proc.StartInfo.FileName = @"DisplaySwitch.exe";
proc.StartInfo.Arguments = "/external";
proc.Start();
}

二、调用系统 API SetDisplayConfig 函数

private const uint SDC_APPLY = 0x00000080;
private const uint SDC_TOPOLOGY_INTERNAL = 0x00000001;
private const uint SDC_TOPOLOGY_CLONE = 0x00000002;
private const uint SDC_TOPOLOGY_EXTERNAL = 0x00000008;
private const uint SDC_TOPOLOGY_EXTEND = 0x00000004;

[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern long SetDisplayConfig(uint numPathArrayElements, IntPtr pathArray, uint numModeArrayElements,IntPtr modeArray, uint flags);

/// /// 设置屏幕的显示模式 ///
/// 模式(0 - 主屏 1 - 双屏复制 2 - 双屏扩展 3 - 第二屏幕
///
public void SetScreenMode(int type)
{
uint mode;

switch (type)
{
    case 0:
        mode = SDC_APPLY | SDC_TOPOLOGY_INTERNAL;
        break;
    case 1:
        mode = SDC_APPLY | SDC_TOPOLOGY_CLONE;
        break;
    case 2:
        mode = SDC_APPLY | SDC_TOPOLOGY_EXTEND;
        break;
    case 3:
        mode = SDC_APPLY | SDC_TOPOLOGY_EXTERNAL;
        break;
    default:
        mode = SDC_APPLY | SDC_TOPOLOGY_INTERNAL;
        break;
}

SetDisplayConfig(0, IntPtr.Zero, 0, IntPtr.Zero, mode);

}

分类: 一句话 标签:

无头浏览器 PuppeteerSharp

2023年9月3日 没有评论

在研究无头浏览器的时候,发现了一些不错的工具,例如 phantomjs 就可以让你以前端 js 脚本的方式来操作页面,以满足一些自动化测试方面的需求。后来又发现了 puppeteer,有很多开发语言的实现。

而今天提及的 PuppeteerSharp 就是 .Net 的实现。可能我执念太深,总觉得 Net 是相当不错的选择。

PuppeteerSharp 是一个控制无头Chrome 浏览器的库,它允许开发者通过编程方式模拟浏览器行为,执行网页操作,截取网页截图,获取网页内容,可以把网页转换成PDF等等。

截图

using var browserFetcher = new BrowserFetcher();
await browserFetcher.DownloadAsync();
await using var browser = await Puppeteer.LaunchAsync(
new LaunchOptions { Headless = true });
await using var page = await browser.NewPageAsync();
await page.GoToAsync("http://www.google.com");
await page.ScreenshotAsync(outputFile);

也支持设置页面的尺寸

await Page.SetViewportAsync(new ViewPortOptions
{
Width = 500,
Height = 500
});

生成 pdf 文件

using var browserFetcher = new BrowserFetcher();
await browserFetcher.DownloadAsync();
await using var browser = await Puppeteer.LaunchAsync(new LaunchOptions {Headless = true});
await using var page = await browser.NewPageAsync();
await page.GoToAsync("http://www.google.com"); // In case of fonts being loaded from a CDN, use WaitUntilNavigation.Networkidle0 as a second param.
await page.EvaluateExpressionHandleAsync("document.fonts.ready"); // Wait for fonts to be loaded. Omitting this might result in no text rendered in pdf.
await page.PdfAsync(outputFile);

动态注入 html 元素

await using var page = await browser.NewPageAsync();
await page.SetContentAsync("My Receipt");
var result = await page.GetContentAsync();

执行脚本

await using var page = await browser.NewPageAsync();
var seven = await page.EvaluateExpressionAsync("4 + 3");
var someObject = await page.EvaluateFunctionAsync("(value) => ({a: value})", 5);
Console.WriteLine(someObject.a);

等待某个选择器

using (var page = await browser.NewPageAsync())
{
await page.GoToAsync("https://www.baidu.com");
await page.WaitForSelectorAsync("div.main-content")
await page.PdfAsync(outputFile));
}

等待函数的执行

using (var page = await browser.NewPageAsync())
{
await page.GoToAsync("https://www.baidu.com");
var watchDog = page.WaitForFunctionAsync("()=> window.innerWidth < 100");
await page.SetViewportAsync(new ViewPortOptions { Width = 50, Height = 50 });
await watchDog;
}


分类: C#, 日常 标签:

SharpShell

2023年7月29日 没有评论

最近在搜索使用C#来实现文件扩展模块,即右键属性对话框里的设置,这个基本上是 com 实现的,当然就是支持 C++,这个东西对我来说过于复杂了,全网搜索了下,最后发现了一个项目满足自己的需求,这里推荐给大家,希望大家喜欢。

https://github.com/dwmkerr/sharpshell#property-sheet-extensions

分类: 一句话, 工具 标签:

.Net 6 使用 Winform API

2022年4月6日 没有评论

最近的项目在计划切换到使用 .net6 来编译,虽然微软官网有提供转换工具,但是作为强迫症人群,还是选择了从0开始创建项目,在创建后,再拷贝相关代码。

很显然的,有一些函数或者命名空间在 .net6里并无法找到对应的,官方也提及,可以先试一试 Microsoft.Windows.Compatibility 这个 NuGet 包。VS2022 的右键菜单里还提供了一个很实用的功能,就是移除未使用的引用,所以大家可以放心地先引用。

如果你的项目使用了注册表,那么就需要添加 Microsoft.Win32.Registry 这个 NuGet 包。

很可惜,我的代码里使用了 System.Windows.Forms 的API去判断电源是 DC 还是 AC 模式,上述都没有很好地解决问题。

经过一番搜索,发现下面的解决方式,
1、右键卸载项目
2、编辑项目的 csproj 文件,在 PropertyGroup 节点下添加 true和true 这两个配置。
3、设置 .net6.0属性,替换为 .net6.0-windows,如果有其它的编译需求,例如 net461也可以加进去,以英文分号分割。
4、重新加载项目

当然,这么做也意味着编译后的二进制文件并不能在 Linux 平台上运行,仅仅意味着使用 .net6编译而已,不过这个对于衣服自己洗来说已经足够了,本身项目就是跑在 windows 平台上的。

分类: 日常 标签:

获取Firefox当前打开的标签页地址

2020年9月3日 没有评论

说起三大浏览器,当然少不了火狐浏览器了。我们继续探讨如何获取该浏览器的当前标签页地址。

首选我们需要下载 NDde.dll 这个文件,添加其引用。

using NDde.Client;

DdeClient dde = new DdeClient("Firefox", "WWW_GetWindowInfo");
dde.Connect();
string url = dde.Request("URL", int.MaxValue);
dde.Disconnect();

MessageBox.Show(url);

虽然是要借助于第三方的库,但是可用快速实现 Demo 也是蛮不错的选择了。

关于NDde,原来的官网已经停止维护了,我看Github上有一个fork该项目的地址,https://github.com/anphonic/NDde 大家可以移步看看。

分类: 一句话 标签:

获取IE当前打开的标签页地址

2020年8月3日 没有评论

在前面的文章中,有分享获取chrome标签页地址的代码。现在,再来看看如何查找到 IE标签页的地址。

首先,我们需要添加2个引用:

1、引用c:\windows\system32\SHDocVw.dll

2、引用COM组件Microsoft HTML Object Library

SHDocVw.ShellWindows sws = new SHDocVw.ShellWindows();
StringBuilder sb = new StringBuilder();
foreach (SHDocVw.InternetExplorer iw in sws)
{
	sb.AppendLine(iw.LocationURL);
}
MessageBox.Show(sb.ToString());

这里iw是所有一打开的IE,包括IE本身或者基于IE内核的企业浏览器,还包括我的电脑打开的所有窗口。
你可根据实际情况筛选掉 file 开头的地址。http开头的都是当前打开的IE的地址。

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

C#中接收系统屏幕锁定和解锁的事件

2019年10月22日 没有评论

最近在做一个 demo,需要去监听用户是否有锁屏,或者注销操作。用 C++ 可以去接收特定的消息去处理,但我总觉得 C# 应该有更好的实现。

搜索了一番,果然找到了,还是微软好呀,啥都为我们考虑到了,搞了一点动力都没有。

为了实现这样的效果,需要使用 Microsoft.Win32.SystemEvents 类,他提供了一些事件,我们可以注册这些事件来完成自己的需求。

Microsoft.Win32.SystemEvents.SessionSwitch += new Microsoft.Win32.SessionSwitchEventHandler(SystemEvents_SessionSwitch);  
void SystemEvents_SessionSwitch(object sender, Microsoft.Win32.SessionSwitchEventArgs e)  
{  
  if (e.Reason == Microsoft.Win32.SessionSwitchReason.SessionLock)  
  {   
    // 屏幕锁定  
  }  
  else if (e.Reason == Microsoft.Win32.SessionSwitchReason.SessionUnlock)  
  {   
    // 屏幕解锁  
  }  
}

是不是很简单?都不需要实例化类。但是需要注意的是,因为是静态类,所以在必要的时候,需要显示地去取消事件注册,以避免内存泄露。

分类: 日常 标签:

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 事件。

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

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

RestSharp 访问https网站出错的问题

2016年6月20日 没有评论

工作中的一个小工具,使用了RestSharp 来向JIRA系统中同步信息。前段时间说是安全升级,更新了jira的ssl证书,结果就导致了同步流程的失败,后来发现原因就在于ssl证书使用了自签名的ssl证书。不知道为什么,我想到了12306的自签名证书。

移除NuGet引用,下载最新版本的源码,重新编译还是一样的问题。

网上也有很多人询问,大部分都可以解决问题的方法如下:

client.RemoteCertificateValidationCallback = new RemoteCertificateValidationCallback((a,b,c,d)=> { return true; });

不过对于我这边没有效果。后来终于在某个小帖子上发现了一句话,一尝试果然解决了问题。

在需要发送请求前,需要先设置协议类型:

ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

从感觉上来说,这个问题应该是仅仅和服务器设置有关,不一定适合所有的玩家。不对,是同学,最近玩游戏有点过份了。

分类: 一句话 标签: , ,