该教程说明如何在 C# 中使用不安全代码(使用指针的代码)。易索博客t8_ be;Z9dNX
'
|
教程
在 C# 中很少需要使用指针,但仍有一些需要使用的情况。例如,在下列情况中使用允许采用指针的不安全上下文是正确的:
处理磁盘上的现有结构
涉及内部包含指针的结构的高级 COM 或平台调用方案
性能关键代码
不鼓励在其他情况下使用不安全上下文。具体地说,不应该使用不安全上下文尝试在 C# 中编写 C 代码。
br,Y_k(wk
~
F5o5T0
警告 使用不安全上下文编写的代码无法被验证为安全的,因此只有在代码完全受信任时才会执行该代码。换句话说,不可以在不受信任的环境中执行不安全代码。例如,不能从 Internet 上直接运行不安全代码。易索博客
-
m ILhr3[#qjQ
!
f\
该教程包括下列示例:
示例
1
使用指针复制一个字节数组。
示例
2
显示如何调用 Windows ReadFile 函数。
示例
3
显示如何打印可执行文件的 Win32 版本。
示例
1
以下示例使用指针将一个字节数组从 src 复制到 dst。用
/
unsafe
选项编译此示例。易索博客 wc;x
*
QB
//
fastcopy.cs
//
compile with: /unsafe
using
System;
class
Test
{
//
The unsafe keyword allows pointers to be used within
//
the following method:
static
unsafe
void
Copy(
byte
[] src,
int
srcIndex,
byte
[] dst,
int
dstIndex,
int
count)
{
if
(src
==
null
||
srcIndex
<
0
||
dst
==
null
||
dstIndex
<
0
||
count
<
0
)
{
throw
new
ArgumentException();
}
int
srcLen
=
src.Length;
int
dstLen
=
dst.Length;
if
(srcLen
-
srcIndex
<
count
||
dstLen
-
dstIndex
<
count)
{
throw
new
ArgumentException();
}
//
The following fixed statement pins the location of
//
the src and dst objects in memory so that they will
//
not be moved by garbage collection.
fixed
(
byte
*
pSrc
=
src, pDst
=
dst)
{
byte
*
ps
=
pSrc;
byte
*
pd
=
pDst;
//
Loop over the count in blocks of 4 bytes, copying an
//
integer (4 bytes) at a time:
for
(
int
n
=
0
; n
<
count
/
4
; n
++
)
{
*
((
int
*
)pd)
=
*
((
int
*
)ps);
pd
+=
4
;
ps
+=
4
;
}
//
Complete the copy by moving any bytes that weren't
//
moved in blocks of 4:
for
(
int
n
=
0
; n
<
count
%
4
; n
++
)
{
*
pd
=
*
ps;
pd
++
;
ps
++
;
}
}
}
static
void
Main(
string
[] args)
{
byte
[] a
=
new
byte
[
100
];
byte
[] b
=
new
byte
[
100
];
for
(
int
i
=
0
; i
<
100
;
++
i)
a[i]
=
(
byte
)i;
Copy(a,
0
, b,
0
,
100
);
Console.WriteLine(
"
The first 10 elements are:
"
);
for
(
int
i
=
0
; i
<
10
;
++
i)
Console.Write(b[i]
+
"
"
);
Console.WriteLine(
"
\n
"
);
}
}输出示例
The first
10
elements are:
0
1
2
3
4
5
6
7
8
9代码讨论
请注意使用了
unsafe
关键字,这允许在 Copy 方法内使用指针。
fixed
语句用于声明指向源和目标数组的指针。它锁定 src 和 dst 对象在内存中的位置以便使其不会被垃圾回收移动。当
fixed
块完成后,这些对象将被解除锁定。
通过略过数组界限检查,不安全代码可提高性能。
示例
2
本示例显示如何从 Platform. SDK 调用 Windows ReadFile 函数,这要求使用不安全上下文,因为读缓冲区需要将指针作为参数。易索博客 B
!
Q4m3V
^-
J
*
W
//
readfile.cs
//
compile with: /unsafe
//
arguments: readfile.cs
//
Use the program to read and display a text file.
using
System;
using
System.Runtime.InteropServices;
using
System.Text;
class
FileReader
{
const
uint
GENERIC_READ
=
0x80000000
;
const
uint
OPEN_EXISTING
=
3
;
IntPtr handle;
[DllImport(
"
kernel32
"
, SetLastError
=
true
)]
static
extern
unsafe
IntPtr CreateFile(
string
FileName,
//
file name
uint
DesiredAccess,
//
access mode
uint
ShareMode,
//
share mode
uint
SecurityAttributes,
//
Security Attributes
uint
CreationDisposition,
//
how to create
uint
FlagsAndAttributes,
//
file attributes
int
hTemplateFile
//
handle to template file
);
[DllImport(
"
kernel32
"
, SetLastError
=
true
)]
static
extern
unsafe
bool
ReadFile(
IntPtr hFile,
//
handle to file
void
*
pBuffer,
//
data buffer
int
NumberOfBytesToRead,
//
number of bytes to read
int
*
pNumberOfBytesRead,
//
number of bytes read
int
Overlapped
//
overlapped buffer
);
[DllImport(
"
kernel32
"
, SetLastError
=
true
)]
static
extern
unsafe
bool
CloseHandle(
IntPtr hObject
//
handle to object
);
public
bool
Open(
string
FileName)
{
//
open the existing file for reading
handle
=
CreateFile(
FileName,
GENERIC_READ,
0
,
0
,
OPEN_EXISTING,
0
,
0
);
if
(handle
!=
IntPtr.Zero)
return
true
;
else
return
false
;
}
public
unsafe
int
Read(
byte
[] buffer,
int
index,
int
count)
{
int
n
=
0
;
fixed
(
byte
*
p
=
buffer)
{
if
(
!
ReadFile(handle, p
+
index, count,
&
n,
0
))
return
0
;
}
return
n;
}
public
bool
Close()
{
//
close file handle
return
CloseHandle(handle);
}
}
class
Test
{
public
static
int
Main(
string
[] args)
{
if
(args.Length
!=
1
)
{
Console.WriteLine(
"
Usage : ReadFile <FileName>
"
);
return
1
;
}
if
(
!
System.IO.File.Exists(args[
0
]))
{
Console.WriteLine(
"
File
"
+
args[
0
]
+
"
not found.
"
);
return
1
;
}
byte
[] buffer
=
new
byte
[
128
];
FileReader fr
=
new
FileReader();
if
(fr.Open(args[
0
]))
{
//
Assume that an ASCII file is being read
ASCIIEncoding Encoding
=
new
ASCIIEncoding();
int
bytesRead;
do
{
bytesRead
=
fr.Read(buffer,
0
, buffer.Length);
string
content
=
Encoding.GetString(buffer,
0
,bytesRead);
Console.Write(
"
{0}
"
, content);
}
while
( bytesRead
>
0
);
fr.Close();
return
0
;
}
else
{
Console.WriteLine(
"
Failed to open requested file
"
);
return
1
;
}
}
}输入示例
在编译和运行此示例时,下列来自 readfile.txt 的输入将产生在“输出示例”中显示的输出。
,Jv9x,nF,ql0
line
1
line 2输出示例
line
1
line 2代码讨论
传递到 Read 函数的字节数组是托管类型。这意味着公共语言运行库垃圾回收器可能会随意地对数组使用的内存进行重新定位。
fixed
语句允许您获取指向字节数组使用的内存的指针,并且标记实例,以便垃圾回收器不会移动它。易索博客uwQJn
&
p
在
fixed
块的末尾,将标记该实例以便可以移动它。此功能称为声明式锁定。锁定的好处是系统开销非常小,除非在
fixed
块中发生垃圾回收(但此情况不太可能发生)。易索博客P
*
X4Oa
!|*
G,Wn
'
g
示例
3
本示例读取并显示可执行文件的 Win32 版本号,在本示例中此版本号与程序集版本号相同。本例中使用的可执行文件为 printversion.exe。本示例使用 Platform. SDK 函数 VerQueryValue、GetFileVersionInfoSize 和 GetFileVersionInfo 从指定的版本信息资源中检索指定的版本信息。
,S
~+~
1j9Y4X0
本示例使用了指针,因为它可以简化在其签名中使用指向指针的指针(这在 Win32 API 中很常见)的方法的使用。易索博客7e`Ua$Z`${8gYa
//
printversion.cs
//
compile with: /unsafe
using
System;
using
System.Reflection;
using
System.Runtime.InteropServices;
//
Give this assembly a version number:
[assembly:AssemblyVersion(
"
4.3.2.1
"
)]
public
class
Win32Imports
{
[DllImport(
"
version.dll
"
)]
public
static
extern
bool
GetFileVersionInfo (
string
sFileName,
int
handle,
int
size,
byte
[] infoBuffer);
[DllImport(
"
version.dll
"
)]
public
static
extern
int
GetFileVersionInfoSize (
string
sFileName,
out
int
handle);
//
The third parameter - "out string pValue" - is automatically
//
marshaled from ANSI to Unicode:
[DllImport(
"
version.dll
"
)]
unsafe
public
static
extern
bool
VerQueryValue (
byte
[] pBlock,
string
pSubBlock,
out
string
pValue,
out
uint
len);
//
This VerQueryValue overload is marked with 'unsafe' because
//
it uses a short*:
[DllImport(
"
version.dll
"
)]
unsafe
public
static
extern
bool
VerQueryValue (
byte
[] pBlock,
string
pSubBlock,
out
short
*
pValue,
out
uint
len);
}
public
class
C
{
//
Main is marked with 'unsafe' because it uses pointers:
unsafe
public
static
int
Main ()
{
try
{
int
handle
=
0
;
//
Figure out how much version info there is:
int
size
=
Win32Imports.GetFileVersionInfoSize(
"
printversion.exe
"
,
out
handle);
if
(size
==
0
)
return
-
1
;
byte
[] buffer
=
new
byte
[size];
if
(
!
Win32Imports.GetFileVersionInfo(
"
printversion.exe
"
, handle, size, buffer))
{
Console.WriteLine(
"
Failed to query file version information.
"
);
return
1
;
}
short
*
subBlock
=
null
;
uint
len
=
0
;
//
Get the locale info from the version info:
if
(
!
Win32Imports.VerQueryValue (buffer,
@"
\VarFileInfo\Translation
"
,
out
subBlock,
out
len))
{
Console.WriteLine(
"
Failed to query version information.
"
);
return
1
;
}
string
spv
=
@"
\StringFileInfo\
"
+
subBlock[
0
].ToString(
"
X4
"
)
+
subBlock[
1
].ToString(
"
X4
"
)
+
@"
\ProductVersion
"
;
byte
*
pVersion
=
null
;
//
Get the ProductVersion value for this program:
string
versionInfo;
if
(
!
Win32Imports.VerQueryValue (buffer, spv,
out
versionInfo,
out
len))
{
Console.WriteLine(
"
Failed to query version information.
"
);
return
1
;
}
Console.WriteLine (
"
ProductVersion == {0}
"
, versionInfo);
}
catch
(Exception e)
{
Console.WriteLine (
"
Caught unexpected exception
"
+
e.Message);
}
return
0
;
}
}输出示例
ProductVersion
==
4.3
.
2.1