当InternalsVisibleToAttribute特性遭遇"强签名"

一、如何让Intenal成员暴露给另一个程序集

我们知道Modifier为Internal的类型成员仅限于当前程序集能够访问,但是在某些情况下,我们希望将它们暴露给另一个程序集。比较典型的应用场景包括如下两种:

  • 将一个组件或者模块定义成两个或者两个以上程序集,一个程序集需要访问另一个程序集的Internal成员。比如将一个Logging组件定义成三个程序集:Logging.dll、Logging.Client.dll和Logging.Server.dll。其中后两个分别用于客户端和服务端的日志记录,而它们共同依赖的功能定义在Logging.dll中。定义在Logging.dll的API以共有成员的形式公布出来,而一些仅仅需要被Logging.Client.dll和Logging.Server.dll使用的API在定义成Interna成员。
  • 对一个组件或者模块进行单元测试时候,单元测试用例需要调用定义在被测试组件或者模块的Internal成员

举个例子,如右图所示,我将某个组件定义在Lib项目中,而Test是与之对应的单元测试项目。定义在Lib中组建成员的可见性依赖于具体的设计,但是在很多情况下,单元测试用例为了尽可能覆盖较多的分支,需要调用一些Internal成员。比如,设置一些Internal属性,或者调用一些Internal方法。

我在Lib中定义了如下一个表示二维向量的Vector类,其中X和Y属性的Set方法为Internal。

   1: public class Vector
   2: {
   3:     private static void EnsureNotNull(object value, string parameterName)
   4:     {
   5:         if (null == value)
   6:         {
   7:             throw new ArgumentNullException(parameterName);
   8:         }
   9:     }
  10:     public double X { get; internal set; }
  11:     public double Y { get; internal set; }
  12:  
  13:     public Vector(double x, double y)
  14:     {
  15:         this.X = x;
  16:         this.Y = y;
  17:     }
  18:  
  19:     public override bool Equals(object obj)
  20:     {
  21:         Vector vector = obj as Vector;
  22:         if (null == vector)
  23:         {
  24:             return false;
  25:         }
  26:  
  27:         return this.X == vector.X && this.Y == vector.Y;
  28:     }
  29:  
  30:     public static Vector operator + (Vector v1, Vector v2)
  31:     {
  32:         EnsureNotNull(v1, "v1");
  33:         EnsureNotNull(v2, "v2");
  34:         return new Vector(v1.X + v2.X, v1.Y+ v2.Y);
  35:     }
  36:  
  37:     public override int GetHashCode()
  38:     {
  39:         return this.X.GetHashCode() ^ this.Y.GetHashCode();
  40:     }
  41: }

在单元测试项目Test中,定义如下一个VectorFixture类型,用于测试向量相加的逻辑。为了测试方便,我在这里希望直接设置Vector的X和Y属性,而这两个属性的Set方式是Internal的。

   1: [TestClass()]
   2: public class VectorFixture
   3: {
   4:     [TestMethod]
   5:     public void Add()
   6:     {
   7:         var v1 = new Vector(1, 2);
   8:         var v2 = new Vector(3, 4);
   9:         Assert.AreEqual<Vector>(new Vector(4,6),v1+v2);
  10:  
  11:         v1.X = -1;
  12:         v1.Y = -2;
  13:         v2.X = -3;
  14:         v2.Y = -4;
  15:         Assert.AreEqual<Vector>(new Vector(-4, -6), v1 + v2);
  16:     }
  17: }

为了解决这个问题,我想很多人都知道一个特殊的自定义特性(Custom Attribute):System.Runtime.CompilerServices.InternalsVisibleToAttribute。没错,我们只需要在Lib项目的AssemblyInfo.cs添加这个InternalsVisibleToAttribute特性,指定目标程序集(能够访问本程序集的Internal成员的程序集)名称即可。

   1: [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Test)]

二、如果对Lib添加强签名呢?

在很多情况下,我们需要将最终的程序集以强命名的形式发布。为此,我们修改Lib项目设置,开启"Sign the assembly”开关,并创建一个密钥文件。

当完成上面的步骤后,Lib项目将不能通过编译,编译错误如下图所示。具体的错误信息为:“Friend
assembly reference 'Test' is invalid. Strong-name signed assemblies
must specify a public key in their InternalsVisibleTo declarations.”

三、如果在InternalsVisibleToAttribute指定程序集的强名称(Strong Name)呢?

从上面的出错消息中我们不难看出,编译错误的原因是:当自身具有强签名的情况下,通过InternalsVisibleToAttribute指定的程序集也需要具有强签名。那么,如果我们将单元测试项目Test也加上强签名,并将InternalsVisibleToAttribute特性指定成程序集的强名称,是否可以解决这个问题呢?

在对Test项目按照上面的步骤进行强签名后,并重新修改了应用在Lib程序集上的InternalsVisibleToAttribute特性设置,即设置成包含4个部分(名称、版本、语言文化和公钥令牌)的程序集强名称。

   1: [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Test, Version=1.0.0.0, Culture=neutral, PublicKeyToken=8dba6a4f4e33b7dc")]

不幸的是,这会导致另一个编译错误(如下图所示)。具体出错信息为:“Friend
assembly reference 'Test, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=8dba6a4f4e33b7dc' is invalid. InternalsVisibleTo
declarations cannot have a version, culture, public key token, or
processor architecture specified.”。出错信息表明:通过InternalsVisibleToAttribute特性指定的程序集名称的时候,只能指定程序集名称(文件名),不能指定版本、语言文化和公钥令牌。

 

四、需要指定的是完整的公钥

实际上对于上面的情况,需要指定的不是程序名的强命名,而是指定对程序集进行签名时采用的公钥。那么如何得到这个公钥呢?我们可以通过强名称(SN.exe)命令行工具直接将公钥从密钥文件中提取出来。

具体来说我们需要两个步骤:通过SN.exe结合-p开关从将包含公钥/私钥的密钥文件中提取公钥,并导入到指定的密钥文件中;然后执行SN.exe并结合使用-tp开关,将公钥文件中的公钥显示出来。

两个步骤地命令行输入和输出入下所示。其中Test.snk表示对单元测试项目进行签名的密钥文件,而Test.PK.snk则表示导出的只包含公钥的密钥文件。最终控制台显示出我们需要的完整的公钥:“0024000004800000940000000602000000240000525341310004000001000100c9d70c8b6c1eb494b113701099f43ef62efe8c9cf4310bda2061eff1cc91ffda4368848d3283d4d83e63087038e32ea25e0098891608ae48993bf16ea93362d10207de3a4dca263c145a6febf1784401948c2474c3f55713e6b97e9c1c3eef5b8966b879407b955b23404c62cd75fcf3598b6950d104a4ea97209ad051763ca4”

1: C:\Users\jinnan\Documents\Visual Studio 2010\Projects\InternalsVisibility\Test>SN -p Test.snk Test.PK.snk
2: Microsoft (R) .NET Framework Strong Name Utility  Version 4.0.30319.1   Copyright (c) Microsoft Corporation.  All rights reserved.   
3: Public key written to Test.PK.snk   
4: C:\Users\jinnan\Documents\Visual Studio 2010\Projects\InternalsVisibility\Test>SN -tp Test.PK.snk 
5: Microsoft (R) .NET Framework Strong Name Utility  Version 4.0.30319.1 Copyright (c) Microsoft Corporation.  All rights reserved.  
6: Public key is: 0024000004800000940000000602000000240000525341310004000001000100c9d70c8b6c1eb494b113701099f43ef62efe8c9cf4310bda2061eff1cc
7: 91ffda4368848d3283d4d83e63087038e32ea25e0098891608ae48993bf16ea93362d10207de3a4dca263c145a6febf1784401948c2474c3  
8: f55713e6b97e9c1c3eef5b8966b879407b955b23404c62cd75fcf3598b6950d104a4ea97209ad051763ca4  
9: Public key token is 8dba6a4f4e33b7dc

我们只需要将该公钥指定到InternalsVisibleToAttribute特性中即可:

   1: [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Test,PublicKey=0024000004800000940000000602000000240000525341310004000
   2: 001000100c9d70c8b6c1eb494b113701099f43ef62efe8c9cf4310bda2061eff1cc91ffda4368848d3283d4d83e63087038e32ea25e0098891608ae48993bf16ea933
   3: 62d10207de3a4dca263c145a6febf1784401948c2474c3f55713e6b97e9c1c3eef5b8966b879407b955b23404c62cd75fcf3598b6950d104a4ea97209ad051763ca4")]

作者:蒋金楠
微信公众账号:大内老A
微博:www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

原文链接

时间: 2024-11-17 13:29:57

当InternalsVisibleToAttribute特性遭遇"强签名"的相关文章

从编译DotNetOpenAuth中学到的程序集强签名知识

1. 背景 最近在研究DotNetOpenAuth--OAuth的一个.NET开源实现,官方网站:http://dotnetopenauth.net/ . 从GitHub签出DotNetOpenAuth的源代码发现最新版本已到5.1,而NuGet中发布的版本只是4.3.新版中使用到了.NET 4.5的异步特性(async, await),于是决定直接用最新版. 用最新版,就要自己进行编译.用Visual Studio 2012打开解决方案文件进行编译,一次编译成功,但编译出的DotNetOpen

VS2012,输入强签名密码后,提示指定了无效的提供程序类型

问题描述 项目采用了密码签名,由于电脑重装了系统,编译程序时,提示如下错误:于是在VS2012开发人员工具中执行输入强签名的操作,但正确输入密码后,提示如下错误:安装了VS2012SP4后,还是无法解决此问题.求解决办法,谢谢! 解决方案 本帖最后由 miaoxiao423 于 2014-09-24 20:44:30 编辑解决方案二:从下面的图能看出来:未知....无效.....很明显是你导入证书没成功哇,跟VS没什么关系了,解决证书问题先解决方案三:我猜啊是你用的.netFramwork版本不

河南中西部遭遇强雷暴天气(组图)

图为平顶山地区发电厂上空的闪电. 图为发电厂上空的闪电. CFP6月13日报道 2009年6月12日晚,河南中西部遭遇强雷暴天气.图为平顶山地区发电厂上空的闪电.

中国多省市遭遇强寒潮袭击交通受阻电煤告急

2010年1月3日,山东滨州市民冒着严寒出行.当日,山东各地遭遇寒潮袭击.山东省气象台1月2日发布寒潮蓝色预警信号,3日夜间到4日白天,鲁西北东部地区有中到大雪,半岛地区中到大雪局部暴雪.受冷空气影响,山东省大部分地区气温将下降8-10℃,最低气温:鲁西北和鲁中山区-15℃左右,其他地区-11℃左右. 中新社发 张滨 摄 中新社北京一月四日电二日以来,北京.天津.河北.内蒙古.山东北部等地出现大范围降雪和剧烈降温.随着强冷空气东移南下,四日,河南.浙江.湖北.湖南等多个地区都将遭遇寒潮入侵,中央

给第三方dll强签名

假若我们要对第三方控件或者是其他的没有源代码的DLL文件想做类似的处理,增加强名称签名,怎么处理,是很多人都会面对的问题. 步骤: 1.首先采用反汇编工具ildasm生成中间语言. ildasm myTest.dll /out:myTest.il 生成的结果包括myTest.il文件和myTest.res文件 2.采用汇编工具ilasm重新生成我们自己的dll文件 ilasm的具体用法可参照MSDN的帮助,其中有一个参数/key即可为我们重新生成新的签名文件 ilasm /dll /res:my

Visual Studio对程序集签名时一个很不好用的地方

由于我们的项目底层使用到一个通过LogicalCallContext实现的上下文数据管理框架,导致所有的Unit Test不能正常运行.具体的现象在<只在UnitTest和WebHost中的出现的关于LogicalCallContext的严重问题>有过详细的介绍.解决的方案就是对相关的程序集进行强签名,并加到GAC中,是Unit Test能够识别基于LogicalCallContext项目的类型.有了Visual Studio这个强大的IDE,程序集的签名工作很好实现--仅仅需要在Projec

一起谈.NET技术,Visual Studio对程序集签名时一个很不好用的地方

由于我们的项目底层使用到一个通过LogicalCallContext实现的上下文数据管理框架,导致所有的Unit Test不能正常运行.具体的现象在<只在UnitTest和WebHost中的出现的关于LogicalCallContext的严重问题>有过详细的介绍.解决的方案就是对相关的程序集进行强签名,并加到GAC中,是Unit Test能够识别基于 LogicalCallContext项目的类型.有了Visual Studio这个强大的IDE,程序集的签名工作很好实现--仅仅需要在Proje

强名称(2)引用强名称签名的程序集

引用强名称程序集的过程对我们来说都是透明的,无需做额外的工作.可以通过这种方式来检验强名称程序集的作用. 首先创建一个类库项目StrongNameReferenceLib,对其进行强名称签名. 图9-16引用强名称程序集 接下来修改之前创建的StrongName项目,让它引用StrongNameReferenceLib项目(如图9-16),调用其GetHello方法. StrongNameReferenceLib项目的主要代码如代码清单9-6所示. 代码清单9-6  StrongNameRefe

关于dll强名称签名问题,我用il反汇编程序转储为.il文件修改之后报错

问题描述 未能加载文件或程序集"Maj.Entity"或它的某一个依赖项.未能验证强名称签名.此程序集可能已被篡改,或者已被延迟签名,但没有用正确的私钥进行完全签名.(异常来自HRESULT:0x80131045).求解啊 解决方案 解决方案二:无解,强签名就是防止外人修改的解决方案三:主程序也要修改,让它不要使用gac中的程序集.