现在的位置: 首页 > 综合 > 正文

mono与本机库集成

2018年03月15日 ⁄ 综合 ⁄ 共 39488字 ⁄ 字号 评论关闭

Everythingyou (n)ever wanted to know about marshaling (and were afraid to ask!)

你想了解的(以及不敢问的)关于封送的一切。

封送

平台调用如何工作?给定一个托管的调用地址(函数调用),以及一个未托管的调用者地址(被调用的函数),每个调用地址的参数封送(转换)成相同的非托管形式。封送的数据放置在运行时栈(与其他数据一起),然后非托管函数被调用。

复杂度是由于封送引起的。对于简单类型,例如整数和浮点数,封送只是一个按位复制(blitting),就像非托管代码一样。有些情况下,封送需要避免,例如传递引用的结构体给非托管代码(替换为指向结构体的指针)。也可能获得封送的更多控制,通过自定义封送和手动封送。

字符串带来额外的复杂度,因为你需要制定字符串转换的形式。运行时将字符串存储为UTF-16编码的字符串,这些和可能需要被封送为更合适的形式(ANSI字符,UIT-8字符串,等等)。字符串有一些特殊支持。

默认封送行为由DllImportMarshalAs属性控制。

内存边界

托管和非托管内存应该作为完全非开考虑。托管内存典型的在垃圾回收的堆中分配的,非托管的内存在任何其他地方分配:通过mallocANSI C内存池,自定义内存池,CLI实现之外的垃圾回收堆(例如LISP或者Scheme内存堆)。

使用C#fixed声明可以锁定部分托管堆。这个用在部分托管堆传递给非托管代码的时候不需要担心GC移动非托管代码正在操作的内存。然而,这完全在程序员的控制之下,而不是平台调用的工作。

由于一个P/Invoke调用,运行时没有模拟C#fixed声明。而是,类和结构体(一切后果)通过以下伪过程封送给本机代码:

1.                运行时分配一块非托管内存。

2.               托管类的数据复制给非托管内存。

3.              非托管函数被调用,传递非托管内存信息而不是托管内存。这是由于一旦GC出现,非托管函数不需要担心。(是的,你需要担心GC,由于非托管函数可能回调运行时,从而导致GC在非托管代码中执行的多线程代码时候也可能引发GC。)

4.              非托管内存拷贝回托管的内存。

参考 Classand
Structure Marshaling
了解更多关于类和结构体封送的细节。

记住一个关键点:上述过程的内存管理师隐式的,没有任何方法控制运行时如何分配封送内存,以及他们持续多久。这是非常重要的。如果运行时封送一个字符串(例如:Anis编码的UTI-16),封送的字符串仅仅持续一个调用。非托管的代码不能保持这个内存的引用,因为它将在这个调用结束后被释放。没有注意到这个限制可能产生奇怪行为,包括内存访问违规和进程死亡。这对于任何运行时为封送过程分配的内存都是正确的。

这点的一个伪异常是委托。表示托管委托的非托管函数指针与托管委托一样持久。当委托被GC回收,非托管函数指针也被回收。这也非常重要:如果委托被回收而非托管内存调用函数指针,你踩到地雷了。任何事都可能发生,包括进程seg-fault。总而言之,你必须确保非托管函数指针的生命周期是托管委托实例的一个子集。

按位复制类型

许多类型需要最小限度复制给本机内存。按位复制的类型是仅仅需要一个memcpy或者可以不需要转换的传递给运行时的类型。这些类型包括:

C# Type

C Type

<stdint.h> Type

<glib.h> Type

sbyte

char

int8_t

gint8

byte

unsigned char

uint8_t

guint8

short

short

int16_t

gint16

ushort

unsigned short

uint16_t

guint16

int

int

long 32-bit platforms only

int32_t

gint32

uint

unsigned int

unsigned long 32-bit platforms only

uint32_t

guint32

long

long 64-bit platforms only

__int64 MSVC

long long GCC

int64_t

gint64

long

unsigned long 64-bit platforms only

unsigned __int64 MSVC

unsigned long long GCC

uint64_t

guint64

char

unsigned short

uint16_t

guint16

float

float

 

gfloat

double

double

 

gdouble

bool

Depends on context

 

 

Strings

Strings are special. String marshaling behavioris also highly platform dependent.

String marshaling for a function call can be specified inthe function declaration with the DllImport attribute,by setting the CharSet field.
The default value for thisfield is 
CharSet.Ansi . The CharSet.Auto value
implies "magic."

Some background. The Microsoft Win32 API supports twoforms of strings: "ANSI" strings, the native character set, such asASCII, ISO-8859-1, or a Double Byte Character Set such as Shift-JIS; andUnicode strings, originally
UCS-2, and now UTF-16. Windows supports thesestring formats by appending an "A" for Ansi string APIs and a"W" ("wide") for Unicode string APIs.

Consider this Win32 API description:

 [DllImport("gdi32.dll",CharSet=CharSet.Auto,

      CallingConvention=CallingConvention.StdCall)]

 privatestaticexternboolTextOut(

      System.IntPtrhdc,

      intnXStart,

      intnYStart,

      stringlpString,

      intcbString);

When TextOut is called, the "magic" propertiesof String marshaling become apparent. Due to string marshaling, the runtimedoesn't just look for an unmanaged function with the same name as the specifiedmethod, as specified
in Invoking Unmanaged Code. Other permutations of thefunction may be searched for, depending on the CLI runtime and the hostplatform.

There are three functions that may be searched for:

·                    TextOutWfor Unicode string marshaling

·                    TextOutAfor Ansi string marshaling

·                    TextOutwith the platform-default marshaling

For platforms whose default character set is UCS2 orUTF-16 Unicode (all flavors of Windows NT, and Windows XP), the default searchpath is TextOutW, TextOutA, and TextOut. Unicode marshaling is preferred, as(ideally)
the System.String can be passed as-is to the function, as long as thefunction doesn't modify the string parameter. Windows CE does not look forTextOutA, as it has no Ansi APIs.

For platforms whose default character set is Ansi (Windows9x, Windows ME), the default search path is TextOutA and TextOut (TextOutW isnot looked for). Ansi marshaling will require translating the Unicode stringinto
an 8-bit or DBCS string in the user's locale. Most (all?) of the time,this WILL NOT be UTF-8, so you CAN NOT assume that CharSet.Ansi will generateUTF-8-encoded strings.

Mono on all platforms currently uses UTF-8 encoding forall string marshaling operations.

If you don't want the runtime to search for the alternateunmanaged functions, specify a CharSet value other than CharSet.Auto. This willcause the runtime to look only for the specified function. Note that if youpass
a wrongly encoded string (e.g. calling MessageBoxW when the CharSet isCharSet.Ansi, the default), you are crossing into "undefined"territory. The unmanaged function will receive data encoded in ways it wasn'texpecting, so you may get such bizarre things as
Asian text when displaying"Hello, World".

Perhaps in the future the CharSet enumeration
will contain morechoices, such as UnicodeLE (little-endian), UnicodeBE (big-endian), Utf7, Utf8,and other common choices. Additionally, making such a change would also likelyrequire changing the UnmanagedType enumeration. However, these would need to gothrough
ECMA, so it won't happen next week. (Unless some time has passed sincethis was originally written, in which case it may very well be next week. Butdon't count on it.)

More Control

Using the DllImport attributeworks if you want to control all the strings in a function, but what if youneed more control? You
would need more control if a string is a member of astructure, or if the function uses multiple different types of strings asparameters. In these circumstances, the 
MarshalAs attributecan
be used, setting the 
Value property (which
is set in theconstructor) to a value from the
UnmanagedType enumeration.
For example:

 [DllImport("does-not-exist")]

 privatestaticexternvoid
Foo
(

      [MarshalAs(UnmanagedType.LPStr)]stringansiString,

      [MarshalAs(UnmanagedType.LPWStr)]stringunicodeString,

      [MarshalAs(UnmanagedType.LPTStr)]stringplatformString);

As you can guess by reading the example, UnmanagedType.LPStr will
marshal the input string into anAnsi string,
UnmanagedType.LPWStr will
marshal the input string into aUnicode string (effectively doing nothing), and 
UnmanagedType.LPTStr will
convert the string to theplatform's default string encoding.

The default platform encoding for all flavors of WindowsNT (including Windows NT 3.51 and 4.0, Windows 2000, Windows XP, Windows Server2003) is Unicode, while for all Windows 9x flavors (Windows 95, 98, ME) theplatform
default encoding is Ansi.

Mono uses UTF-8 encoding as the default encoding on allplatforms.

There are other UnmangedType stringmarshaling options, but they're primarily of interest in COM Interop (BStrAnsiBStrTBStr).

If UnmanagedType doesn'tprovide enough flexibility for your string marshaling needs (for example,you're wrapping GTK+ and you
need to marshal strings in UTF-8 format), look atthe 
CustomMarshaling or ManualMarshaling sections.

PassingCaller-Modifiable Strings

A common C language idiom is for the caller to providethe callee a buffer to fill. For example, consider strncpy(3):

 char*strncpy(char*dest,constchar*src,size_t
n
);

We can't use System.String forboth parameters, as strings are immutable. This is OK for src,but dest willbe
modified, and the caller should be able to see the modification.

The solution is to use a System.Text.StringBuilder ,
which gets special marshalingsupport from the runtime. This would allow 
strncpy(3)to be wrapped and used as:

 [DllImport("libc.so")]

 privatestaticexternvoidstrncpy(StringBuilder
dest,

      stringsrc,uint n);

 

 privatestaticvoidUseStrncpy()

 {

    StringBuilder sb =new
StringBuilder
(256);

    strncpy (sb,"thisis the source string", sb.Capacity);

    Console.WriteLine(sb.ToString());

 }

Some things to note is that the return value of strncpy(3)was changed to void,as
there is no way to specify that the return value will be the same pointeraddress as the input 
dest stringbuffer, and thus it doesn't need to be marshaled. If string wereused
instead, Bad Things could happen (the returned string would be freed;see 
Stringsas Return Values ).The StringBuilder isallocated
with the correct amount of storage as a constructor parameter, andthis amount of storage is passed to 
strncpy(3)to prevent buffer overflow. If you use aStringBuilder instancemultiple
times, always call[http:/monodoc/M:System.Text.StringBuilder.EnsureCapacity(System.Int32)EnsureCapacity()] before passing it into the native method, as the capacity mayshrink as a memory optimization over time, leading to unexpectedly truncatedresults.

TODO: How does StringBuilder interact with the specifiedCharSet?

Strings as ReturnValues

The String typeis a class, so seethe
section on returning classes from functions
. Summary: the runtime will attempt to freethe returned pointer. The usual symptom is a runtime crash like this:

=================================================================

Got a SIGSEGV while executing native code. This usually indicates

a fatal error in the mono runtime or one of the native libraries

used by your application.

=================================================================

 

Stacktrace:

 

in <0x4> (wrapper managed-to-native)System.Object:__icall_wrapper_g_free (intptr)

in <0x6b9d0c> (wrapper managed-to-native)System.Object:__icall_wrapper_g_free (intptr)

If you don't want the runtime to free the returnedstring, either (a) don't specify the return value (aswas
done for the strncpy(3) function above
), or (b) return an IntPtr and
use one of theMarshal.PtrToString* functions, depending on the type of string returned. Forexample, use 
Marshal.PtrToStringAnsi to
marshal from a Ansi string, anduse 
Marshal.PtrToStringUni to
marshal from a Unicode string.

See also:

·                    Defaultmarshaling for Strings at MSDN

·                    AnOverview of
Managed/Unmanaged Code Interoperability

Class and StructureMarshaling类和结构体封送

The conceptual steps that occur to marshal classes andstructures is detailed above, in the MemoryBoundaries section.

The principal difference between class and structuremarshaling is which, if any, of conceptual steps actually occurs.

类和结构体主要的不同是真实出现的概念步骤不同。

类封送

记住类是CLI堆分配的并且会被垃圾回收。因此,你不能将类通过值传递给非托管函数,只能通过引用:

 /* Unmanaged code declarations */

 struct UnmanagedStruct{

    int a, b,
c
;

 };

 

 void WRONG(structUnamangedStruct pass_by_value);

 

 void RIGHT(struct UnmanagedStruct*pass_by_reference);

 void RIGHT2(structUnmanagedStruct**pass_by_reference_out_or_ref);

这表示你不能使用类来调用期望传值的变量(就像上面的WRONG函数)。

在类中有两个其他为题,首先,类默认使用LayoutKind.Auto层。这意味着类的数据成员的顺序是未知的,并且在运行之前都不会知道。运行时会重新以任何它选择的方式排列成员次序,来优化访问时间或者数据的空间。因此,你必须使用 StructLayout属性并指定 LayoutKindLayoutKind.Sequential 或者 LayoutKind.Explicit

其次,类(再次默认)仅有边界内封送。就是说,Step4 (将非托管的内存拷贝回托管内存)省略了。如果你需要将非托管类存拷贝回托管类存,你必须在DllImport函数声明参数上增加out属性。如果你需要copy-incopy-out行为,你也要使用in属性。总而言之:

·                    使用[In]等价于不指定任何参数属性,并且将跳过步骤4(将非托管内存拷贝回托管内存)。

·                    使用[out]将跳过步骤2(将托管内存拷贝到非托管内存)。

·                    使用[in,out]在非托管函数调用之前吧托管内存拷贝到非托管内存,在函数调用之后将非托管内存拷贝回托管内存。

在某些情况下,封送的复制可能被省略。对象简单的保持在内存中并把数据指针传递给非托管函数。

TODO:这什么时候出现?如果对于任何Sequential 层的类都出现,你不必指定Out属性,因为非托管代码将看到真实对象。是否在某些特殊情况才出现?这个似乎出现在StringBuilder,但是这是我知道的唯一一个。

参考:

·                    DirectionalAttributes
(MSDN)

结构体封送

类和结构体有两个主要不同。首先,结构体不需要再堆中分配;他们可能在运行时栈中分配。其次,他们默认为LayoutKind.Sequential,因此结构体声明不需要任何附加的属性来与非托管代码一起使用(假定默认次序规则对于非托管结构体是正确的)。

这些不同允许结构体通过值传递给非托管函数,不像类。另外,如果结构体位于栈上,并且结构体包含任何可按位复制类型,如果你用引用传递一个结构体给非托管代码,结构体将直接传递给非托管函数,没有任何中间的非托管内存复制。这表示你不需要指定任何out属性就可以看到非托管代码的对结构体数据的更改。

注意如果结构体包含任何不可按位复制类型(例如System.BooleanSystem.String,或者一个数组),这个优化不再可能并且结构体复制必须作为封送过程的一部分。

类和结构体作为返回值

类和结构体的类存分配的不同形式也影响他们作为函数返回值的处理方法。

当非托管函数返回一个非托管结构的指针的时候类可以作为函数的返回值。类不能作为值类型的返回值。

当非托管函数通过值返回结构的时候结构体可以作为返回值。不能返回refout类型的结构,如果一个非托管函数返回一个结构的指针,对安全的代码必须使用IntPtr,或者对于不安全的代码,使用指向结构体的指针。如果IntPtr用来作为返回值,Marshal.PtrToStructure可以用来将非托管指正转换为托管的结构。

内存管理也涉及较多。

内存管理

对于大多数平台调用和封送的内存管理可以省略,但是对于CLI实现的返回值的一些默认处理必须考虑。

CLI运行时假定,在某些情况下,CLI运行时负责释放非托管代码分配的内存。返回值就是一个情况,小心返回值成为内存分配控制的边界。

CLI假定所有在CLI/非托管代码之间传递的内存都是由公共内存分配器分配的。开发者不能选择使用哪个内存分配器。对托管代码,Marshal.AllocCoTaskMem方法可以用来非配内存,Marshal.FreeCoTaskMem用来释放 Marshal.AllocCoTaskMem, Marshal.ReAllocCoTaskMem 用来重新设置 Marshal.AllocCoTaskMem分配的内存大小。

由于类经由引用传递,作为指针返回,运行时假定它必须释放这个内存来避免内存泄露。事件链是这样的:

1.                托管代码调用返回指向非托管内存中的非托管结构体的非托管函数。

2.                一个适当的托管类被实例化,非托管内存被封送为托管类。

3.                非托管内存由运行时释放。

How is Marshal.AllocCoTaskMemMarshal.ReAllocCoTaskMem,and Marshal.FreeCoTaskMem implemented?That's
platform-dependent. (So much for portable platform-dependent code.)Under Windows, the COM Task Memory allocator is used (via
CoTaskMemAlloc()CoTaskMemReAlloc(),
and 
CoTaskMemFree()). Under
Unix, the GLib memoryfunctions 
g_malloc() , g_realloc() ,
and
g_free() functions are used. Typically, thesecorrespond to
the ANSI C functions 
malloc(3), realloc(3),and free(3),but
this is not necessarily the case as GLib can use different memoryallocators; see 
g_mem_set_vtable() and g_mem_is_system_malloc() .

如果你不想运行时释放内存你应该怎么做?不要返回类。而是,返回IntPtr(基本与Cvoid*指针等价),然后使用Marshal类的方法来操作指针,例如Marshal.PtrToStructure,对于标记为 [StructLayout(LayoutKind.Sequential)]C#类和结构体都适用。

选择类还是结构体

在封装非托管代码的时候我们应该使用哪个,类还是结构体?

一般,这个问题的答案依赖于非托管代码需求(废话)。如果你需要传值语义,你必须使用结构体。如果你想反回指向非托管类型指针而不依赖unsafe或者手工代码,你必须使用类(假定默认内存分配规则是合适的)。

大的十字路口的非托管代码,没有pass-by-value结构或从函数返回指针结构?使用哪个更方便最终用户。不是所有的语言都支持通过引用传递类型(例如Java),所以使用类将允许一个更大的身体语言使用包装器库。此外,微软建议结构尺寸不超过16字节。

总结

用代码展示总是更加容易,因此。。。给定一下非托管代码的声明:

 /* unmanaged code declarations */

 

 struct UnmanagedStruct{

    int n;

 };

 

 void PassByValue(structUnmanagedStruct
s
);

 

 void PassByReferenceIn(structUnmanagedStruct*s);

 void PassByReferenceOut(structUnmanagedStruct*s);

 void PassByReferenceInOut(structUnmanagedStruct*s);

 

 struct UnmanagedStruct ReturnByValue();

 struct UnmanagedStruct*ReturnByReference();

 

 void DoubleIndirection(structUnmanagedStruct**s);

类封装应该是:

 /* note: sequential layout */

 [StructLayout(LayoutKind.Sequential)]

 classClassWrapper{

    publicint n;

 

    /* cannot wrapfunction PassByValue */

 

    /* PassByReferenceIn*/

    [DllImport("mylib")]

    publicstaticextern

       voidPassByReferenceIn(ClassWrapper s);

 

    /* PassByReferenceOut*/

    [DllImport("mylib")]

    publicstaticextern

       voidPassByReferenceOut([Out]ClassWrapper
s
);

 

    /*PassByReferenceInOut */

    [DllImport("mylib")]

    publicstaticextern

       voidPassByReferenceInOut([In,Out]ClassWrapper
s
);

 

    /* cannot wrapfunction ReturnByValue */

 

    /* ReturnByReference*/

    [DllImport("mylib")]

    publicstaticexternClassWrapper ReturnByReference();

       /* note: this causesreturned pointer to be freed

          by runtime */

     /* DoubleIndirection*/

    [DllImport("mylib")]

    publicstaticextern

       voidDoubeIndirection(refClassWrapper
s
);

 }

而结构体封装应该是:

 structStructWrapper{

    publicint n;

 

    /* PassByValue */

    [DllImport("mylib")]

    publicstaticexternvoidPassByValue(StructWrapper
s
);

 

    /* PassByReferenceIn*/

    [DllImport("mylib")]

    publicstaticexternvoidPassByReferenceIn(

       ref StructWrappers);

 

    /* PassByReferenceOut*/

    [DllImport("mylib")]

    publicstaticexternvoidPassByReferenceOut(

       outStructWrapper s);

 

    /*PassByReferenceInOut */

    [DllImport("mylib")]

    publicstaticexternvoidPassByReferenceInOut(

       refStructWrapper s);

 

    /* ReturnByValue */

    [DllImport("mylib")]

    publicstaticexternStructWrapper ReturnByValue();

 

    /* ReturnByReference:CLS-compliant way */

    [DllImport("mylib",EntryPoint="ReturnByReference")]

    publicstaticexternIntPtr ReturnByReferenceCLS();

       /* note: this DOES NOTcause returned pointer to be

          freed by the runtime, so it's notidentical to

          ClassWrapper.ReturnByReference.

          Use Marshal.PtrToStructure() to accessthe

          underlying structure. */

 

    /* ReturnByReference:"unsafe" way */

    [DllImport("mylib",EntryPoint="ReturnByReference")]

    publicstaticunsafeexternStructWrapper*

       ReturnByReferenceUnsafe
();

       /* note: this DOES NOTcause returned pointer to be

          freed by the runtime, so it's notidentical to

          ClassWrapper.ReturnByReference */

 

    /* DoubleIndirection:CLS-compliant way */

    [DllImport("mylib",EntryPoint="DoubleIndirection")]

    publicstaticextern

       voidDoubeIndirectionCLS(refIntPtr
s
);

       /* note: this issimilar to ReturnByReferenceCLS().

          Pass a `ref IntPtr' to the function,then use

          Marshal.PtrToStructure() to accessthe

          underlying structure. */

 

    /* DoubleIndirection:"unsafe" way */

    [DllImport("mylib",EntryPoint="DoubleIndirection")]

    publicstaticunsafeextern

       voidDoubeIndirectionUnsafe(StructWrapper**s);

 }

Marshaling Classand Structure Members

除了上述类和结构体的主要不同,类成员和结构体成员的封送是相同的。

一般的建议是:绝不传递包含引用类型成员的类或结构体给非托管代码。这是因为非托管代码不能用非托管引用(指针)做任何事,而CLI运行时不做任何深封送(封送类成员,以及成员的成员,递归的)。

这个的中间网络效果是你不能有任何封送类的数组成员,以及(我们在前面已经看到的)字符串处理可能是错误的(字符串也是引用类型)。

另外,默认字符串封送是平台默认的,尽管这个可用StructLayoutAttribute.CharSet字段改变,这个默认为CharSet.Auto.可选的,你可以用MarshalAsattribute指定他们是什么类型的字符串。

Boolean Members

The System.Boolean (bool inC#)
type is special. (FUBAR might be more appropriate.) A 
bool within astructure is marshaled as an int(a4-byte integer),
with 0 being 
false and non-zero being true; see UnmanagedType.Bool .
bool passed as anargument to a function is marshaled as a short (a 2-byteinteger), with 0 being false and-1
being 
true (asall bits are set); seeUnmanagedType.VariantBool .

You can always explicitly specify the marshaling to useby using the MarshalAsAttribute onthe boolean member, but there are only
three legal UnmanagedType values: 
UnmanagedType.BoolUnmanagedType.VariantBool,and UnmanagedType.U1.UnmanagedType.U1 ,the
only un-discussed type, is a 1-byte integer where 1 represents 
true and 0represents false.

If you need to marshal as another data type, you shouldoverload the method accepting the boolean parameter, and manually convert theboolean to your desired type:

 // Unmanaged C declaration:

 void DoSomething(intboolean);

 // Managed declaration:

 [DllImport("SomeLibrary")]

 privatestaticexternvoidDoSomething(intboolean);

 

 publicstaticvoidDoSomething(boolboolean)

 {

    DoSomething (boolean?1:0);

 }

See also: DefaultMarshaling
for Boolean Types

Unions

A C union (in which multiple members share the sameoffset into a structure) can be simulated by using the FieldOffset attribute
and specifying the sameoffset for the union members.

Longs

The C 'long' type is difficult to marshal as a structmember, since there is no CLR type which matches it, i.e. 'int' is 32 bit,'long' is 64 bit, while C's 'long' can be 32 bit or 64 bit, dependending on theplatform.
There are two possible solutions:

·                    Usingtwo sets of structures, one for 32 bit and one for 64 bit platforms.

·                    MappingC 'long' to 'IntPtr'. This will work on all 32 bit and 64 bit platforms, _except_64 bit windows, where sizeof(long)==4 and sizeof(void*)==8. See This.

嵌入结构体的数组

内联数组可以通过使用UnmanagedType.ByValArray并用MarshalAsAttribute.SizeConst指定数组大小的 MarshalAs 属性来封送。包含字符串的数组可以使用UnmanagedType.ByValTStr 来指定字符串。

然而,运行时不会自动分配指定为 UnmanagedType.ByValArray数组.程序员任然负责分配托管的数组。更多信息请参考总结。

TODOBernie Solomon说对于out参数,运行时将分配内联数组。检查一下。

例如,非托管代码:

 struct UnmanagedStruct{

    int data[10];

    charname[32];

 };

可以用C#表示为:

 structManagedStruct_Slow{

    [MarshalAs(UnmanagedType.ByValArray,SizeConst=10)]

    publicint[]  data;

    [MarshalAs(UnmanagedType.ByValTStr,SizeConst=32)]

    publicstringname;

 }

当然,托管的结构体可以用其他方式声明,因性能和使用权衡而不同。前面的声明是最直接的方式,但是有糟糕的性能表现。以下结构体将封送更快,但是更难使用:

 structManagedStruct_Fast_1{

    publicint  data_0, data_1, data_2, data_3, data_4,

                data_5, data_6, data_7, data_8,data_9;

    publicbytename_00, name_01, name_02, name_03, name_04,

                name_05, name_06, name_07,name_08, name_09,

                name_10, name_11, name_12,name_13, name_14,

                name_15, name_16, name_17,name_18, name_19,

                name_20, name_21, name_22,name_23, name_24,

                name_25, name_26, name_27,name_28, name_29,

                name_30, name_31,

 }

另一个方案是直接指定结构体大小,而不是让结构体内容指示结构体大小。这个经由StructLayout.Size 字段完成。这使结构体的处理非常讨厌,因为指针算法必须用来处理名称成员。

 [StructLayout(LayoutKind.Sequential,Size=72)]

 structManagedStruct_Fast_2{

    publicint  data_0, data_1, data_2, data_3, data_4,

                data_5, data_6, data_7, data_8,data_9;

    publicbytename;/*
first byte of name*/

    /* Size propertyspecifies that 31 bytes of nameless

       "space" is placed here. */

 }

C# 2.0 功能

C# 2.0 添加了语言功能来处理内联数组,使用fixed数组语法。这可以让前一个结构体这样声明:

 structManagedStruct_v2{

    publicfixedint  data[10];

    publicfixedbytename[32];

 }

固定语法的数组仍然是不安全的,并且需要高权限执行力。

实际经验

这可能有用。来自大卫杰斯http://www.chat.net/~jeske/ ):

This time I have some PInvoke information to share, sothat when someone else runs into this issue they can see what I've done. In myClearSilver (www.clearsilver.net, an HTML template system) C# wrapper, I wantedto
access this C-struct:

 typedefstruct _neo_err

 {

   interror;

   interr_stack;

   intflags;

   chardesc[256];

   constchar*file;

   constchar*func;

   intlineno;

   /*internal use only */

   struct_neo_err*next;

 }NEOERR;

My philosophy of using unsafe struct pointers, and justaccessing the struct out in unmanaged memory is great, and it's exactly what Iwant to do. However, handling "char dest[256]" is notstraightforward.

In C# arrays are reference types. Using one makes thestruct a managed type, and I can't put the array size in. The following isconceptually what I want to do, however, it's obviously invalid:

 [StructLayout(LayoutKind.Sequential)]

 unsafestruct NEOERR{

   publicint error;

   publicint err_stack;

   publicint flags;

   publicbyte[256]desc; //
this is invalid,

                           // can't contain size

   publicconstbyte*file;

   publicconstbyte*func;

   publicint lineno;

 

   /*internal use only */

   privateNEOERR*next;

 }

This dosn't work either:

 [MarshalAs(UnmanagedType.LPStr,SizeConst=256)]

 publicstring desc;

Because in this case, I don't want to marshal the data. Ijust want to talk to it in place. The solution I could come up with is this:

 [StructLayout(LayoutKind.Explicit)]

 unsafestruct NEOERR{

   [FieldOffset(0)]publicint
error
;

   [FieldOffset(4)]publicint
err_stack
;

   [FieldOffset(8)]publicint
flags
;

   //public byte[256] dest;  // notrepresentable

 

   // usethis as an address??

   [FieldOffset(12)]publicbyte
dest_first_char
;

   [FieldOffset(268)]publicbyte*file;//const

   [FieldOffset(272)]publicbyte*func;//const

   [FieldOffset(276)]publicint
lineno
;

 }

UGH! First, this is obviously annoying. Second, the onlyway I can figure to get access to "char dest[256]" is to use"char* dest = &nerr->dest_first_char;" and then just use destas a pointer to the string. I've
dug through the documentation, and I can'tfind any better solution.

Obviously it would be ideal if there were away to represent a value-type array. I wonder how Managed C++ handles"char foo[256];" in a struct.

See also:

·                    EricGunnerson's C# Blog: Arrays inside of structures

总结

另一个本机代码示例...

 /* original code */

 struct UnmanagedInformation{

    int num;

    char*string;

    int array[32];

 

    union{

       int64_taddr; /* from<stdint.h>,
C99 */

       doubleother;

    } stuff;

 };

对应的可能本机代码:

 /* managed representation */

 [StructLayout(LayoutKind.Explicit,CharSet=CharSet.Ansi)]

 structManagedInformation{

    [FieldOffset(0)]int
num
;

    [FieldOffset(4)]string
str
;

    [FieldOffset(8),

      MarshalAs (UnmanagedType.ByValArray,SizeConst=32)]

    int[]array;

 

    /* union members */

    [FieldOffset(136)]longstuff_addr;

    [FieldOffset(136)]doublestuff_other;

 }

注意着并没有精确匹配非托管代码。这个结构体必须用两个阶段来匹配非托管形式:(1)创建结构体,并且(2)分配托管的信息的内存数组。例如:

 ManagedInformation info =new
ManagedInformation
();

 info.array=newint[32];

 一个关于FieldOffset的警告

FieldOffset属性有一个主要的陷阱:它使类型的偏移精确。一旦类/结构体包含指针,引用或数组或者你改变进的处理器的位宽(从32bit变为64比特)这非常容易打破。应尽可能的使用LayoutKind.Sequential,因为指针改变大小的时候运行时会自动更新结构体偏移。

TODO: include MSDN examples using the more esotoric MarshalAs fields,such as SizeParamIndexArraySubType,etc.

See also: *MarshalingData
with Platform Invoke at MSDN
,*ArraysSample
at MSDN

Marshaling Pointers

Didn't we start using a managed execution environmentto avoid pointers?But I digress...

Alas, pointers are a fact of life in unmanaged code. As the AvoidingMarshaling sectionpoints
out, there are two ways to represent pointers: the "safe" way,using 
System.IntPtr or System.UIntPtr ,
and the "unsafe" way, byusing 
unsafe codeand pointers.

Marshaling Embedded Strings

Behold the topic that just won't die! "Inline"strings -- in which the storage for the string is part of the structure itself-- were coveredpreviously.
Obviously, and likely more commonly,strings are not always allocated within the structure; typically a pointer to anull-terminated string is stored.

The typical approach is to map the string as an IntPtr,and use Marshal.PtrToStringAnsi and
similar functions to manuallymarshal the string.

Why manually marshal? Because you typically use a custommemory allocator (such as malloc(3)),and don't want the runtime incorrectly
freeing the memory that the stringreferences. In this case, it's 
essential thatyou manually marshal the string to avoid memory corruption.

Custom Marshaling

The ICustomMarshaler interface
allows the CLI to invokecustom code as part of the P/Invoke call. Normal P/Invoke calls follow thestructure:

Method invocation CLI P/Invoke Default Marshaler Unmanaged method
call

Custom marshaling allows this to become

Method invocation CLI P/Invoke Default Marshaler custom marshaler
CLI P/Invoke marshaler
Unmanaged method call

In order for the custom marshaler to be invoked,

1.                thecustom marshaler must implement the ICustomMarshaler interface,and

2.                thecustom marshaler must provide a static GetInstance methodwhich takes a
string and returns a 
ICustomMarshaler instance:

publicstaticICustomMarshaler GetInstance(string
s
);

1.                The DllImport declarationmust have a parameter with a MarshalAs attributespecifying UnmanagedType.CustomMarshaler and:

o                           The MarshalTypeRef or MarshalType fields.

o                           (Optionally)The MarshalCookie field.
This string is passed to 
GetInstance.

An example custom marshaler can be found in the Mono.Unix.Native.FileNameMarshaler (FileNameMarshaler.cs)
and in the Mono unit tests (
marshal9.cs).

Given all the work involved with custom marshaling, suchas the required ICustomMarshaler classimplementation and MarshalAsattributes,what
is the advantage of custom marshaling over 
manualmarshaling?Maintenance.
For only one method, manual marshaling is simpler:

 // Custom Marshaling withMono.Unix.Native.FileNameMarshaler.cs

 [DllImport(LIB)]

 publicstaticexternintopen(

    [MarshalAs(UnmanagedType.CustomMarshaler,

       MarshalTypeRef=typeof(Mono.Unix.Native.FileNameMarshaler))]

    stringpathname,

    intflags

 );

And the manual marshaler implementation:

 // Use Manual Marshaling

 [DllImport(LIB)]

 privatestaticexternintopen(IntPtr
pathname,
intflags);

 

 publicstaticexternintopen(stringpathname,intflags)

 {

    IntPtr _pathname =UnixMarshal.StringToHeap(pathname,

          UnixEncoding.Instance);

    try{

       returnopen(_pathname, flags);

    }

    finally{

       UnixMarshal.FreeHeap(_pathname);

    }

 }

However, as the number of methods that requireessentially identical marshaling increases, it becomes easier to maintain thecustom marshaler than to maintain the N separatemanual
marshal copies that would otherwise be necessary.


See also: *
ICustomMarshalInterface at MSDN

Manual Marshaling

What do you do when the default marshaling rules (amidall the variations that the DllImport and MarshalAs attributespermit)
don't allow you to invoke a given function? You do it manually bymaking extensive use of the 
Marshal class
methods.

TODO: finish.

Marshaling char**

Note: The original howto can be found here: http://hisham.cc/files/howto/marshalling_strings/

The key in the following tutorial isSystem.Runtime.InteropServices, where we can find the Marshal class. That classis very useful because it bridges created managed objects and unmanaged ones.Its functionalities are
very similar to blocks, unsafe, and more. For example,let's say that all pointer types in .NET are saved in an instance of the typeIntPtr. With the Marshal class, we can perform any operation like adding adetermined number of bytes in order to point to other
objects, and convertingthings that are there in a structure or a chain (other thing is if in thatmemory direction is something with sense or not).

Using this piece of code, we can see how to put data intoan unmanaged structure through a pointer obtained from a function or an externalpublic structure of a native library.

By default C#, establishes the order that is mostoptimized for the structure fields in memory. Nevertheless, if we want to dumpthe contents of the unmanaged pointer in our structure correctly, all thefields must maintain
their order and size (in bytes). To solve this problem, weapply the attribute StructLayout(LayoutKind.Sequential) on the structure.

 [StructLayout(LayoutKind.Sequential)]

   publicclass_EcoreEventDndEnter

     {

        publicIntPtr win;

        publicIntPtr src;

        publicIntPtr types;

        publicintnum_types;

        public_EcoreEventDndEnter()

          {}

     }

The structure has two pointers to X windows, a pointer toa character matrix (a string table), and an integer that defines the number ofpresent chains. It seems to be a complex structure, but it isn't.

We want to create a class from that structure. So, wehave a constructor with a parameter that points to the unmarshalled structure.Afterwards, we use the Marshal class in order to utilize its basic types.Everything
is straight forward except for the matrix that couldn't be convertedwithout producing an error. The Mono.UnixMarshal class solves that problem,which was basically a portability issue:

   publicclassEcoreEventDndEnter

     {

        publicWindow win;

        publicWindow src;

        publicstring[]types;

        publicintnum_types;

        publicEcoreEventDndEnter()

          {}

 

        publicEcoreEventDndEnter(IntPtr EventInfo)

          {

             _EcoreEventDndEnter e
=new _EcoreEventDndEnter();

             e =(_EcoreEventDndEnter)Marshal.PtrToStructure(EventInfo,typeof(_EcoreEventDndEnter));

             win =new
Window
(e.win);

             src =new
Window
(e.src);

             types =Mono.Unix.UnixMarshal.PtrToStringArray(e.types);

             num_types = e.num_types;

          }

     }

The fact that Mono is open-source helped understand thatMonoUnix is merely an extension of System.Runtime.InteropServices.Marshal,using the same methods of the Marshal class. This means that we can put thenecessary
code of UnixMarshal in the project in order to guarantee itsportability from Windows Mono or even .NET. Here's the piece of code:

 

   publicclassCommon

     {

        publicstaticstringPtrToString(IntPtr
p
)

          {

             //TODO: deal with character set issues. Will PtrToStringAnsi always

             //"Do The Right Thing"?

             if(p==IntPtr.Zero)

               returnnull;

             returnMarshal.PtrToStringAnsi(p);

          }

 

 

        publicstaticstring[]PtrToStringArray(IntPtr
stringArray
)

          {

             if(stringArray==
IntPtr
.Zero)

               returnnewstring[]{};

 

 

             intargc= CountStrings(stringArray);

             returnPtrToStringArray(argc, stringArray);

          }

 

        privatestaticint CountStrings(IntPtr
stringArray
)

          {

             intcount=0;

             while(Marshal.ReadIntPtr(stringArray,count*IntPtr.Size)!=
IntPtr
.Zero)

               ++count;

             returncount;

          }

 

 

        publicstaticstring[]PtrToStringArray(intcount,
IntPtr stringArray
)

          {

             if(count<0)

               thrownew
ArgumentOutOfRangeException
("count","<0");

             if(stringArray==IntPtr.Zero)

               returnnewstring[count];

 

 

             string[]members=newstring[count];

             for(int i=0;
i
<count;++i){

                IntPtr s =Marshal.ReadIntPtr(stringArray,i*
IntPtr
.Size);

                members[i]=PtrToString(s);

             }

 

 

             returnmembers;

          }

     }

After the following step, the final class is completely portable:

 publicclassEcoreEventDndEnter

     {

        publicWindow win;

        publicWindow src;

        publicstring[]types;

        publicintnum_types;

        public EcoreEventDndEnter()

          {}

 

        publicEcoreEventDndEnter(IntPtr EventInfo)

          {

             _EcoreEventDndEnter e
=new _EcoreEventDndEnter();

             e =(_EcoreEventDndEnter)Marshal.PtrToStructure(EventInfo,typeof(_EcoreEventDndEnter));

             win =new
Window
(e.win);

             src =new
Window
(e.src);

             types =Common.PtrToStringArray(e.types);

             num_types = e.num_types;

          }

     }

回避封送

封送不是万能的,因为封送隐式的复制数据。由于数据传递是一个复杂的,消耗时间的过程,可能使封送出现问题。可选的,如果不复制数据也可能出现问题,因为数据不知道或者是很可能改变的。

后面一个情况的例子是GTK+库。GTK+是用C写的一个面向对象的工具集。像所有面向对象库一样,它可能有未知数量的继承类,他们相互之间拥有不同的类大小。另外,类实例基本通过指针访问。因此,在托管和非托管内存之间封送整个类并不可选,因为复制并不是想要的,想要的是访问相同的实例。

另一个示例是使用不透明的数据类型;也就是,交互的类型仅通过指针,类型内部没有任何东西公有。这描述了大部分Win32APIHANDLE用来表示大部分对象。

有两种方式在C#处理:安全类型方法,包括使用指针和C#语言的unsafe功能,以及CLS-compliant方式,使用System.IntPtr表示一个void指针。

在这两种情况,托管和非托管代码的区别变得明显。托管内存就是类型安全的,而非托管内存不是(由于System.IntPtr用来指向非托管内存,没有任何方法确认SYstem.IntPtr指向的类型)。

注意如果非托管内存本身是垃圾回收的,这可能不安全。这可能由于非托管内存由不同的运行时系统处理(PythonRubyLisp等等)或者使用了垃圾回收器(Boehm)。如果非托管内存是垃圾回收的,当非托管内存经过一次垃圾回收,System.IntPtr不会更新,从而产生内存崩溃。

例如,给定一个非托管API

 typedefvoid*HANDLE;

 

 bool CreateItem (HANDLE*item);

 void DestroyItem(HANDLEitem);

 int GetInfo(HANDLEitem);

类型安全C#封装可以是(使用不安全代码):

 structItem{

    [DllImport("library")]

    publicstaticunsafeextern

       boolCreateItem(outItem*
item
);

 

    [DllImport("library")]

    publicstaticunsafeexternvoidDestroyItem(Item*item);

 

    [DllImport("library")]

    publicstaticunsafeexternintGetInfo(Item*
item
);

 }

 

 classExampleUsage{

    publicstaticunsafevoid
Main
()

    {

       Item* item;

       Item.CreateItem(outitem);

       int n=Item.GetInfo(item);

       System.Console.WriteLine("itemcount:
{0}"
,

          n.ToString());

       Item.DestroyItem(item);

    }

 }

在你不传递任意内存位置给静态的Item函数的时候这是类型安全的,你必须传递指针给一个Item结构体。这并不是严格的类型安全,但是它可能最小化内存访问违规。最大的问题是它使用了不安全代码,并且这可能对于其他的.NET语言来说不安全,例如VisualBasic
.Net
或者javaScript

CLS兼容版本使用System.IntPtr来引用非托管内存。这与Marshal类与非托管内存交互的方法类似。

 classItem{

    [DllImport("library")]

    publicstaticexternbool

       CreateItem (outSystem.IntPtritem);

 

    [DllImport("library")]

    publicstaticexternvoid
DestroyItem
(System.IntPtr item);

 

    [DllImport("library")]

    publicstaticexternintGetInfo(System.IntPtr
item
);

 }

 

 classExampleUsage{

    publicstaticunsafevoid
Main
()

    {

       System.IntPtritem=null;

       Item.CreateItem(out item);

       int n=Item.GetInfo(item);

       System.Console.WriteLine("itemcount:
{0}"
,

          n.ToString());

       Item.DestroyItem(item);

    }

 }

这不安全是由于非常容易使用错误指针。例如,你正在使用两个不同的库并使用System.IntPtr封装他们,很可能传递从一个库分配的对象给另一个库导出的函数,而CLI运行时不会捕获这个错误,而“unsafe”C#代码可能捕获这个错误。

然而,这通常不是问题,因为大部分托管代码不应该与P/Invoke代码交互,而是与托管代码封装过的非托管代码交互,这可以提供更加自然的接口给托管客户端。

GC-Safe P/Invoke 代码

上述的封装代码有一个问题:在用户代码和运行时垃圾回收器(GC)之间有一个竞争条件。.NetGC并不保守。它了解涉及到的所有类型,并且可以区别看起来像指针的整数以及真实的指针值。它知道所有栈分配的变量,以及这些变量的作用范围。最后,GC看不见非托管代码。

结果就是很可能在实例的方法正在执行的时候GC回收了类实例。

这怎么可能?如果方法不再引用类数据(接口成员),也没有其他代码引用它,GC可能手机类。毕竟,如果没有使用实例成员,也没有谁在使用实例,实例被回收有什么关系呢?

如果非托管代码想让实例任然存在的话就变得很重要。或者,如果类有个终结器,可以在本机代码从本机方法中执行的时候执行。

这儿有一些从 Chris Brumme's博客改编的示例:

 class C{

    // handle intounmanaged memory, for an unmanaged object

    IntPtr _handle;

 

    // performs someoperation on h

    [DllImport("...")]

    staticexternvoidOperateOnHandle(IntPtr
h
);

 

    // frees resources ofh

    [DllImport("...")]

    staticexternvoidDeleteHandle(IntPtr
h
);

 

    ~C()

    {

       DeleteHandle(h);

    }

 

    publicvoid m()

    {

       OperateOnHandle(_handle);

       // no furtherreferences to _handle

    }

 }

 

 classOther

 {

    voidwork()

    {

       C c =new
C
();

       c.m();

       // no furtherreferences to c. 

       // c is now eligablefor collection.

    }

 }

设想一下: Other.work 调用 C.m ,这有调用非托管代码的C.OperateOnHandle 注意 Other.work不再使用 ,因此 符合回收条件,并且被放置在GC的回收队列.

这通常会是合理的,除了与非托管代码交互的情况。非托管代码C.OperateOnHandle 任然在使用实例c持有的成员,
但是GC不知道——也不可能知道。

这就引进了可能性,尽管不太可能,C.OperateOnHandle is仍然被执行的时候C.DeleteHandle 被调用
(
GC的垃圾回收线程) .

假定非托管代码不欣赏这一点是公平的。假定这可能对进程产生大问题,包括分段错误也是公平的。

事实上,bug.Net1.0存在的bug很像,在某个注册表包装类中。

你如何避免这个问题?不要使用原始的IntPtr。使用IntPtrGC无法知道需要回顾的类。要避免这个bug,我们避免使用IntPtr

我们使用HandleRef取代IntPtr。这是一个持有包含类的引用和指针值的结构体。

下面,我们用 P/Invoke代码访问HandleRefs而不是IntPtr参数。HandleRefs对于运行时和GC系统是特殊的,并且在封送操作的时候他们塌缩IntPtr

这可以让我们写安全的代码:

 class C{

    // handle intounmanaged memory, for an unmanaged object

    HandleRef _handle;

 

    // performs some operationon h

    [DllImport("...")]

    staticexternvoidOperateOnHandle(HandleRef
h
);

 

    // frees resources ofh

    [DllImport("...")]

    staticexternvoidDeleteHandle(HandleRef
h
);

 

    // Creates Resource

    [DllImport("...")]

    staticexternIntPtr CreateHandle();

 

    public C()

    {

       IntPtr h =CreateHandle();

       _handle =new
HandleRef
(this, h);

    }

 

    ~C()

    {

       DeleteHandle(h);

    }

 

    publicvoid m()

    {

       OperateOnHandle(_handle);

       // no furtherreferences to _handle

    }

 }

 

 classOther

 {

    voidwork()

    {

       // Note: no change toclient

       C c =new
C
();

       c.m();

    }

 }

See also: ChrisBrumme's Blog: Lifetime, GC.KeepAlive, handle recycling

.Net 2.0 SafeHandles

.NET2.0中,引进了一个新的封装非托管句柄的机制。这个新机制有SafeHandle类导出。SafeHandlesIntPtr的形式压缩一个句柄,但是作为SafeHandle类的子类导出(例如SafeFileHandle或者SafeWaitHandle),开发者安全的获得类型。

SafeHandle另外提供了一个避免偶然的句柄回收的机制。

运行时特别对待SafeHandles并在使用 P/Invoke调用的时候自动提供封送。行为依赖于它的使用:

·                    在输出参数中,SafeHandle的句柄被传递。

·                    在返回值中,具体SafeHandle类的新实例被创建,句柄值设置为返回的IntPtr值。

·                     refSafeHandle中,输出值被忽略(必须为0)并且返回值转换为恰当的SafeHandle

·                    在结构体字段中,SafeHandle的句柄被传递。

对于mono真实实现的细节,参考SafeHandles文档。

恰当的释放资源

在避免封送的时候,你涉及了非托管内存和其他从托管代码的资源。这涉及大量责任。它也创造了大量的关系,因为在托管代码中很对东西可能出错,特别因为异常和相对复杂度。当任何函数都可能抛出异常的时候,确保资源合适的释放是非常重要的。

对前述部分的一个建议是,确保资源最终释放的一个简单方法是用包含终结器的类来包装资源。终结器最后可以释放资源。

这个方法有两个问题,他们都涉及到了垃圾回收器:

1.                垃圾回收期不能看到非托管内存,他们仅与托管内存打交道。这样,如果在非托管内存中有大量内存(例如图像和视频数据),GC看到的所有东西只是涉及到这些数据的IntPtr,而不是被托管内存持有的非托管内存的大小。因此,GC并不知道一个回收应该执行(没有任何内存压力可以引起回收)。

.Net2.0中使用 System.GC.AddMemoryPressure(long) 方法部分修复这个问题,这可以告诉GC一个托管对象引用了多少非托管内存,这可以提高GC的启发率。然而,必须修改代码来支持这个功能,并且在.NET
1.1
中没用。

2.                在对象被回收的时候.NET的垃圾收集器不会执行对象的终结器。取代的是,对象呗提升为新代,在下一次二代回收的时候被执行。

例如,如果拥有终结器的0代对象适合回收(比如,没有对象引用它了),对象产生新的一代,并被放置在1代的终结队列。下一次1代被回收,对象的终结器被执行。

问题2是真实的杀手,因为收集频率域代数成反比。一般0代是1代收集频率的10倍,1代是2代的10倍。2代事实上极少回收,因为这些应该是应用程序的常驻对象。

鉴于最快终结器收集后两个集合,一代0和一代1(在0代10次收集后发生),在执行终结器前它可能是一个长时间。依靠终结器资源处理和收集是一个糟糕的主意,甚至在考虑到终结器不能保证执行的情况下,特别是在程序关闭的时候。

幸运的是,在.NET类库中有一个简单的原型来帮助确保资源快速释放:

·                    实现System.IDisposable接口。

·                    在用完资源的时候调用Dispose方法。Dispose方法做两件事:

1.                            释放资源,

2.                            调用GC.SuppressFinalize

SuppressFinalize 通知GC对象的终结器不需要被调用,从而允许对象在第一次收集的时候被释放,而不必再相当长的延时之后。一个简单的实现可以是:

 // for IntPtr, IDisposable

 usingSystem;

 

 // for HandleRef, DllImport

 usingSystem.Runtime.InteropServices;

 

 sealedclassUnmanagedResource:
IDisposable

 {

    [DllImport("...")]

    privatestaticexternIntPtr CreateResource();

 

    [DllImport("...")]

    privatestaticextern

       voidDeleteResource(HandleRef handle);

 

    // Use a HandleRef toavoid race conditions;

    // see the GC-SafeP/Invoke Code section

    privateHandleRef _handle;

 

    publicUnmanagedResource()

    {

       IntPtr h =CreateResource();

       _handle =new
HandleRef
(this, h);

    }

 

    // Provide access to3rd party code

    publicHandleRef Handle{

       get {return_handle;}

    }

 

    // Dispose of theresource

    publicvoidDispose()

    {

       Cleanup ();

 

       // Prevent the objectfrom being placed on the

       // finalization queue

       System.GC.SuppressFinalize(this);

    }

 

    // Finalizer providedin case Dispose isn't called.

    // This is a fallbackmechanism, but shouldn't be

    // relied upon (seeprevious discussion).

    ~UnmanagedResource ()

    {

       Cleanup ();

    }

 

    // Really dispose ofthe resource

    privatevoidCleanup()

    {

       DeleteResource (Handle);

 

       // Don't permit thehandle to be used again.

       _handle =new
HandleRef
(this,IntPtr.Zero);

    }

 }

 IDisposable 例子提供了虚的 Dispose(bool) 方法, Dispose 和终结器委托的,与上面 Cleanup() 方法使用相反。然而,同时提供虚函数和非密封的类来继承,可能不是好主意。

由于垃圾收集器,这可能再次是潜在的坏注意。当对象产生一个GC代的时候,所有它引用的对象都产生新代,递归的。因此如果你的可终结对象包含其他对象的ArrayList,包括ArrayList,以及它包含的对象,以及这些对象引用的所有对象(递归的)都会被晋升为新代。这可能潜在产生一代大量托管内存。

鉴于GC晋升规则,强烈建议可终结对象为叶子节点;也就是,对象在托管内存树中不引用其他对象。为此,建议可终结对象不要为可继承对象,来最小化引用对象的托管对象数量。Rico Mariani"Almost-rule
#2: Never havefinalizers"
中讨论了这个。

实现 IDisposable 接口不是完整的方案,因为它需要用户记住调用Dispose方法。C#使用block可以确保在块结束的时候Dispose方法一定被调用。

 using(UnmanagedResourceur=new
UnmanagedResource
()){

    // Use the unmanagedresource.  It will be automatically

    // disposed of at theend of this block.

 }

See also:

·                    RicoMariani's Blog: Two things to avoid for better memory
usage
.

MiscellaneousTopics

Topics that didn't seem to fit in anywhere else, butmight be useful.

Meaning of"Unsafe"

A "problem" is that "unsafe" is anoverloaded term. It can refer to the use of the "unsafe" C# keyword,and it can be used as "anything that isn't safe", which may notrequire the "unsafe" keyword.

So, "unsafe" can mean (a) C# keyword; (b)violates .NET type system (similar to (a)); (c) may be insecure (reading filesfrom a web client); (d) capable of causing a segfault. There are likely othermeanings people can
dream up as well. Note that (d) doesn't imply (b), as faras .NET is concerned. The runtime could itself have a bug that generates asegfault, but this doesn't violate the type system.

IntPtr doesn't require a violation of the type system, asyou can't get the address of a .NET object (unless you "pin" it,which would require the appropriate Security rights), and is thus principallyuseful for interacting
with unmanaged code, which exists outside of the .NETtype system.

Surely, this is pure semantics, but I can see thedesigners perspective.

Security

.NET has a highly flexible security system. You can'tinvoke DllImported functions unless your app has the appropriate securityrights -- generally, that the app is running on the local machine. If you'rerunning it
from a network share, or from a web site (similar to Java Applets),then your app will get a SecurityException.

You can get lots of security exceptions for variousthings, actually. Opening files can generate a security exception, for example.

System.Security.Permissions.SecurityPermission is
needed with 
SecurityPermissionFlag.UnmanagedCode specified
in order to perform aP/Invoke.

Programs can't specify this permission; they can onlyrequest it (or demand it, and if they can't get it, a SecurityException isthrown).

Administrators are the people who specify whatpermissions an application actually receives.

That's about the limits of my knowledge -- Security isn'tmy forte. You might find the following topics interesting.

·                    RequestingPermissions
at MSDN

·                    SecuritySyntax
at MSDN

·                    CodeAccess
Security at MSDN

·                    InheritanceDemands
at MSDN

See also: UnsafeCode at MSDN

 

抱歉!评论已关闭.