第三部分
原文:
DLLs
DLLs can be a lot of things. Many are just libraries of simple functions; the Windows APIs are a good example of this type of DLL. Others can be much more complex; ActiveX controls,Excel add-ins,and OLE servers are some examples of more complex DLLs. ActiveX controls will be the only complex DLLs covered in this article.Simple DLLs are easy to use in .NET. Just add one or more declaration statements to your program,and you can call the functions in the library just like they are subroutines in your own program. The upgrade wizard will do a good job of converting these Declare statements from VB6 to VB.NET. In addition to upgrading the variable types discussed above,there are two new .NET attributes,packing and pinning,that may need to be added to some of the parameters being passed.
To understand packing,let's look at an example. Consider a VB6 user-defined type that contains two Integers; after the conversion,we want them to still be 16-bit integers. As mentioned above,the first thing we need to do is change the type from Integer to Short. Now let's look at packing.
Suppose we declare an instance of this type in VB6. The compiler will put it in memory somewhere; to simplify this discussion,we assume the instance is placed at the beginning of memory,at address 0000. The two bytes of the first Integer will be placed at addresses 0000 and 0001. The second Integer will be at addresses 0002 and 0003. This minimizes use of memory,and has no performance penalty on the 16-bit processors (8086/80286) that Windows and VB were originally designed to run on. Modern 32-bit processors can access the first integer at full speed; they load the 32 bits starting at 0000,and just mask out the second 16 bits. Accessing the second Integer is more complex. When the processor loads the first 32 bits,the 16 bits it needs are in the wrong position,so there is a small performance penalty when the processor shuffles the bits to where they need to be. ObvIoUsly,loading variables is a very common thing for a processor to do,so this small performance penalty becomes a big factor when you consider all the variables used in a program. .NET prevents this slowdown by putting all variables on 32-bit boundaries (this has been a compile option in C++ for a while). Under .NET,in this instance of the type,the first Integer will be at addresses 0000 and 0001,just like before,but the second integer will be at 0004 and 0005; addresses 0002 and 0003 will not be used. This wastes memory but the program will run faster.
The problem with this is that if you pass the user type to a DLL,that DLL will almost certainly expect the type to be "packed," like in VB6. .NET handles this with the "packed" attribute. Adding this attribute causes the VB.NET compiler to pack the type like in VB6,saving memory and maintaining compatibility at the expense of speed. The following example shows how this is done.
<StructLayout(LayoutKind.Sequential)> Userdef MyType IntOne as Integer IntTwo as Integer End Type
In cases where user-defined types,arrays,or fixed-length strings are passed as parameters to the DLL,the upgrade wizard will add warnings that marshaling attributes may need to be added. Even though the wizard places the warning at the Declare statement,the attribute (as in the above example) needs to be added where the parameter type itself is defined.
Garbage collection can bring up another issue with DLLs - pinning. In .NET,the memory manager/ garbage collector can and will move variables around in memory while a program is running. For instance,if a program creates five 100-byte arrays,they will probably be in a contiguous 500-byte block of memory. If the array taking up the first 100 bytes is released and gets garbage collected,the other 400 bytes will be moved down 100 bytes so there are no unused "holes" in the memory block. If a program passes a reference to a block of memory to a DLL,and that memory is later moved,the DLL's reference will no longer be valid,causing a major bug. To prevent this,a variable in C# can be "pinned" with the fixed command,which tells the garbage collector not to move the referenced memory; VB.NET does not have an equivalent to the "fixed" command,so I am unsure of how this is handled in VB.NET.
译文:
DLLs
DLL文件可以代表很多东西。但很多都只是简单函数的库,Windows API就是一个很好的例子。其它的可能就更加复杂了,像AX控件,EXCEL加载宏,以及OLE服务器这些都是更复杂的DLL库。这里将主要讲一下AX控件。在.NET中简单的Dlls很容易使用,只需添加一个或多个声明语句到你的程序里,就可以像是在自己程序中调用子过程一样调用库中的功能。我们的升级向导会很好地将这些声明语句从VB6转换到VB.NET。除了对上面提及的变量类型进行升级,还有两个新的.NET特性:packing 和pinning,这可能需要添加进传参过程中。
为了更好理解Packing,我们来看一个例子。想象一下一个VB6的用户定义类型,它由两个Integer组成,在转换之后,我们本想要让他们都保持原有的16位整型。但是就像我之前所说的,我们得先在转换之前将Integer改成Short。现在让我们看看Packing。
假设在VB6中我们声明一个这个类型的实例。编译器会把它放在内存的某处,长话短说,我们假设该实例被放在内存的开头,也就是0000H处。第一个Integer的两个字节将放在0000H和0001H处,第二个就是0002H和0003H。这样可以最大限度减少内存的使用,并且在Windows和VB最初设计的目标平台16位机(8086/80286)上没有性能损失。现代的32位处理器可以全速访问第一个整型数,他们在0000H处加载一个32位的单元,只是简单地屏蔽掉了第二个16位,然而要访问第二个整型数将会更复杂。当处理器加载这第一个32位时,它需要的16位却在不对的地方,所以在挪动这些位到要使用的地方时有一个小小的性能损失。显然,处理器加载变量是一个很平常的事情,所以当你把程序中使用的所有的变量都考虑在内的话小小的损失堆积起来就是质变的问题了。.NET为了阻止这样的量变过程,将所有的变量以32位来划分存储(这实际上已经作为C++编译的一个选项很久了)。在.NET里,对于上面的例子。第一个Integer像之前一样被放在0000H和0001H,但是第二个会被放在0004H和0005H,剩下的0002H和0003H并不会被使用,这实际上是空间换时间的思想。
这个问题的重点就是,如果你将你定义的自定义类型传给一个DLL,然后该DLL几乎很确定就是VB6中那样的"被包装"的类型。.NET也用"被包装"特性来对待这样的问题。增加这样的特性使得VB.NET编译器会像在VB6中一样打包我们的类型,这样就节约了内存,同时还保持速度损失上有一些兼容性。下面的例子就是在告诉我们怎么做。
<StructLayout(LayoutKind.Sequential)> Userdef MyType IntOne as Integer IntTwo as Integer End Type
在用户定义类型、数组、或者定长字符串被传参给DLL的情况下,升级向导将会添加Warnings来警告你可能需要增加marshaling特性语句。尽管向导在声明语句那里加了警告,但是该特性语句(就像上面那个例子一样)仍然必须添加到参数类型定义的地方去。
垃圾回收(Garbage collection)同样会给DLLs带来另一个问题-Pinning。在.NET里,当程序运行时,内存管理者(垃圾回收)将会并且能够将变量放在内存中的各个地方。比如说,当一个程序创建了5个长度为100字节的数组,那么他们将会被放在一个连续的长度为500字节的内存块里头。如果这个数组的前100个字节被释放并且被垃圾回收了,那么剩下的400字节将会被下移100字节的内存单位,因此不会存在未被使用的内存"空洞"。(意思就是如果不移位那么只有当有<100字节的内存需求存在时,这段内存才会被考虑进去)。如果一个程序传递一块内存的引用给DLL里,内存后来发生了位移,该DLL的引用将不再有效,于是有了一个大BUG。为了防止这样的问题存在,在C#里面可以通过固定的命令来把变量"Pinned",以此来告知垃圾收集器不要移动这块内存。VB.NET里面没有一个等同于这个特性的Fixed命令,所以我还不确定在VB.NET中是怎么处理这种问题的。
Update Log:
2014-4-12 10:47