深入了解C#反射中Assembly.Load()、Assembly.LoadFrom()、Assembly.LoadF ile ()方法

本文介绍了C#中Assembly的Load()、LoadFrom()和LoadFile()方法。Load()通过程序集长名称加载,会加载引用的其他程序集;LoadFrom()从指定路径加载,也会加载引用的程序集,但可能导致重复加载;LoadFile()从指定文件加载,不加载引用的程序集。一般优先选Load()方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文转自:https://www.cnblogs.com/zagelover/articles/2726034.html 

Assembly.Load()

Load()方法接收一个String或AssemblyName类型作为参数,这个参数实际上是需要加载的程序集的强名称(名称,版本,语言,公钥标记)。例如.NET 2.0中的FileIOPermission类,它的强名称是:System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089,对于弱命名的程序集,则只会有程序集名称,而不会有版本,语言和公钥标记。如 TestClassLibrary.

细节
CLR内部普遍使用了Load()方法来加载程序集,在Load()方法的内部,CLR首先会应用这个程序集的版本绑定重定向策略,接着在GAC中查找目标程序集。如果GAC中没有找到,则会在应用程序目录和子目录中寻找(应用配置文件的codebase所指定的位置)
如果希望加载弱命名程序集,Load()方法就不会去GAC中查找
当Load()找到目标程序集时,就会加载它,并返回一个相应Assembly对象的引用

当没有找到程序集时,会抛出System.IO.FileNotFoundException异常。

当存在特定CPU架构的程序集时,CLR会优先加载当前架构的程序集(例如x86版本优先于IL中立版本)

如果希望强迫加载某个架构版本的程序集,需要在强名称中加以指定。ProcessorArchitecture可以为x86 IA64 AMD64MSIL,当然还有None

Load方法与Win32函数中的LoadLibrary方法等价

 Assembly.LoadFrom()

LoadFrom()方法可以从指定文件中加载程序集,通过查找程序集的AssemblyRef元数据表,得知所有引用和需要的程序集,然后在内部调用Load()方法进行加载。例如:Assembly.LoadFrom(@"C:\ABC\Test.dll");

细节

LoadFrom()首先会打开程序集文件,通过GetAssemblyName方法得到程序集名称,然后关闭文件,最后将得到的AssemblyName对象传入Load()方法中

随后,Load()方法会再次打开这个文件进行加载。所以,LoadFrom()加载一个程序集时,会多次打开文件,造成了效率低下的现象(与Load相比)
由于内部调用了Load(),所以LoadFrom()方法还是会应用版本绑定重定向策略,也会在GAC和各个指定位置中进行查找
LoadFrom()会直接返回Load()的结果——一个Assembly对象的引用
如果目标程序集已经加载过,LoadFrom()不会重新进行加载
LoadFrom支持从一个URL加载程序集(如"http://www.abc.com/test.dll"),这个程序集会被下载到用户缓存文件夹中
从URL加载程序集时,如果计算机未联网,LoadFrom会抛出一个异常。如果IE被设置为“脱机工作”,则不会抛出异常,转而从缓存中寻找已下载的文件

Assembly.LoadFile() 

LoadFile()从一个指定文件中加载程序集,它和LoadFrom()的不同之处在于LoadFile()不会加载目标程序集所引用和依赖的其他程序集。您需要自己控制并显示加载所有依赖的程序集

LoadFile()不会解析任何依赖

LoadFile()可以多次加载同一程序集

显式加载依赖程序集的方法是,注册AppDomain的AssemblyResolve事件


  


 

一些关于C#反射的知识,估计也就最多达到使用API的程度,至于要深入了解,以现在的水平估计很难做到,所以下面此篇文章,以作为一个阶段的总结。

对于反射的总结,我想从以下几个方面展开,首先是反射程序集,模块,类的成员以及成员的一些信息;接下来就是动态调用类的成员方法;第三个方面就动态产生程序集,模块和类以及类的成员。好了,现在就让我们从反射各种信息开始吧

在C#中,我们要使用反射,首先要搞清楚以下命名空间中几个类的关系:

System.Reflection命名空间

(1)   AppDomain:应用程序域,可以将其理解为一组程序集的逻辑容器

(2)   Assembly:程序集类

(3)   Module:模块类

(4)   Type:使用反射得到类型信息的最核心的类

他们之间是一种从属关系,也就是说,一个AppDomain可以包含N个Assembly,一个Assembly可以包含N个Module,而一个Module可以包含N个Type.

AppDomain这个类我们等下再来讲解。我们先关注Assembly个类

在程序中,如果我们要动态加载一个程序集怎么办呢?有几种方式可以使用,分别是Load、LoadFrom和LoadWithPartialName三个Assembly的静态方法.

先来讲解Assembly.Load方法,该方法会有多个重载版本,其中一个就是提供程序集的详细信息,即程序集的标识,包括程序集的名称,版本,区域信息,公有密钥标记,全部都是以一个字符串的形式提供,例如:"MyAssembly,Version=1.0.0.0,culture=zh-CN,PublicKeyToken=47887f89771bc57f”.

那么,使用Assembly.Load加载程序集的顺序是怎样的呢?首先它会去全局程序集缓存查找,然后到应用程序的根目录查找,最后会到应用程序的私有路径查找。

当然,如果你使用的是弱命名程序集,也即只给出程序集的名称,那么这个时候,CLR将不会在程序集上应用任何安全或者部署策略,而且Load也不会到全局缓存程序集中查找程序集。

测试加载弱命名程序集的例子如下:

(1)   新建一个控制台应用程序的工程,同时勾选创建解决方案

(2)   在解决方案中新建一个类库的项目,随便写一个类和一个方法

(3)   在控制台项目中,首先不添加引用,直接在Main方法中添加如下代码:

Assembly assembly = Assembly.Load("MyAssembly");

if (assembly != null)

{ Console.WriteLine("加载成功"); }

执行程序,会抛出异常,说找不到该程序集。什么原因呢?因为我们使用的是弱命名程序集,Load方法不会去全局程序集缓存中查找,而该应用程序目录下又没有该程序集,所以程序找不到。这个时候,我们把程序稍微改一下,不用添加代码,只需添加对MyAssembly的引用,重新运行程序,加载成功了。

接下来,我们就要看看Load怎么加载强命名程序集了,这个步骤稍微有些复杂。还是刚才的项目,找到MyAssembly.dll程序集所在的目录,一般在bin"Debug目录下

(1)生成密钥对文件   sn –k MyAssemblyKey.keys

你也可以自己随便起一个密钥对文件名

(2)生成公钥文件

sn –p MyAssemblyKey.keys MyAssemblyPublicKey.PublicKey

注:查看公钥命令:sn –tp MyAssemblyPublicKey.PublicKey

(3)创建强命名程序集。

很简单,只需要在声明命名空间的那句代码上加上如下特性:

[assembly:AssemblyKeyFileAttribute(@”D:"Test"MyAssemblyKey.keys”)]

(4)   编译项目

(5)   将程序集添加到程序集全局缓存

gacutil –i MyAssembly.dll

这个时候,转到加载程序集的项目中,将Load方法中的参数改为”程序集名,Version=版本,culture=区域信息,PublicKeyToken=公钥“,然后再去掉对程序集的引用,我们会发现,程序运行成功。表明Load到全局缓存区查找到了该程序集。

使用Load方法加载程序集,特别是强命名程序集,能在程序集上应用安全和部署策略,推荐使用该方法动态加载程序集,至于LoadFrom和LoadWithPartialName。

首先我们还是来看看LoadFrom方法,这个方法的原理是这样的:我们如果要使用它来动态加载程序集,必须告诉它程序集的路径,也即在哪个目录下面,CLR会去加载与你指定的路径完全匹配的程序集。记住,当我们指定程序集路径时,不能包括任何关于程序集强命名的信息,所以,CLR不会在我们指定的程序集文件上应用任何策略,而且也不会去任何其他的地方搜索程序集,简言之,它就是指哪打哪,呵呵。

例如:你有个程序集在D:/Test/MyAssembly.dll,你要用Assembly.LoadFrom加载该程序集,代码就如下:

Assembly assembly = Assembly.LoadFrom(@”D:/Test/MyAssembly.dll”);

对于,LoadWithParitalName方法,推荐大家最好不要使用它,因为程序无法确定最终要去加载哪个程序集的版本,所以我们这里只是简单的介绍一下它的工作原理:你可以传递一个程序集标识给它,包括程序集名称,至于其他信息是可选的(区域信息,公有密钥等),该方法执行时,会首先检查应用程序中配置文件的qualifyAssembly节点,如果存在,则把该部分名称的程序集替换成完全的程序集标识,如果不存在,则使用程序集名称先到应用程序根目录下查找,然后是私有目录,没有找到的话,就到程序集全局缓存中查找。简单过程如下:

       应用程序根目录 -> 应用程序私有目录 -> 程序集全局缓存. 

 

 Assembly.Load()方法,Assembly.LoadFrom()方法,Assembly.LoadFile()方法的区别!

1,Assembly.Load()

这个方法通过程序集的长名称(包括程序集名,版本信息,语言文化,公钥标记)来加载程序集的,会加载此程序集引用的其他程序集,一般情况下都应该优先使用 这个方法,他的执行效率比LoadFrom要高很多,而且不会造成重复加载的问题(原因在第2点上说明)

使用这个方法的时候, CLR会应用一定的策略来查找程序集,实际上CLR按如下的顺序来定位程序集:

⑴如果程序集有强名称,在首先在全局程序集缓(GAC)中查找程序集。         

⑵如果程序集的强名称没有正确指定或GAC中找不到,那么通过配置文件中的<codebase>元素指定的URL来查找

⑶如果没有指定强名称或是在GAC中找不到,CLR会探测特定的文件夹:

假设你的应用程序目录是C:\AppDir,<probing>元素中的privatePath指定了一个路径Path1,你要定位的程序集是AssemblyName.dll则CLR将按照如下顺序定位程序集

          C:\AppDir\AssemblyName.dll

          C:\AppDir\AssemblyName\AssemblyName.dll

          C:\AppDir\Path1\AssemblyName.dll

          C:\AppDir\Path1\AssemblyName\AssemblyName.dll

如果以上方法不能找到程序集,会发生编译错误,如果是动态加载程序集,会在运行时抛出异常!

2,Assembly.LoadFrom()

这个方法从指定的路径来加载程序集,实际上这个方法被调用的时候,CLR会打开这个文件,获取其中的程序集版本,语言文化,公钥标记等信息,把他们传递给 Load方法,接着,Load方法采用上面的策略来查找程序集。如果找到了程序集,会和LoadFrom方法中指定的路径做比较,如果路径相同,该程序集会被认为是应用程序的一部分,如果路径不同或Load方法没有找到程序集,那该程序集只是被作为一个“数据文件”来加载,不会被认为是应用程序的一部分。 这就是在第1点中提到的Load方法比LoadFrom方法的执行效率高的原因。另外,由于可能把程序集作为“数据文件”来加载,所以使用 LoadFrom从不同路径加载相同程序集的时候会导致重复加载。当然这个方法会加载此程序集引用的其他程序集。

3,Assembly.LoadFile()

这个方法是从指定的文件来加载程序集,和上面方法的不同之处是这个方法不会加载此程序集引用的其他程序集!

结论:一般大家应该优先选择Load方法来加载程序集,如果遇到需要使用LoadFrom方法的时候,最好改变设计而用Load方法来代替!

另:Assembly.LoadFile 与 Assembly.LoadFrom的区别

1、Assembly.LoadFile只载入相应的dll文件,比如Assembly.LoadFile("abc.dll"),则载入abc.dll,假如abc.dll中引用了def.dll的话,def.dll并不会被载入。

Assembly.LoadFrom则不一样,它会载入dll文件及其引用的其他dll,比如上面的例子,def.dll也会被载入。

2、用Assembly.LoadFrom载入一个Assembly时,会先检查前面是否已经载入过相同名字的Assembly,比如abc.dll有两个版本(版本1在目录1下,版本2放在目录2下),程序一开始时载入了版本1,当使用Assembly.LoadFrom("2\\abc.dll")载入版本2时,不能载入,而是返回版本1。Assembly.LoadFile的话则不会做这样的检查,比如上面的例子换成Assembly.LoadFile的话,则能正确载入版本2。

LoadFile:加载指定路径上的程序集文件的内容。LoadFrom: 根据程序集的文件名加载程序集文件的内容。

区别:

LoadFile 方法用来来加载和检查具有相同标识但位于不同路径中的程序集.但不会加载程序的依赖项。

LoadFrom 不能用于加载标识相同但路径不同的程序集。

 

<think>在stb_image库中,当我们使用`stbi_info`函数时,它会填充一个名为`bpp`(bits per pixel)的字段。但是,请注意,`stb_image`库并没有一个名为`info.bpp`的标准结构。通常,我们使用`stbi_info`函数来获取图像信息,该函数的参数包括宽度、高度和通道数,但没有直接提供一个名为`bpp`的字段。 然而,在讨论图像时,bpp通常有两种含义: 1. **Bits per pixel (每个像素的位数)**: 整个像素占用的位数,即通道数乘以每个通道的位数(位深度)。 2. **Bits per channel (每个通道的位数)**: 每个颜色通道的位数,例如8位/通道。 在`stb_image`库中,当我们使用`stbi_info`函数时,它返回的是图像的基本信息(宽度、高度、通道数),但不直接返回位深度(即每个通道的位数)。`stb_image`在加载图像时,默认将图像转换为8位每通道(即每个通道8位)。因此,如果我们没有使用其他函数(如`stbi_load_16`)来加载更高位深的图像,那么我们可以认为每个通道是8位。 但是,`stb_image`提供了一个函数`stbi_is_16_bit`,它可以告诉我们文件是否包含16位每通道的数据。此外,还有`stbi_info`函数,其原型如下: ```c int stbi_info(const char *filename, int *x, int *y, int *comp); ``` 这个函数可以获取图像的宽度(x)、高度(y)和通道数(comp),但它不提供位深度信息。 那么,如果我们想要知道图像的位深度(即每个通道的位数),我们可以使用以下方法: 1. 使用`stbi_is_16_bit`函数检查图像是否为16位/通道。 2. 使用`stbi_is_hdr`函数检查图像是否为浮点HDR图像(32位/通道)。 3. 对于8位/通道的图像,以上两个函数将返回false。 因此,我们可以这样推断: - 如果`stbi_is_16_bit(filename)`返回1,则位深度为16。 - 如果`stbi_is_hdr(filename)`返回1,则位深度为32(浮点数)。 - 否则,默认位深度为8。 注意:`stb_image`库在加载图像时,默认加载为8位每通道。如果要加载16位或浮点图像,需要使用`stbi_load_16`或`stbi_loadf`。 现在,回答你的问题:在`stb_image`的上下文中,并没有一个直接叫做`info.bpp`的字段。但是,如果我们自己计算每个像素的位数(bpp),那么可以通过通道数和每个通道的位数来计算。例如: - 对于8位每通道的RGB图像(3通道),每个像素的位数是24(3*8)。 - 对于16位每通道的RGBA图像(4通道),每个像素的位数是64(4*16)。 下面是一个示例代码,展示如何获取图像的信息,包括计算每个像素的位数: ```cpp #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" #include <iostream> int main() { const char* filename = "test.png"; int width, height, channels; // 获取图像基本信息 if (!stbi_info(filename, &width, &height, &channels)) { std::cerr << "无法获取图像信息: " << stbi_failure_reason() << std::endl; return -1; } // 检查位深度 int bits_per_channel = 8; if (stbi_is_16_bit(filename)) { bits_per_channel = 16; } else if (stbi_is_hdr(filename)) { bits_per_channel = 32; // 浮点数 } // 计算每个像素的位数 int bpp = channels * bits_per_channel; std::cout << "图像信息: " << filename << "\n" << "宽度: " << width << "\n" << "高度: " << height << "\n" << "通道数: " << channels << "\n" << "每个通道的位数: " << bits_per_channel << "\n" << "每个像素的位数: " << bpp << std::endl; return 0; } ``` 注意:`stbi_info`函数不会加载整个图像,因此速度较快。而`stbi_is_16_bit`和`stbi_is_hdr`函数需要读取文件头,但也不会加载整个图像数据,所以相对高效。 总结:在`stb_image`中,没有直接名为`info.bpp`的字段,但我们可以通过上述方法计算每个像素的位数(bpp)。 §§相关问题§§: 1. 如何用`stb_image`加载16位深度的图像? 2. `stb_image`中的`stbi_load`函数默认加载的图像每个通道是多少位? 3. 如何判断一个图像文件是否为HDR(高动态范围)图像? 4. 使用`stb_image`加载图像时,如何处理通道数的需求(例如强制加载为4通道)?</think>### `stb_image` 中的 `info.bpp` 解析 在 `stb_image` 库中,**`bpp` (Bits Per Pixel)** 是指图像中**每个像素占用的总位数**。这个值由两个关键因素决定: 1. **通道数**(channels):RGB=3,RGBA=4,灰度=1 2. **位深度**(bit depth):每个通道的精度(8位/通道,16位/通道等) 计算公式为: `bpp = 通道数 × 位深度` #### 实际使用中的关键点 1. **`stb_image` 没有直接提供 `bpp` 字段** 库本身不直接暴露 `bpp` 值,需要通过以下方式计算: ```cpp #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" int get_bpp(const char* filename) { int width, height, channels; // 1. 获取位深度信息 int is_16bit = stbi_is_16_bit(filename); int is_hdr = stbi_is_hdr(filename); // 2. 获取通道数 stbi_info(filename, &width, &height, &channels); // 3. 计算bpp if(is_hdr) return channels * 32; // HDR=32位浮点/通道 if(is_16bit) return channels * 16; // 16位整型/通道 return channels * 8; // 默认8位/通道 } ``` 2. **位深度检测方法** | 函数 | 用途 | 返回值 | |------|------|--------| | `stbi_is_16_bit()` | 检测16位/通道图像 | 1=true, 0=false | | `stbi_is_hdr()` | 检测32位浮点HDR图像 | 1=true, 0=false | 3. **完整信息获取示例**: ```cpp #include <iostream> #include "stb_image.h" void print_image_info(const char* filename) { int width, height, channels; // 获取基础信息 if(!stbi_info(filename, &width, &height, &channels)) { std::cerr << "错误: " << stbi_failure_reason(); return; } // 检测位深度 int bit_depth = 8; if(stbi_is_16_bit(filename)) bit_depth = 16; if(stbi_is_hdr(filename)) bit_depth = 32; // 计算bpp int bpp = channels * bit_depth; std::cout << "图像信息: " << filename << "\n" << "尺寸: " << width << "x" << height << "\n" << "通道数: " << channels << "\n" << "位深度: " << bit_depth << "位/通道\n" << "bpp: " << bpp << "位/像素\n" << "理论大小: " << (width * height * bpp / 8) / 1024 << " KB\n"; } int main() { print_image_info("image.jpg"); return 0; } ``` ### 典型 bpp 值示例 | 图像类型 | 通道数 | 位深度 | bpp | 说明 | |----------|--------|--------|-----|------| | 标准JPG | 3 | 8 | 24 | 最常见的RGB格式 | | 带Alpha的PNG | 4 | 8 | 32 | RGBA格式 | | 16位灰度TIFF | 1 | 16 | 16 | 医疗/专业图像 | | HDR全景图 | 3 | 32 | 96 | 浮点HDR图像 | ### 为什么需要关注 bpp? 1. **内存计算**:`内存占用 = 宽 × 高 × (bpp/8)` 2. **GPU纹理优化**:匹配GPU支持的纹理格式 3. **处理性能**:高位深图像需要特殊算法处理 4. **文件格式验证**:检测图像是否被意外转换 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值