- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
免责声明 。
使用者本人对于传播和利用本公众号提供的信息所造成的任何直接或间接的后果和损失负全部责任。公众号及作者对于这些后果不承担任何责任。如果造成后果,请自行承担责任。谢谢! 。
大家好,我是沙漠尽头的狼.
本文首发于 Dotnet9 ,结合前面两篇( 如何在没有第三方.NET库源码的情况下调试第三库代码? 和 拦截、篡改、伪造.NET类库中不限于public的类和方法 ),本文将设计一个案例,手把手地带大家应用这两篇文章中涉及的技能,并介绍一种支持多个版本的库的兼容性解决方案(涉及第三方库的反编译和强签名).
本文的目录如下:
技术的存在即合理,关键在于如何使用。在前面的文章中,有读者留言:
Lib.Harmony似乎不是一个正经的库,有什么合法的场景需要使用它吗?
站长回答: 非常正经 。当你使用一个第三方库,并且确定了版本并已经上线,有时候不能随意升级第三方库,因为可能存在潜在的风险。这时,你只能修改自己的代码,而不动第三方库.
还有读者说得很有道理:
这个工具非常强大,但有时也很可怕.
既然读者有疑问,所以我写了这篇文章,尽量模拟一个看起来比较实际的应用场景。你可以跟着做一做,看看这个工具到底是不是正经的。本文提供了详细的手把手教程.
这是一个小动画游戏,我已经将其发布到NuGet上: Dotnet9Games 。在这个小动画游戏中,我设置了两个陷阱。我们将按照我的步骤一一解决这些问题。首先,我们创建一个 .NET Framework 4.6.1 的WPF空项目【Dotnet9Playground】。我认为大部分人都会使用这个版本的桌面应用程序,如果不是,请在评论中告诉我.
我已经将制作好的(虚构的)游戏发布在[NuGet]( NuGet Gallery | Dotnet9Games 1.0.2 )上作为第三方包使用。为了模拟一个比较真实的场景,直接安装最新版本即可:
打开 MainWindow.xaml ,引入 Dotnet9Games 命名空间:
xmlns:dotnet9="https://dotnet9.com"
MainWindow.xaml 完整代码如下:
<Window
x:Class="Dotnet9Playground.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:dotnet9="https://dotnet9.com"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="综合小案例:模拟.NET应用场景,综合应用反编译、第三方库调试、拦截、一库多版本兼容"
Width="800"
Height="450"
Background="Bisque"
Icon="Resources/favicon.ico"
mc:Ignorable="d">
<Border Padding="10">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel
Grid.Row="0"
VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock
VerticalAlignment="Center"
FontSize="20"
Foreground="Blue"
Text="生成" />
<TextBox
x:Name="TextBoxBallCount"
Width="50"
Height="25"
Margin="10,0"
VerticalAlignment="Center"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
FontSize="20"
Foreground="Red"
Text="{Binding ElementName=MyBallGame, Path=BallCount, Mode=TwoWay}" />
<TextBlock
Margin="0,0,10,0"
VerticalAlignment="Center"
FontSize="20"
Foreground="Blue"
Text="个气球,点击" />
<Button
Padding="15,2"
Background="White"
BorderBrush="DarkGreen"
BorderThickness="2"
Click="StartGame_OnClick"
Content="开始游戏"
FontSize="20"
Foreground="DarkOrange" />
</StackPanel>
<dotnet9:BallGame
x:Name="MyBallGame"
Grid.Row="1"
BallCount="8" />
</Grid>
</Border>
</Window>
MainWindow.xaml.cs 代码如下:
using System.Windows;
namespace Dotnet9Playground;
/// <summary>
/// 综合小案例:模拟.NET应用场景,综合应用反编译、第三方库调试、拦截、一库多版本兼容
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void StartGame_OnClick(object sender, RoutedEventArgs e)
{
MyBallGame.StartGame();
}
}
准备操作完成,运行程序:
这个游戏比较简单,主要包含以下几个步骤:
BallCount
属性。 开始游戏
按钮,点击按钮后会触发 MyBallGame.StartGame()
方法,用于生成气球并播放动画。 气球生成8个可能太少了,让我们来生成80个气球吧:
怎么弹出一个红色的大圆,气球都消失了?这就是陷阱! 。
输入 80 个气球后,我们点击 开始游戏 是调用了游戏的方法 StartGame() , 我们打开[dnSpy]( Releases · dnSpyEx/dnSpy (github.com) )(这个链接提供32位和64位下载链接),拖入 Dotnet9Games.dll ,找到该方法代码:
// Token: 0x06000022 RID: 34 RVA: 0x000022AC File Offset: 0x000004AC
public void StartGame()
{
bool flag = this.BallCount > 9;
if (flag)
{
this.PlayBrokenHeartAnimation();
}
else
{
this.GenerateBalloons();
}
}
原来是当气球个数多于9个时调用了 PlayBrokenHeartAnimation() 方法,这个方法干啥的呢?看代码:
大致看出来了吗?首先是清空气球控件,然后又添加了一个红色的圆动画,我们调试试试呢?
大致说下步骤:
StartGame()
方法第一行打上断点; dnSpy
【启动】按钮; .NET Framework
,"可执行程序"选择我们的WPF主程序Exe【Dotnet9Playground.exe】,再点击【确定】即将WPF程序运行起来了; PlayBrokenHeartAnimation()
方法
明白了原因,我们使用 Lib.Harmony 拦截 StartGame() 方法.
我们安装最低版本 1.2.0.1 :
为啥是安装最低版本?
为了后面引入一库多版本兼容需求,低版本的 Lib.Harmony 有Bug,我们继续,哈哈.
添加拦截类“/Hooks/HookBallGameStartGame.cs”:
using Dotnet9Games.Views;
using Harmony;
using System.Reflection;
namespace Dotnet9Playground.Hooks;
internal class HookBallGameStartGame
{
/// <summary>
/// 拦截游戏的开始方法StartGame
/// </summary>
public static void StartHook()
{
var harmony = HarmonyInstance.Create("https://dotnet9.com/HookBallGameStartGame");
var hookClassType = typeof(BallGame);
var hookMethod =
hookClassType!.GetMethod(nameof(BallGame.StartGame), BindingFlags.Public | BindingFlags.Instance);
var replaceMethod = typeof(HookBallGameStartGame).GetMethod(nameof(HookStartGame));
var replaceHarmonyMethod = new HarmonyMethod(replaceMethod);
harmony.Patch(hookMethod, replaceHarmonyMethod);
}
/// <summary>
/// StartGame替换方法
/// </summary>
/// <param name="__instance">BallGame实例</param>
/// <returns></returns>
public static bool HookStartGame(ref object __instance)
{
#region 原方法原代码
//if (BallCount > 9)
//{
// // 播放爆炸动画效果
// PlayExplosionAnimation();
//}
//else
//{
// // 生成彩色气球
// GenerateBalloons();
//}
#endregion
#region 拦截替换方法逻辑
// 1、删除气球个数限制逻辑
// 2、生成气球方法为private修饰,我们通过反射调用
var instanceType = __instance.GetType();
var hookGenerateBalloonsMethod =
instanceType.GetMethod("GenerateBalloons", BindingFlags.Instance | BindingFlags.NonPublic);
// 生成彩色气球
hookGenerateBalloonsMethod!.Invoke(__instance, null);
#endregion
return false;
}
}
上面的代码加了相关的注释,这里再提一提:
StartHook()
方法用于关联被拦截方法 StartGame
与拦截替换方法 HookStartGame
; HookStartGame
是拦截替换方法,方法中注释的代码为原方法逻辑代码; if (BallCount > 9)
判断,改为直接调用气球生成方法 GenerateBalloons
。 在 App.xaml.cs 注册上面的拦截类:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
// 拦截气球动画播放方法
HookBallGameStartGame.StartHook();
}
}
现在再运行WPF程序,我们把气球个数改为80个,正常生成了:
看着气球在动,我们缩放下窗体大小(这里建议Debug下尝试,因为程序会崩溃,导致操作系统会卡那么一小会儿):
程序异常了,再截图看看:
贴上异常代码:
/// <summary>
/// 重写MeasureOverride方法,引出Size参数为负数异常
/// </summary>
/// <param name="constraint"></param>
/// <returns></returns>
protected override Size MeasureOverride(Size constraint)
{
// 计算最后一个元素宽度,不需要关注为什么这样写,只是为了引出Size异常使得
var lastChild = _balloons.LastOrDefault();
if (lastChild != null)
{
var remainWidth = ActualWidth;
foreach (var balloon in _balloons)
{
remainWidth -= balloon.Shape.Width;
}
lastChild.Shape.Measure(new Size(remainWidth, lastChild.Shape.Height));
}
return base.MeasureOverride(constraint);
}
分析 。
BallGame
的 MeasureOverride
方法会触发,对布局进行重新计算; BallGame
的实际宽度减去所有子气球的宽度之间的差,得到 remainWidth
; remainWidth
重新计算最后一个气球的大小; remainWidth
在做减法操作,那么气球个数足够多,以致于游戏控件宽度小于这些气球宽之和时,就会为负数; Size
构造函数代码(如果你用的VS,这里推荐大家安装ReSharper,十分方便的查看引用库方法 ),如下截图:
代码复制过来看:
/// <summary>Implements a structure that is used to describe the <see cref="T:System.Windows.Size" /> of an object. </summary>
[TypeConverter(typeof (SizeConverter))]
[ValueSerializer(typeof (SizeValueSerializer))]
[Serializable]
public struct Size : IFormattable
{
// 这里省略N多代码
/// <summary>Initializes a new instance of the <see cref="T:System.Windows.Size" /> structure and assigns it an initial <paramref name="width" /> and <paramref name="height" />.</summary>
/// <param name="width">The initial width of the instance of <see cref="T:System.Windows.Size" />.</param>
/// <param name="height">The initial height of the instance of <see cref="T:System.Windows.Size" />.</param>
public Size(double width, double height)
{
this._width = width >= 0.0 && height >= 0.0 ? width : throw new ArgumentException(MS.Internal.WindowsBase.SR.Get("Size_WidthAndHeightCannotBeNegative"));
this._height = height;
}
// 这里省略N多代码
}
当宽高为负数时会抛出异常,这就能理解了,我们再使用 Lib.Harmony 拦截 BallGame 的 MeasureOverride 方法,如法炮制.
添加 /Hooks/HookBallgameMeasureOverride.cs 类拦截:
using Dotnet9Games.Views;
using Harmony;
using System.Reflection;
namespace Dotnet9Playground.Hooks;
/// <summary>
/// 拦截BallGame的MeasureOverride方法
/// </summary>
internal class HookBallgameMeasureOverride
{
/// <summary>
/// 拦截游戏的MeasureOverride方法
/// </summary>
public static void StartHook()
{
var harmony = HarmonyInstance.Create("https://dotnet9.com/HookBallgameMeasureOverride");
var hookClassType = typeof(BallGame);
var hookMethod = hookClassType!.GetMethod("MeasureOverride", BindingFlags.NonPublic | BindingFlags.Instance);
var replaceMethod = typeof(HookBallgameMeasureOverride).GetMethod(nameof(HookMeasureOverride));
var replaceHarmonyMethod = new HarmonyMethod(replaceMethod);
harmony.Patch(hookMethod, replaceHarmonyMethod);
}
/// <summary>
/// MeasureOverride替换方法
/// </summary>
/// <param name="__instance">BallGame实例</param>
/// <returns></returns>
public static bool HookMeasureOverride(ref object __instance)
{
// 暂时不做任何处理,返回false表示
return false;
}
}
再在 App.xaml.cs 添加拦截注册:
using Dotnet9Playground.Hooks;
using System.Windows;
namespace Dotnet9Playground
{
/// <summary>
/// App.xaml 的交互逻辑
/// </summary>
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
// 拦截气球动画播放方法
HookBallGameStartGame.StartHook();
// 这是第二个拦截方法:拦截气球MeasureOverride方法
HookBallgameMeasureOverride.StartHook();
}
}
}
再运行程序:
拦截方法进入了断点,但无法获取 BallGame 的实例,提示 无法读取内存 ,拦截方法返回False(不执行原方法)有下面的异常:
这时程序异常退出,我们将拦截方法返回True(继续执行原方法),又有提示:
因为继续执行原方法,取最后一个气球方法又报错 var lastChild = _balloons.LastOrDefault(); ,好无奈呀,心酸.
经过公司专家指点:
因为Size是个结构体指针,0Harmony 1.2.0.1版本把指针当成4位,但“我们的程序”是64位,指针是8位,所有内存错了.
好,那我们使用高版本 Lib.Harmony ?
理由 。
有可能程序中使用低版本的 Lib.Harmony 库做了不少拦截操作,贸然全部升级,测试不到位,容易出现程序大崩溃(当前本程序只加了一个 HookBallGameStartGame 拦截类),而工程 Dotnet9Playground 直接引入同一个库多版本无法实现(网友如果有建议欢迎留言).
添加新类库“Dotnet9HookHigh”,并使用 NuGet 安装 2.2.2 稳定最新版 Lib.Harmony 库:
同时也添加 Dotnet9Games 的 NuGet 包,将前面添加的 HookBallgameMeasureOverride 类剪切到该库, Lib.Harmony 高版本用法与低版本有所区别,在代码中有注释,注意对比,升级后的 HookBallgameMeasureOverride 类定义:
using Dotnet9Games.Views;
using HarmonyLib;
using System.Reflection;
namespace Dotnet9HookHigh;
/// <summary>
/// 拦截BallGame的MeasureOverride方法
/// </summary>
public class HookBallgameMeasureOverride
{
/// <summary>
/// 拦截游戏的MeasureOverride方法
/// </summary>
public static void StartHook()
{
//var harmony = HarmonyInstance.Create("https://dotnet9.com/HookBallgameMeasureOverride");
// 上面是低版本Harmony实例获取代码,下面是高版本
var harmony = new Harmony("https://dotnet9.com/HookBallgameMeasureOverride");
var hookClassType = typeof(BallGame);
var hookMethod = hookClassType!.GetMethod("MeasureOverride", BindingFlags.NonPublic | BindingFlags.Instance);
var replaceMethod = typeof(HookBallgameMeasureOverride).GetMethod(nameof(HookMeasureOverride));
var replaceHarmonyMethod = new HarmonyMethod(replaceMethod);
harmony.Patch(hookMethod, replaceHarmonyMethod);
}
/// <summary>
/// MeasureOverride替换方法
/// </summary>
/// <param name="__instance">BallGame实例</param>
/// <returns></returns>
public static bool HookMeasureOverride(ref object __instance)
{
return false;
}
}
区别如下图, Harmony 实例获取代码有变化,其它不变:
主工程 Dotnet9Playground 添加 Dotnet9HookHigh 工程的引用, App.xaml.cs 中添加引用 HookBallgameMeasureOverride 命名空间: using Dotnet9HookHigh; ,代码如下:
using Dotnet9HookHigh;
using Dotnet9Playground.Hooks;
using System.Windows;
namespace Dotnet9Playground
{
/// <summary>
/// App.xaml 的交互逻辑
/// </summary>
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
// 拦截气球动画播放方法
HookBallGameStartGame.StartHook();
// 这是第二个拦截方法:拦截气球MeasureOverride方法
HookBallgameMeasureOverride.StartHook();
}
}
}
这就完了?运行试试:
这提示是指我的新工程 Dotnet9HookHigh 未成功应用高版本 Lib.Harmony (2.2.2),亦指主工程 Dotnet9Playground 未成功识别加载高版本 Lib.Harmony ,怎么办?看我接下来的表演! 。
程序输出目录只有一个 0Harmony.dll ,高低2个版本应该是两个库才对,怎么办?
低版本不变(存在位置依然放输出目录的根目录),为了兼容,我们把高版本改目录存放,比如: Lib/Lib.Harmony/2.2.2/0Harmony.dll ,将库按目录结构存放在工程 Dotnet9HookHigh 中:
0Harmony.dll
的属性【复制到输出目录】设置为【如果较新则复制】 Dotnet9HookHigh
对 Lib.Harmony
库的 NuGet
引用,改为本地引用(原来的配方,浏览本地路径的方式);
这就完了吗?咋还是报那个错?
App.config
配置多版本 修改 Dotnet9Palyground 的 App.config 文件,添加 0Harmony.dll 两个版本及读取位置:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
</startup>
<runtime>
<assemblyBinding>
<dependentAssembly>
<assemblyIdentity name="0Harmony"
publicKeyToken="null"/>
<codeBase version="1.2.0.1" href="0Harmony.dll" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="0Harmony"
publicKeyToken="null"/>
<codeBase version="2.2.2.0" href="Lib\Lib.Harmony\2.2.2\0Harmony.dll" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
再运行,还是报上面的错?啊,我要晕了。。。.
上面分目录、配置文件版本配置目录也还不够,主工程还是无法区分两个版本的 Lib.Harmony 库,这里涉及.NET 库强签名,就是上面 App.config 配置中的 publicKeyToken 特性,加上这个主程序就认识了,关于强签名网上找到个说明[《 .Net程序集强签名详解 》]( .Net程序集强签名详解_51CTO博客_.net 签名 ):
可以将强签名的dll注册到GAC,不同的应用程序可以共享同一dll.
强签名的库,或者应用程序只能引用强签名的dll,不能引用未强签名的dll,但是未强签名的dll可以引用强签名的dll.
强签名无法保护源代码,强签名的dll是可以被反编译的.
强签名的dll可以防止第三方恶意篡改.
这里,对于1.2.0.1版本的 0Harmony.dll 库我们依然不动,只对 2.2.2 高版本做强签名处理,签名步骤参考[ VS2008版本引入第三方dll无强签名 ],我们来一起做一遍,这里会借助 Everything 软件搜索使用到的命令程序,建议提前下载.
注意:暂时不要用最新预览版 2.3.0-prerelease.2 ,站长做这个示例签名用这个版本花了2个晚上没成功,换成 2.2.2 就可以,下面的图也重新录了,可能该版本有其他依赖的缘故,只是猜测:
0Harmony.snk
使用 Everything 查找一个 sn.exe 程序,随便使用一个,比如: "C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\sn.exe" ,在高版本目录下生成一个密钥对文件 0Harmony.snk ,命令如下:
"C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\sn.exe" -k "F:\github_gitee\TerminalMACS.ManagerForWPF\src\Demo\MultiVersionLibrary\Dotnet9HookHigh\Lib\Lib.Harmony\2.2.2\0Harmony.snk"
0Harmony.dll
查找 ildasm.exe ,比如 C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\ildasm.exe ,执行以下命令生成 0Harmony.dll 的il中间文件:
"C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\ildasm.exe" "F:\github_gitee\TerminalMACS.ManagerForWPF\src\Demo\MultiVersionLibrary\Dotnet9HookHigh\Lib\Lib.Harmony\2.2.2\0Harmony.dll" /out="F:\github_gitee\TerminalMACS.ManagerForWPF\src\Demo\MultiVersionLibrary\Dotnet9HookHigh\Lib\Lib.Harmony\2.2.2\0Harmony.il"
查找 ilasm.exe ,比如 C:\Windows\Microsoft.NET\Framework64\v2.0.50727\ilasm.exe ,执行以下命令做签名:
"C:\Windows\Microsoft.NET\Framework64\v2.0.50727\ilasm.exe" "F:\github_gitee\TerminalMACS.ManagerForWPF\src\Demo\MultiVersionLibrary\Dotnet9HookHigh\Lib\Lib.Harmony\2.2.2\0Harmony.il" /dll /resource="F:\github_gitee\TerminalMACS.ManagerForWPF\src\Demo\MultiVersionLibrary\Dotnet9HookHigh\Lib\Lib.Harmony\2.2.2\0Harmony.res" /key="F:\github_gitee\TerminalMACS.ManagerForWPF\src\Demo\MultiVersionLibrary\Dotnet9HookHigh\Lib\Lib.Harmony\2.2.2\0Harmony.snk" /optimize
"C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\sn.exe" -v "F:\github_gitee\TerminalMACS.ManagerForWPF\src\Demo\MultiVersionLibrary\Dotnet9HookHigh\Lib\Lib.Harmony\2.2.2\0Harmony.dll"
也可将生成的 dll 拖入 dnSpy 查看:
做为对比,查看NuGet下载的 Lib.Harmony 是没做签名的:
我们将签名补充进 App.Config 文件.
注意 :因为我们使用的随机密钥对,所以您生成的签名和我的肯定不一样:
再调试,能正常拦截 MeasureOverride 方法了,传入的实例也能正常显示 BallGame (就这?对,我搞了2个晚上。。。。):
代码如下:
using Dotnet9Games.Views;
using HarmonyLib;
using System.Collections;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Shapes;
namespace Dotnet9HookHigh;
/// <summary>
/// 拦截BallGame的MeasureOverride方法
/// </summary>
public class HookBallgameMeasureOverride
{
/// <summary>
/// 拦截游戏的MeasureOverride方法
/// </summary>
public static void StartHook()
{
//var harmony = HarmonyInstance.Create("https://dotnet9.com/HookBallgameMeasureOverride");
// 上面是低版本Harmony实例获取代码,下面是高版本
var harmony = new Harmony("https://dotnet9.com/HookBallgameMeasureOverride");
var hookClassType = typeof(BallGame);
var hookMethod = hookClassType!.GetMethod("MeasureOverride", BindingFlags.NonPublic | BindingFlags.Instance);
var replaceMethod = typeof(HookBallgameMeasureOverride).GetMethod(nameof(HookMeasureOverride));
var replaceHarmonyMethod = new HarmonyMethod(replaceMethod);
harmony.Patch(hookMethod, replaceHarmonyMethod);
}
/// <summary>
/// MeasureOverride替换方法
/// </summary>
/// <param name="__instance">BallGame实例</param>
/// <returns></returns>
public static bool HookMeasureOverride(ref object __instance)
{
#region 原方法代码逻辑
//// 计算最后一个元素宽度,不需要关注为什么这样写,只是为了引出Size异常使得
//var lastChild = _balloons.LastOrDefault();
//if (lastChild != null)
//{
// var remainWidth = ActualWidth;
// foreach (var balloon in _balloons)
// {
// remainWidth -= balloon.Shape.Width;
// }
// lastChild.Shape.Measure(new Size(remainWidth, lastChild.Shape.Height));
//}
//return base.MeasureOverride(constraint);
#endregion
#region 拦截替换代码
var instanceType = __instance.GetType();
var balloonsField = instanceType.GetField("_balloons", BindingFlags.NonPublic | BindingFlags.Instance);
var balloons = (IEnumerable)balloonsField!.GetValue(__instance);
var lastChild = balloons.Cast<object>().LastOrDefault();
if (lastChild == null)
{
return false;
}
var remainWidth = ((UserControl)__instance).ActualWidth;
foreach (object balloon in balloons)
{
remainWidth -= GetBalloonSize(balloon).Width;
}
// 注意:关键代码在这,如果剩余宽度大于0才重新计算最后一个子项大小
// 这段代码可能没什么意义,可按实际开发修改
if (remainWidth > 0)
{
var lashShape = GetBalloonShape(lastChild);
lashShape.Measure(new Size(remainWidth, lashShape.Height));
}
#endregion
return false;
}
private static Ellipse GetBalloonShape(object balloon)
{
var shapeProperty = balloon.GetType().GetProperty("Shape");
var shape = (Ellipse)shapeProperty!.GetValue(balloon);
return shape;
}
private static Size GetBalloonSize(object balloon)
{
var shape = GetBalloonShape(balloon);
return new Size(shape.Width, shape.Height);
}
}
其中关键代码是:
// 注意:关键代码在这,如果剩余宽度大于0才重新计算最后一个子项大小
// 这段代码可能没什么意义,可按实际开发修改
if (remainWidth > 0)
{
var lashShape = GetBalloonShape(lastChild);
lashShape.Measure(new Size(remainWidth, lashShape.Height));
}
其他代码就是反射的使用,不再细说,我们运行程序,现在随便缩放窗体了:
当剩余宽度小于0时跳过计算最后一个子项大小 。
上面部分截图中可能您也看到了 0Harmony.ref 文件,我们简单说说.
Git一般是配置成不能上传可执行程序或 dll 文件的,但多版本 dll 特殊,部分库不能直接从 NuGet 引用,所以本文中的高版本 Lib.Harmony 库只能使用自己强签名版本,我们将 dll 文件扩展名改为“.ref"以允许上传,他人能正常使用,程序如果需要正常编译、生成,则给 Dotnet9HookHigh 工程添加生成前命令行,即生成时将 .ref 复制一份为 .dll :
copy "$(ProjectDir)Lib\Lib.Harmony\2.2.2\0Harmony.ref" "$(ProjectDir)Lib\Lib.Harmony\2.2.2\0Harmony.dll"
文中案例写的一般,特别是第二个陷阱,有兴趣可以阅读游戏相关代码,提PR大家一起切磋,把这个案例写的更合理、更有趣、更好玩一点,能让第二个陷阱写一些好玩的特效,拦截后实现不同的效果,这才是拦截的乐趣.
本文通过一个模拟实际案例,帮助大家应用前两篇文章中涉及的技能( dnSpy 调试第三方库和 Lib.Harmony 拦截第三方库),并且介绍一种支持多个版本的库的兼容性解决方案.
通过本文介绍支持多个版本的库的兼容性解决方案,读者可以简单了解如何反编译第三方库,以及如何使用强签名技术来保证库的兼容性(和安全性,本文未展开说,可以阅读此文[浅谈.NET程序集安全签名]( 浅谈.NET程序集安全签名 - 知乎 (zhihu.com) ))。希望本文提供的案例能帮助读者更好地理解和应用这些技能.
谢谢您阅读到这,可以关注【Dotnet9】微信公众号,大家技术交流、保持进步:
最后此篇关于模拟.NET应用场景,综合应用反编译、第三方库调试、拦截、一库多版本兼容方案的文章就讲到这里了,如果你想了解更多关于模拟.NET应用场景,综合应用反编译、第三方库调试、拦截、一库多版本兼容方案的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
是否有任何库或框架旨在促进从另一种成熟的编程语言中构建项目? 在 C++、java 等编程语言中指定逻辑、集合和复杂规则非常容易,但在 Makefile 中完成这些事情似乎是一场艰苦的战斗。我还没有深
我有这段代码可以用 clang 编译得很好(即使使用 -Weverything),但是 gcc 会发出错误。 #include #include #include using namespace
我有以下 block 头文件 BKE_mesh.h: /* Connectivity data */ typedef struct IndexNode { struct IndexNode *
我在我的一个项目中遇到了一个奇怪的问题。我的代码库依赖于一个外部库,其中包含一个名为 Dataset 的类. Dataset类私有(private)继承自 std::vector (其中 Sample
当使用 gcc、g++ 或 make 在终端中编译一个小型 C 或 C++ 项目时,我收到以下错误: /tmp/ccG1caGi.o: In function `main': main.c:(.tex
我正在尝试从 CVS 为 Windows 上的 Emacs 23.1.50 编译 CEDET,但在“第 6 步:打开 EDE...”时出现错误:“defvar:作为变量的符号值是无效的:cedet-m
我正在(重新)学习编程,我从 C 开始。我的 IDE(如果我可以这么说)是 Windows7 上的 cygwin(32 位)和 Visual-Studio 2010。我总是编译我用 gcc (cygw
我喜欢在模板类中使用本地类来执行类似“static if”的构造。但是我遇到了 gcc 4.8 不想编译我的代码的问题。但是 4.7 可以。 这个例子: #include #include #in
我有一个项目,必须仅使用 java 1.4 进行编译。但我计划使用mockito 编写一些单元测试。我想要一种在 pom 中指定的方法,以便 src/main/java 使用 jdk 1.4 编译,但
我想了解 PHP 编译过程是如何工作的。 假设我有一个名为funcs.php 的文件并且这个文件有三个函数,如果我include 或require 它,所有的在文件加载期间编译三个函数?或者源代码会被
编译工具链 我们写程序的时候用的都是集成开发环境 (IDE: Integrated Development Environment),集成开发环境可以极大地方便我们程序员编写程序,但是配置起来
当我编写一些 Scala 代码时,在尝试编译代码时收到一条奇怪的错误消息。我将代码分解为一个更简单的代码(从语义的角度来看这完全没有意义,但仍然显示了错误)。 scala> :paste // Ent
我正在编译一个 SCSS 文件,它似乎删除了我的评论。我可以使用什么命令来保留所有评论? >SASS input.scss output.css 我在 SCSS 中看到两种类型的注释。 // Comm
这是我的代码: #include typedef struct { const char *description; float value; int age; } swag
当您编译 grails war 时,我知道 .groovy 代码被编译为字节码类文件,但我不明白容器(例如 tomcat)如何在请求 GSP 时知道如何编译它们。容器了解 GSP 吗?安装在服务器上的
我正在努力将多个文件编译成一个通用程序。我收到一个错误: undefined reference to 'pi' 这是我的代码和 Makefile 的框架。我做错了什么?谢谢! 文件:calcPi.c
我尝试使用 LD_PRELOAD 来 Hook sprintf function ,所以我将打印到缓冲区的结果: #define _GNU_SOURCE #include #include int
我正在寻找最简单的方法来自动将 CoffeeScript 重新编译为 JS。 阅读documentation但仍然很难得到我想要的东西。 我需要它来监视文件夹 src/ 中的任何 *.coffee 文
我想使用定制waveformjs 。我发现this on SO但是,我不知道如何编译/安装波形来开始。我从 GitHub 克隆它并进行了更改,但是我不知道如何将其转换为 .js 文件。 最佳答案 为了
很难说出这里问的是什么。这个问题是含糊的、模糊的、不完整的、过于宽泛的或修辞性的,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开它,visit the help center 。 已关
我是一名优秀的程序员,十分优秀!