欲练神功,引刀自宫。为了避免内存管理的烦恼,Java咔嚓一下,把指针砍掉了。当年.Net也追随潮流,咔嚓了一下,化名小桂子,登堂入室进了皇宫。康熙往下面一抓:咦?还在?——原来是假太监韦小宝。
打开unsafe选项,C#指针就biu的一下子蹦出来了。指针很强大,没必要抛弃这一强大的工具。诚然,在大多数情况下用不上指针,但在特定的情况下还是需要用到的。比如:
(1)大规模的运算中使用指针来提高性能;
(2)与非托管代码进行交互;
(3)在实时程序中使用指针,自行管理内存和对象的生命周期,以减少GC的负担。
目前使用指针的主要语言是C和C++。但是由于语法限制,C和C++中的指针的玩法很单调,在C#中,可以进行更优雅更好玩的玩法。本文是《重新认识C#: 玩转指针》一文的续篇,主要是对《重新认识C#: 玩转指针》内容进行总结和改进。
C#下使用指针有两大限制:
(1)使用指针只能操作struct,不能操作class;
(2)不能在泛型类型代码中使用未定义类型的指针。
第一个限制没办法突破,因此需要将指针操作的类型设为struct。struct + 指针,恩,就把C#当更好的C来用吧。对于第二个限制,写一个预处理器来解决问题。
下面是我写的简单的C#预处理器的代码,不到200行:
代码
1
using
System;
2
using
System.Collections.Generic;
3
using
System.IO;
4
using
System.Text;
5
using
System.Text.RegularExpressions;
6
7
namespace
Orc.Util.Csmacro
8
{
9
class
Program
10
{
11
static
Regex includeReg
=
new
Regex(
"
#region\\s+include.+\\s+#endregion
"
);
12
static
Regex mixinReg
=
new
Regex(
"
(?<=#region\\s+mixin\\s)[\\s|\\S]+(?=#endregion)
"
);
13
///
<summary>
14
///
Csmacro [dir|filePath]
15
///
16
///
语法:
17
///
#region include ""
18
///
#endregion
19
///
20
///
</summary>
21
///
<param name="args"></param>
22
static
void
23
#region
include<>
24
Main
25
#endregion
26
(
string
[] args)
27
{
28
if
(args.Length
!=
1
)
29
{
30
PrintHelp();
31
return
;
32
}
33
34
String filePath
=
args[
0
];
35
36
Path.GetDirectoryName(filePath);
37
String dirName
=
Path.GetDirectoryName(filePath);
38
#if
DEBUG
39
Console.WriteLine(
"
dir:
"
+
dirName);
40
#endif
41
String fileName
=
Path.GetFileName(filePath);
42
#if
DEBUG
43
Console.WriteLine(
"
file:
"
+
fileName);
44
#endif
45
46
if
(String.IsNullOrEmpty(fileName))
47
{
48
Csmacro(
new
DirectoryInfo(dirName));
49
}
50
else
51
{
52
if
(fileName.EndsWith(
"
.cs
"
)
==
false
)
53
{
54
Console.WriteLine(
"
Csmacro只能处理后缀为.cs的源程序.
"
);
55
}
56
else
57
{
58
Csmacro(
new
FileInfo(fileName));
59
}
60
}
61
62
Console.WriteLine(
"
[Csmacro]:处理完毕.
"
);
63
64
#if
DEBUG
65
Console.ReadKey();
66
#endif
67
}
68
69
static
void
Csmacro(DirectoryInfo di)
70
{
71
Console.WriteLine(
"
[Csmacro]:进入目录
"
+
di.FullName);
72
73
foreach
(FileInfo fi
in
di.GetFiles(
"
*.cs
"
, SearchOption.AllDirectories))
74
{
75
Csmacro(fi);
76
}
77
}
78
79
static
void
Csmacro(FileInfo fi)
80
{
81
String fullName
=
fi.FullName;
82
if
(fi.Exists
==
false
)
83
{
84
Console.WriteLine(
"
[Csmacro]:文件不存在-
"
+
fullName);
85
}
86
else
if
(fullName.EndsWith(
"
_Csmacro.cs
"
))
87
{
88
return
;
89
}
90
else
91
{
92
String text
=
File.ReadAllText(fullName);
93
94
DirectoryInfo parrentDirInfo
=
fi.Directory;
95
96
MatchCollection mc
=
includeReg.Matches(text);
97
if
(mc
==
null
||
mc.Count
==
0
)
return
;
98
else
99
{
100
Console.WriteLine(
"
[Csmacro]:处理文件
"
+
fullName);
101
102
StringBuilder sb
=
new
StringBuilder();
103
104
Int32 from
=
0
;
105
foreach
(Match item
in
mc)
106
{
107
sb.Append(text.Substring(from, item.Index
-
from));
108
from
=
item.Index
+
item.Length;
109
sb.Append(Csmacro(parrentDirInfo, item.Value));
110
}
111
112
sb.Append(text.Substring(from, text.Length
-
from));
113
114
String newName
=
fullName.Substring(
0
, fullName.Length
-
3
)
+
"
_Csmacro.cs
"
;
115
if
(File.Exists(newName))
116
{
117
Console.WriteLine(
"
[Csmacro]:删除旧文件
"
+
newName);
118
}
119
File.WriteAllText(newName, sb.ToString());
120
Console.WriteLine(
"
[Csmacro]:生成文件
"
+
newName);
121
}
122
}
123
}
124
125
static
String Csmacro(DirectoryInfo currentDirInfo, String text)
126
{
127
String outfilePath
=
text.Replace(
"
#region
"
, String.Empty).Replace(
"
#endregion
"
, String.Empty).Replace(
"
include
"
,String.Empty).Replace(
"
\
""
,String.Empty).Trim();
128
try
129
{
130
if
(Path.IsPathRooted(outfilePath)
==
false
)
131
{
132
outfilePath
=
currentDirInfo.FullName
+
@"
\
"
+
outfilePath;
133
}
134
FileInfo fi
=
new
FileInfo(outfilePath);
135
if
(fi.Exists
==
false
)
136
{
137
Console.WriteLine(
"
[Csmacro]:文件
"
+
fi.FullName
+
"
不存在.
"
);
138
return
text;
139
}
140
else
141
{
142
return
GetMixinCode(File.ReadAllText(fi.FullName));
143
}
144
}
145
catch
(Exception ex)
146
{
147
Console.WriteLine(
"
[Csmacro]:出现错误(
"
+
outfilePath
+
"
)-
"
+
ex.Message);
148
}
149
finally
150
{
151
}
152
return
text;
153
}
154
155
static
String GetMixinCode(String txt)
156
{
157
Match m
=
mixinReg.Match(txt);
158
if
(m.Success
==
true
)
159
{
160
return
m.Value;
161
}
162
else
return
String.Empty;
163
}
164
165
static
void
PrintHelp()
166
{
167
Console.WriteLine(
"
Csmacro [dir|filePath]
"
);
168
}
169
}
170
}
然后编译为 Csmacro.exe ,放入系统路径下。在需要使用预处理器的项目中添加 Pre-build event command lind:
Csmacro.exe $(ProjectDir)
Visual Studio 有个很好用的关键字 “region” ,我们就把它当作我们预处理器的关键字。include 一个文件的语法是:
#region include "xxx.cs"
#endregion
一个文件中可以有多个 #region include 块。
被引用的文件不能全部引用,因为一个C#文件中一般包含有 using,namespace … 等,全部引用的话会报编译错误。因此,在被引用文件中,需要通过关键字来规定被引用的内容:
#region mixin
…
#endregion
这个预处理器比较简单。被引用的文件中只能存在一个 #region mixin 块,且在这个region的内部,不能有其它的region块。
预处理器 Csmacro.exe 的作用就是找到所有 cs 文件中的 #region include 块,根据 #region include 路径找到被引用文件,将该文件中的 #region mixin 块 取出,替换进 #region include 块中,生成一个以_Csmacro.cs结尾的新文件 。
由于C#的两个语法糖“partial” 和 “using”,预处理器非常好用。如果没有这两个语法糖,预处理器会很丑陋不堪。(谁说语法糖没价值!一些小小的语法糖,足以实现新的编程范式。)
partial 关键字 可以保证一个类型的代码存在几个不同的源文件中,这保证了预处理器的执行,您可以像写正常的代码一样编写公共部分代码,并且正常编译。
using 关键字可以为类型指定一个的别名。这是一个不起眼的语法糖,却在本文中非常重要:它可以为不同的类型指定一个相同的类型别名。之所以引入预处理器,就是为了复用包含指针的代码。我们可以将代码抽象成两部分:变化部分和不变部分。一般来说,变化部分是类型的型别,如果还有其它非类型的变化,我们也可以将这些变化封装成新的类型。这样一来,我们可以将变化的类型放在源文件的顶端,使用using 关键字,命名为固定的别名。然后把不变部分的代码,放在 #region mixin 块中。这样的话,让我们需要 #region include 时,只需要在 #region include 块的前面(需要在namespace {} 的外部)为类型别名指定新的类型。
举例说明,位图根据像素的格式可以分为很多种,这里假设有两种图像,一种是像素是一个Byte的灰度图像ImageU8,一个是像素是一个Argb32的彩色图像ImageArgb32。ImageU8代码如下:
代码
1
public
class
ImageU8
2
{
3
public
Int32 Width {
get
;
set
; }
4
public
Int32 Height {
get
;
set
; }
5
6
public
unsafe
Byte
*
Pointer;
7
public
unsafe
void
SetValue(Int32 row, Int32 col, Byte value)
8
{
9
Pointer[row
*
Width
+
col]
=
value;
10
}
11
}
在 ImageArgb32 中,我们也要写重复的代码:
代码
1
public
class
ImageArgb32
2
{
3
public
Int32 Width {
get
;
set
; }
4
public
Int32 Height {
get
;
set
; }
5
6
public
unsafe
Argb32
*
Pointer;
7
public
unsafe
void
SetValue(Int32 row, Int32 col, Argb32 value)
8
{
9
Pointer[row
*
Width
+
col]
=
value;
10
}
11
}
对于 Width和Height属性,我们可以建立基类来进行抽象和复用,然而,对于m_pointer和SetValue方法,如果放在基类中,则需要抹去类型信息,且变的十分丑陋。由于C#不支持泛型类型的指针,也无法提取为泛型代码。
使用 Csmacro.exe 预处理器,我们就可以很好的处理。
首先,建立一个模板文件 Image_Template.cs
代码
1
using
TPixel
=
System.Byte;
2
3
using
System;
4
5
namespace
XXX.Hidden
6
{
7
class
Image_Template
8
{
9
public
Int32 Width {
get
;
set
; }
10
public
Int32 Height {
get
;
set
; }
11
12
#region
mixin
13
14
public
unsafe
TPixel
*
Pointer;
15
public
unsafe
void
SetValue(Int32 row, Int32 col, TPixel value)
16
{
17
Pointer[row
*
Width
+
col]
=
value;
18
}
19
20
#endregion
21
}
22
}
然后建立一个基类 BaseImage,再从BaseImage派生ImageU8和ImageArgb32。两个派生类都是 partial 类:
代码
1
using
System;
2
using
System.Collections.Generic;
3
using
System.Text;
4
5
namespace
XXX
6
{
7
public
class
BaseImage
8
{
9
public
Int32 Width {
get
;
set
; }
10
public
Int32 Height {
get
;
set
; }
11
}
12
13
public
partial
class
ImageU8 : BaseImage
14
{
15
}
16
17
public
partial
class
ImageArgb32 : BaseImage
18
{
19
}
20
}
下面我们建立一个 ImageU8_ClassHelper.cs 文件,来 #region include 引用上面的模板文件:
1
using
TPixel
=
System.Byte;
2
3
using
System;
4
namespace
XXX
5
{
6
public
partial
class
ImageU8
7
{
8
#region
include "Image_Template.cs"
9
#endregion
10
}
11
}
编译,编译器会自动生成文件 “ImageU8_ClassHelper_Csmacro.cs” 。将这个文件引入项目中,编译通过。这个文件内容是:
代码
1
using
TPixel
=
System.Byte;
2
3
using
System;
4
namespace
XXX
5
{
6
public
partial
class
ImageU8
7
{
8
9
public
unsafe
TPixel
*
Pointer;
10
public
unsafe
void
SetValue(Int32 row, Int32 col, TPixel value)
11
{
12
Pointer[row
*
Width
+
col]
=
value;
13
}
14
15
}
16
}
对于 ImageArgb32 类也可以进行类似操作。
从这个例子可以看出,使用 partial 关键字,能够让原代码、模板代码、ClassHelper代码三者共存。使用 using 关键字,可以分离出代码中变化的部分出来。
下面是我写的图像操作的一些模板代码:
(1)通过模板提供指针和索引器:
代码
1
using
TPixel
=
System.Byte;
2
using
TCache
=
System.Int32;
3
using
TKernel
=
System.Int32;
4
5
using
System;
6
using
System.Collections.Generic;
7
using
System.Text;
8
9
namespace
Orc.SmartImage.Hidden
10
{
11
public
abstract
class
Image_Template : UnmanagedImage
<
TPixel
>
12
{
13
private
Image_Template()
14
:
base
(
1
,
1
)
15
{
16
throw
new
NotImplementedException();
17
}
18
19
#region
mixin
20
21
public
unsafe
TPixel
*
Start {
get
{
return
(TPixel
*
)
this
.StartIntPtr; } }
22
23
public
unsafe
TPixel
this
[
int
index]
24
{
25
get
26
{
27
return
Start[index];
28
}
29
set
30
{
31
Start[index]
=
value;
32
}
33
}
34
35
public
unsafe
TPixel
this
[
int
row,
int
col]
36
{
37
get
38
{
39
return
Start[row
*
this
.Width
+
col];
40
}
41
set
42
{
43
Start[row
*
this
.Width
+
col]
=
value;
44
}
45
}
46
47
public
unsafe
TPixel
*
Row(Int32 row)
48
{
49
if
(row
<
0
||
row
>=
this
.Height)
throw
new
ArgumentOutOfRangeException(
"
row
"
);
50
return
Start
+
row
*
this
.Width;
51
}
52
53
#endregion
54
}
55
}
(2)通过模板提供常用的操作和Lambda表达式支持
代码
1
using
TPixel
=
System.Byte;
2
using
TCache
=
System.Int32;
3
using
TKernel
=
System.Int32;
4
5
using
System;
6
using
System.Collections.Generic;
7
using
System.Text;
8
9
namespace
Orc.SmartImage.Hidden
10
{
11
static
class
ImageClassHelper_Template
12
{
13
#region
mixin
14
15
public
unsafe
delegate
void
ActionOnPixel(TPixel
*
p);
16
public
unsafe
delegate
void
ActionWithPosition(Int32 row, Int32 column, TPixel
*
p);
17
public
unsafe
delegate
Boolean PredicateOnPixel(TPixel
*
p);
18
19
public
unsafe
static
void
ForEach(
this
UnmanagedImage
<
TPixel
>
src, ActionOnPixel handler)
20
{
21
TPixel
*
start
=
(TPixel
*
)src.StartIntPtr;
22
TPixel
*
end
=
start
+
src.Length;
23
while
(start
!=
end)
24
{
25
handler(start);
26
++
start;
27
}
28
}
29
30
public
unsafe
static
void
ForEach(
this
UnmanagedImage
<
TPixel
>
src, ActionWithPosition handler)
31
{
32
Int32 width
=
src.Width;
33
Int32 height
=
src.Height;
34
35
TPixel
*
p
=
(TPixel
*
)src.StartIntPtr;
36
for
(Int32 r
=
0
; r
<
height; r
++
)
37
{
38
for
(Int32 w
=
0
; w
<
width; w
++
)
39
{
40
handler(w, r, p);
41
p
++
;
42
}
43
}
44
}
45
46
public
unsafe
static
void
ForEach(
this
UnmanagedImage
<
TPixel
>
src, TPixel
*
start,
uint
length, ActionOnPixel handler)
47
{
48
TPixel
*
end
=
start
+
src.Length;
49
while
(start
!=
end)
50
{
51
handler(start);
52
++
start;
53
}
54
}
55
56
public
unsafe
static
Int32 Count(
this
UnmanagedImage
<
TPixel
>
src, PredicateOnPixel handler)
57
{
58
TPixel
*
start
=
(TPixel
*
)src.StartIntPtr;
59
TPixel
*
end
=
start
+
src.Length;
60
Int32 count
=
0
;
61
while
(start
!=
end)
62
{
63
if
(handler(start)
==
true
) count
++
;
64
++
start;
65
}
66
return
count;
67
}
68
69
public
unsafe
static
Int32 Count(
this
UnmanagedImage
<
TPixel
>
src, Predicate
<
TPixel
>
handler)
70
{
71
TPixel
*
start
=
(TPixel
*
)src.StartIntPtr;
72
TPixel
*
end
=
start
+
src.Length;
73
Int32 count
=
0
;
74
while
(start
!=
end)
75
{
76
if
(handler(
*
start)
==
true
) count
++
;
77
++
start;
78
}
79
return
count;
80
}
81
82
public
unsafe
static
List
<
TPixel
>
Where(
this
UnmanagedImage
<
TPixel
>
src, PredicateOnPixel handler)
83
{
84
List
<
TPixel
>
list
=
new
List
<
TPixel
>
();
85
86
TPixel
*
start
=
(TPixel
*
)src.StartIntPtr;
87
TPixel
*
end
=
start
+
src.Length;
88
while
(start
!=
end)
89
{
90
if
(handler(start)
==
true
) list.Add(
*
start);
91
++
start;
92
}
93
94
return
list;
95
}
96
97
public
unsafe
static
List
<
TPixel
>
Where(
this
UnmanagedImage
<
TPixel
>
src, Predicate
<
TPixel
>
handler)
98
{
99
List
<
TPixel
>
list
=
new
List
<
TPixel
>
();
100
101
TPixel
*
start
=
(TPixel
*
)src.StartIntPtr;
102
TPixel
*
end
=
start
+
src.Length;
103
while
(start
!=
end)
104
{
105
if
(handler(
*
start)
==
true
) list.Add(
*
start);
106
++
start;
107
}
108
109
return
list;
110
}
111
112
///
<summary>
113
///
查找模板。模板中值代表实际像素值。负数代表任何像素。返回查找得到的像素的左上端点的位置。
114
///
</summary>
115
///
<param name="template"></param>
116
///
<returns></returns>
117
public
static
unsafe
List
<
System.Drawing.Point
>
FindTemplate(
this
UnmanagedImage
<
TPixel
>
src,
int
[,] template)
118
{
119
List
<
System.Drawing.Point
>
finds
=
new
List
<
System.Drawing.Point
>
();
120
int
tHeight
=
template.GetUpperBound(
0
)
+
1
;
121
int
tWidth
=
template.GetUpperBound(
1
)
+
1
;
122
int
toWidth
=
src.Width
-
tWidth
+
1
;
123
int
toHeight
=
src.Height
-
tHeight
+
1
;
124
int
stride
=
src.Width;
125
TPixel
*
start
=
(TPixel
*
)src.SizeOfType;
126
for
(
int
r
=
0
; r
<
toHeight; r
++
)
127
{
128
for
(
int
c
=
0
; c
<
toWidth; c
++
)
129
{
130
TPixel
*
srcStart
=
start
+
r
*
stride
+
c;
131
for
(
int
rr
=
0
; rr
<
tHeight; rr
++
)
132
{
133
for
(
int
cc
=
0
; cc
<
tWidth; cc
++
)
134
{
135
int
pattern
=
template[rr, cc];
136
if
(pattern
>=
0
&&
srcStart[rr
*
stride
+
cc]
!=
pattern)
137
{
138
goto
Next;
139
}
140
}
141
}
142
143
finds.Add(
new
System.Drawing.Point(c, r));
144
145
Next:
146
continue
;
147
}
148
}
149
150
return
finds;
151
}
152
153
#endregion
154
}
155
}
配合lambda表达式,用起来很爽。在方法“FindTemplate”中,有这一句:
if (pattern >= 0 && srcStart[rr * stride + cc] != pattern)
其中 srcStart[rr * stride + cc] 是 TPixel 不定类型,而 pattern 是 int 类型,两者之间需要进行比较,但是并不是所有的类型都提供和整数之间的 != 操作符。为此,我建立了个新的模板 TPixel_Template。
(3)通过模板提供 != 操作符 的定义
代码
1
using
TPixel
=
System.Byte;
2
using
System;
3
4
namespace
Orc.SmartImage.Hidden
5
{
6
public
struct
TPixel_Template
7
{
8
/*
9
#region mixin
10
11
public static Boolean operator ==(TPixel lhs, int rhs)
12
{
13
throw new NotImplementedException();
14
}
15
16
public static Boolean operator !=(TPixel lhs, int rhs)
17
{
18
throw new NotImplementedException();
19
}
20
21
public static Boolean operator ==(TPixel lhs, double rhs)
22
{
23
throw new NotImplementedException();
24
}
25
26
public static Boolean operator !=(TPixel lhs, double rhs)
27
{
28
throw new NotImplementedException();
29
}
30
31
public static Boolean operator ==(TPixel lhs, float rhs)
32
{
33
throw new NotImplementedException();
34
}
35
36
public static Boolean operator !=(TPixel lhs, float rhs)
37
{
38
throw new NotImplementedException();
39
}
40
41
#endregion
42
43
*/
44
}
45
}
这里,在 #region mixin 块被注释掉了,不注释掉编译器会报错。注释之后,不会影响程序预处理。
通过 ClassHelper类来使用模板:
代码
1
using
System;
2
using
System.Collections.Generic;
3
using
System.Text;
4
5
namespace
Orc.SmartImage
6
{
7
using
TPixel
=
Argb32;
8
using
TCache
=
System.Int32;
9
using
TKernel
=
System.Int32;
10
11
public
static
partial
class
ImageArgb32ClassHelper
12
{
13
#region
include "ImageClassHelper_Template.cs"
14
#endregion
15
}
16
17
public
partial
class
ImageArgb32
18
{
19
#region
include "Image_Template.cs"
20
#endregion
21
}
22
23
public
partial
struct
Argb32
24
{
25
#region
include "TPixel_Template.cs"
26
#endregion
27
}
28
}
由于 Argb32 未提供和 int 之间的比较,因此,在这里 #region include "TPixel_Template.cs"。而Byte可以与int比较,因此,在ImageU8中,就不需要#region include "TPixel_Template.cs":
代码
3
using
System;
4
using
System.Collections.Generic;
5
using
System.Text;
6
7
namespace
Orc.SmartImage
8
{
9
using
TPixel
=
System.Byte;
10
using
TCache
=
System.Int32;
11
using
TKernel
=
System.Int32;
12
13
public
static
partial
class
ImageU8ClassHelper
14
{
15
#region
include "ImageClassHelper_Template.cs"
16
#endregion
17
}
18
19
public
partial
class
ImageU8
20
{
21
#region
include "Image_Template.cs"
22
#endregion
23
}
24
}
是不是很有意思呢?强大的指针,结合C#强大的语法和快速编译,至少在图像处理这一块是很好用的。