1.为什么要用ActiveX?
网页本身的功能是有限的,要想实现一些网页本身不支持的功能,比如:网页上的p2p视频播放,就得靠ActiveX这种古老的技术。
2.c#能开发ActiveX吗?
严格意义上讲,c#是不能生成纯正ocx控件的,我们在vs.net中新建项目时,也找不到专门的"ActiveX项目"新建项,最多也只就能新 建"类库"得到一个dll而非ocx(因此我们也无法用传统的regsvr32来注册该dll),但是c#能开发com组件,activeX控件本质上讲 跟com是一类技术,所以用c#开发"能够让网页调用的com类库"还是可行的。
3.开发步骤:
(1)新建一个类库
(2)修改项目的"属性",在“生成”选项中把“输出”中的“为com互操作注册”勾中,然后再到“应用程序”选项中找到“程序集信息”按钮,点击它,在弹出的界面中勾中“使程序集COM可见(M)”
(3)修改AssemblyInfo.cs,增加[assembly: AllowPartiallyTrustedCallers()],完整内容类似下面这样:
代码
1
using
System.Reflection;
2
using
System.Runtime.CompilerServices;
3
using
System.Runtime.InteropServices;
4
using
System.Security;
5
6
//
General Information about an assembly is controlled through the following
7
//
set of attributes. Change these attribute values to modify the information
8
//
associated with an assembly.
9
[assembly: AssemblyTitle(
"
ActiveXDemo
"
)]
10
[assembly: AssemblyDescription(
""
)]
11
[assembly: AssemblyConfiguration(
""
)]
12
[assembly: AssemblyCompany(
"
Microsoft
"
)]
13
[assembly: AssemblyProduct(
"
ActiveXDemo
"
)]
14
[assembly: AssemblyCopyright(
"
Copyright ? Microsoft 2009
"
)]
15
[assembly: AssemblyTrademark(
""
)]
16
[assembly: AssemblyCulture(
""
)]
17
[assembly: AllowPartiallyTrustedCallers()]
18
19
//
Setting ComVisible to false makes the types in this assembly not visible
20
//
to COM components. If you need to access a type in this assembly from
21
//
COM, set the ComVisible attribute to true on that type.
22
[assembly: ComVisible(
true
)]
23
24
//
The following GUID is for the ID of the typelib if this project is exposed to COM
25
[assembly: Guid(
"
bd585d12-7f22-4b3f-959f-18efbfc53f94
"
)]
26
27
//
Version information for an assembly consists of the following four values:
28
//
29
//
Major Version
30
//
Minor Version
31
//
Build Number
32
//
Revision
33
//
34
//
You can specify all the values or you can default the Build and Revision Numbers
35
//
by using the '*' as shown below:
36
//
[assembly: AssemblyVersion("1.0.*")]
37
[assembly: AssemblyVersion(
"
1.0.0.0
"
)]
38
[assembly: AssemblyFileVersion(
"
1.0.0.0
"
)]
(4)新建一个IObjectSafety接口文件IObjectSafety.cs,内容如下:
代码
1
using
System;
2
using
System.Collections.Generic;
3
using
System.Text;
4
using
System.Runtime.InteropServices;
5
6
namespace
ActiveXDemo
7
{
8
[ComImport, GuidAttribute(
"
CB5BDC81-93C1-11CF-8F20-00805F2CD064
"
)]
9
[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
10
public
interface
IObjectSafety
11
{
12
[PreserveSig]
13
int
GetInterfaceSafetyOptions(
ref
Guid riid, [MarshalAs(UnmanagedType.U4)]
ref
int
pdwSupportedOptions, [MarshalAs(UnmanagedType.U4)]
ref
int
pdwEnabledOptions);
14
15
[PreserveSig()]
16
int
SetInterfaceSafetyOptions(
ref
Guid riid, [MarshalAs(UnmanagedType.U4)]
int
dwOptionSetMask, [MarshalAs(UnmanagedType.U4)]
int
dwEnabledOptions);
17
}
18
19
}
该内容除命名空间可以更改外,其它内容都是固定的,不要修改
(5)新建一个:Windows Forms-->“用户控件”,我们的主要逻辑就写在这里(还可以在它上面随便放置其它windows常用控件,跟winForm开发一样),不过首先要修改类定义,让其实现我们刚才定义的接口
代码
1
using
System;
2
using
System.Runtime.InteropServices;
3
using
System.Threading;
4
using
System.Windows.Forms;
5
6
7
namespace
ActiveXDemo
8
{
9
[Guid(
"
8d7d8518-ca58-4863-b94d-3c616fda7b35
"
)]
10
public
partial
class
MyActiveX : UserControl,IObjectSafety
11
{
12
delegate
void
D(
object
obj);
13
14
public
MyActiveX()
15
{
16
InitializeComponent();
17
}
18
19
#region
IObjectSafety 成员
20
21
private
const
string
_IID_IDispatch
=
"
{00020400-0000-0000-C000-000000000046}
"
;
22
private
const
string
_IID_IDispatchEx
=
"
{a6ef9860-c720-11d0-9337-00a0c90dcaa9}
"
;
23
private
const
string
_IID_IPersistStorage
=
"
{0000010A-0000-0000-C000-000000000046}
"
;
24
private
const
string
_IID_IPersistStream
=
"
{00000109-0000-0000-C000-000000000046}
"
;
25
private
const
string
_IID_IPersistPropertyBag
=
"
{37D84F60-42CB-11CE-8135-00AA004BB851}
"
;
26
27
private
const
int
INTERFACESAFE_FOR_UNTRUSTED_CALLER
=
0x00000001
;
28
private
const
int
INTERFACESAFE_FOR_UNTRUSTED_DATA
=
0x00000002
;
29
private
const
int
S_OK
=
0
;
30
private
const
int
E_FAIL
=
unchecked
((
int
)
0x80004005
);
31
private
const
int
E_NOINTERFACE
=
unchecked
((
int
)
0x80004002
);
32
33
private
bool
_fSafeForScripting
=
true
;
34
private
bool
_fSafeForInitializing
=
true
;
35
36
public
int
GetInterfaceSafetyOptions(
ref
Guid riid,
ref
int
pdwSupportedOptions,
ref
int
pdwEnabledOptions)
37
{
38
int
Rslt
=
E_FAIL;
39
40
string
strGUID
=
riid.ToString(
"
B
"
);
41
pdwSupportedOptions
=
INTERFACESAFE_FOR_UNTRUSTED_CALLER
|
INTERFACESAFE_FOR_UNTRUSTED_DATA;
42
switch
(strGUID)
43
{
44
case
_IID_IDispatch:
45
case
_IID_IDispatchEx:
46
Rslt
=
S_OK;
47
pdwEnabledOptions
=
0
;
48
if
(_fSafeForScripting
==
true
)
49
pdwEnabledOptions
=
INTERFACESAFE_FOR_UNTRUSTED_CALLER;
50
break
;
51
case
_IID_IPersistStorage:
52
case
_IID_IPersistStream:
53
case
_IID_IPersistPropertyBag:
54
Rslt
=
S_OK;
55
pdwEnabledOptions
=
0
;
56
if
(_fSafeForInitializing
==
true
)
57
pdwEnabledOptions
=
INTERFACESAFE_FOR_UNTRUSTED_DATA;
58
break
;
59
default
:
60
Rslt
=
E_NOINTERFACE;
61
break
;
62
}
63
64
return
Rslt;
65
}
66
67
public
int
SetInterfaceSafetyOptions(
ref
Guid riid,
int
dwOptionSetMask,
int
dwEnabledOptions)
68
{
69
int
Rslt
=
E_FAIL;
70
string
strGUID
=
riid.ToString(
"
B
"
);
71
switch
(strGUID)
72
{
73
case
_IID_IDispatch:
74
case
_IID_IDispatchEx:
75
if
(((dwEnabledOptions
&
dwOptionSetMask)
==
INTERFACESAFE_FOR_UNTRUSTED_CALLER)
&&
(_fSafeForScripting
==
true
))
76
Rslt
=
S_OK;
77
break
;
78
case
_IID_IPersistStorage:
79
case
_IID_IPersistStream:
80
case
_IID_IPersistPropertyBag:
81
if
(((dwEnabledOptions
&
dwOptionSetMask)
==
INTERFACESAFE_FOR_UNTRUSTED_DATA)
&&
(_fSafeForInitializing
==
true
))
82
Rslt
=
S_OK;
83
break
;
84
default
:
85
Rslt
=
E_NOINTERFACE;
86
break
;
87
}
88
89
return
Rslt;
90
}
91
92
#endregion
93
94
private
void
MyActiveX_Load(
object
sender, EventArgs e)
95
{
96
97
}
98
99
100
public
void
Start(
object
obj)
101
{
102
for
(
int
i
=
0
; i
<
10
; i
++
)
103
{
104
Thread t
=
new
Thread(
new
ParameterizedThreadStart(ShowTime));
105
t.Start(obj.ToString()
+
"
,线程:
"
+
i.ToString());
106
}
107
}
108
109
private
void
button1_Click(
object
sender, EventArgs e)
110
{
111
Start(
"
Hello World
"
);
112
}
113
114
void
ShowTime(
object
obj)
115
{
116
if
(
this
.listBox1.InvokeRequired)
117
{
118
D d
=
new
D(DelegateShowTime);
119
listBox1.Invoke(d, obj);
120
}
121
else
122
{
123
this
.listBox1.Items.Add(obj);
124
}
125
126
127
}
128
129
130
void
DelegateShowTime(
object
obj)
131
{
132
this
.listBox1.Items.Add(obj);
133
}
134
135
136
}
137
}
#region IObjectSafety 成员 ... #endregion这一段的内容是固定的,不要修改,其它内容根据自己的业务要求自行修改,另外类前面要加上Guid的标识,以便网页调用时,能用CLSID="xxx"来调用
基本上这样弄完后,就可以在网页中,用类似下面这样的代码来本机调用了:
注意:c#定义的public方法,如果想直接让js调用,只能返回string,DateTime,int,double这一类基本值类型,其它返回类型比如array,object,在js中要么直接报错,要么得到null
代码
1
<
object
id
="x"
classid
="clsid:8d7d8518-ca58-4863-b94d-3c616fda7b35"
></
object
>
2
<
hr
/>
3
<
input
type
="button"
value
="调用ActiveX中的多线程方法"
onclick
="fnTest()"
/>
4
<
script
type
="text/javascript"
>
5
var
fnTest
=
function
(){
6
var
x
=
document.getElementById(
"
x
"
);
7
x.Start(
"
这是js中的参数
"
);
8
}
9
</
script
>
4.安装部署
前面已经提到了,c#开发的(伪)"ActiveX"控件并非纯正的ocx,所以只能用RegAsm.Exe xxx.dll来进行程序集的注册,这里要注意一点:在开发机上,项目编译后vs.net会自动将bin\debug\xxx.dll调用regasm注 册,但在别人机器上就不行了,为了能在调试时模拟其它机器的运行结果,可以在编译后,手动用类似 regAsm.exe D:\MyDoc\ActiveXDemo\output\ActiveXDemo.dll /u 来反注册(在vs.net命令行模式下)
当然,如果您不勾选3.(2)中所说的“为com互操作注 册”,vs编译时便不会自动注册,但是这样调试起来不太方便,另外注册/反注册时的RegAsm.exe要起开发环境中的版本一致(比如你开发时设置是 64位版本,那么反注册也要用64位版本的RegAsm.exe)
另外,我们也不可能在每个客户机上手动用RegAsm.exe来帮客户注册,所以我们还得新建安装项目来做一个安装包,这个比较简单,直接新建一个"其他项目类型-->安装和部署-->安装项目"即可
然后在安装项目上,右键"添加"-->"项目输出"-->"主输出"-->在项目下拉框中选择activex所对应的项目即可.
注意:"主输出来自xxx"的属性栏中,有一个"Register"必须选择"vsdrpCOM"
另外还有一个问题,可能是我机器的个别现象,每次activex项目有修改时,建议最好手动清除安装项目debug目录下的文件,再重新生成安装项目,否则有时候会发现activex修改了,但是安装包中包含的dll还是未修改过的版本。
后话:c#开发的东西是运行于.net 框架之上的,就好比java开发的东西必须要java runtime才能运行一样,利用本文方法开发出来的dll也必须要安装.net框架才能跑起来,幸好最新的win7中已经集成了.net框架,当然您如 果对于庞大的.net框架安装程序很敏感,仍然觉得纯正的ocx更好的话,建议还是用vb/delphi/c++这一类老牌的开发工具/语言实现。(可以 参考我的另一篇重温delphi之:如何快速开发原生ActiveX控件)
示例源代码下载:http://files.cnblogs.com/yjmyzz/ActiveXDemo.rar