您的位置:首页 > 编程语言 > C#

C#开发ActiveX插件

2015-02-09 22:56 148 查看
刚毕业来到公司主管安排个任务 就是封装一个第三方OCX包,开发个网页插件提供内部工作人员使用。经过了解OCX就是ActiveX插件,但试过网上的方法用都报错“对象没有此属性或方法”(已regsvr32注册),查看过方法发现没有构造函数,不知道是不是因为用VB编写的原因。无奈之下想到封装为WinForm下的dll再封装为ActiveX,最后委屈求全实现了需要的功能,现在ActiveX基本是被抛弃了吧?网上能找到的教程都是N年前的,而且关于C#都特别少,所以借鉴了很多博文,特此记录下来。

好像话太多了,直接上步骤:

运行环境:Windows8.1 + VS2013

1、用 AxImp.exe 将OCX转换为WinForm可承载的控件

aximp c:\systemroot\system32***.ocx /source

aximp 在路径 C:\Program Files (x86)\Microsoft SDKs\Windows\v8.1A\bin\NETFX 4.5.1 Tools

/source 生成 .cs文件

之后在目录下生成 .dll 、Ax.dll 、 *.cs三个文件。

2、用于测试,注册OCX包:

regsvr32 c:\windows\SysWOW64***.ocx

虽然主要是引用第一步生成的dll,但必须注册OCX包。

3、将ActiveX添加到WinForm可承载窗体,也就是继承UserControl的类。且必须BeginInit、EndInit:

UserControl ctl = new UserControl();
AxyourActiveX activeX = new AxyourActiveX();
activeX.BeginInit();
ctl.Controls.Add(cti);
activeX.EndInit();


这里有个小插曲,我原本这样写

public class CustomActiveX : UserControl{
public CustomActiveX()
{
AxyourActiveX activeX = new AxyourActiveX();
activeX.BeginInit();
this.Controls.Add(cti);
activeX.EndInit();
}
}


理论上好像没问题,但跑起来报错(COMException),可能是当前的类运行在web上面的时候本身已经变成COM组件,所以出错,一定要注意。

4、实现IObjectSafety,声明为安全ActiveX,声明后IE会提示用户是否运行,而不是拦截。

接口:
[ComImport, Guid("CB5BDC81-93C1-11CF-8F20-00805F2CD064")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IObjectSafety
{
[PreserveSig]
int GetInterfaceSafetyOptions(ref Guid riid, [MarshalAs(UnmanagedType.U4)] ref int pdwSupportedOptions, [MarshalAs(UnmanagedType.U4)] ref int pdwEnabledOptions);

[PreserveSig()]
int SetInterfaceSafetyOptions(ref Guid riid, [MarshalAs(UnmanagedType.U4)] int dwOptionSetMask, [MarshalAs(UnmanagedType.U4)] int dwEnabledOptions);
}


实现:
private const string _IID_IDispatch = "{00020400-0000-0000-C000-000000000046}";
private const string _IID_IDispatchEx = "{a6ef9860-c720-11d0-9337-00a0c90dcaa9}";
private const string _IID_IPersistStorage = "{0000010A-0000-0000-C000-000000000046}";
private const string _IID_IPersistStream = "{00000109-0000-0000-C000-000000000046}";
private const string _IID_IPersistPropertyBag = "{37D84F60-42CB-11CE-8135-00AA004BB851}";

private const int INTERFACESAFE_FOR_UNTRUSTED_CALLER = 0x00000001;
private const int INTERFACESAFE_FOR_UNTRUSTED_DATA = 0x00000002;
private const int S_OK = 0;
private const int E_FAIL = unchecked((int)0x80004005);
private const int E_NOINTERFACE = unchecked((int)0x80004002);

private bool _fSafeForScripting = true;
private bool _fSafeForInitializing = true;

public int GetInterfaceSafetyOptions(ref Guid riid, ref int pdwSupportedOptions, ref int pdwEnabledOptions)
{
int Rslt = E_FAIL;

string strGUID = riid.ToString("B");
pdwSupportedOptions = INTERFACESAFE_FOR_UNTRUSTED_CALLER | INTERFACESAFE_FOR_UNTRUSTED_DATA;
switch (strGUID)
{
case _IID_IDispatch:
case _IID_IDispatchEx:
Rslt = S_OK;
pdwEnabledOptions = 0;
if (_fSafeForScripting == true)
pdwEnabledOptions = INTERFACESAFE_FOR_UNTRUSTED_CALLER;
break;
case _IID_IPersistStorage:
case _IID_IPersistStream:
case _IID_IPersistPropertyBag:
Rslt = S_OK;
pdwEnabledOptions = 0;
if (_fSafeForInitializing == true)
pdwEnabledOptions = INTERFACESAFE_FOR_UNTRUSTED_DATA;
break;
default:
Rslt = E_NOINTERFACE;
break;
}

return Rslt;

}

public int SetInterfaceSafetyOptions(ref Guid riid, int dwOptionSetMask, int dwEnabledOptions)
{
int Rslt = E_FAIL;
string strGUID = riid.ToString("B");
switch (strGUID)
{
case _IID_IDispatch:
case _IID_IDispatchEx:
if (((dwEnabledOptions & dwOptionSetMask) == INTERFACESAFE_FOR_UNTRUSTED_CALLER) &&
(_fSafeForScripting == true))
Rslt = S_OK;
break;
case _IID_IPersistStorage:
case _IID_IPersistStream:
case _IID_IPersistPropertyBag:
if (((dwEnabledOptions & dwOptionSetMask) == INTERFACESAFE_FOR_UNTRUSTED_DATA) &&
(_fSafeForInitializing == true))
Rslt = S_OK;
break;
default:
Rslt = E_NOINTERFACE;
break;
}

return Rslt;

}


一字不漏贴上就ok,具体作用暂不探究。

5、实现注册时注册表的操作:

[ComRegisterFunction()]
public static void RegisterClass(string key)
{
// Strip off HKEY_CLASSES_ROOT\ from the passed key as I don't need it
StringBuilder sb = new StringBuilder(key);
sb.Replace(@"HKEY_CLASSES_ROOT\", "");

// Open the CLSID\{guid} key for write access
RegistryKey k = Registry.ClassesRoot.OpenSubKey(sb.ToString(), true);

// And create the 'Control' key - this allows it to show up in
// the ActiveX control container
RegistryKey ctrl = k.CreateSubKey("Control");
ctrl.Close();

// Next create the CodeBase entry - needed if not string named and GACced.
RegistryKey inprocServer32 = k.OpenSubKey("InprocServer32", true);
inprocServer32.SetValue("CodeBase", Assembly.GetExecutingAssembly().CodeBase);
inprocServer32.Close();

// Finally close the main key
k.Close();

}

[ComUnregisterFunction()]
public static void UnregisterClass(string key)
{
StringBuilder sb = new StringBuilder(key);
sb.Replace(@"HKEY_CLASSES_ROOT\", "");

// Open HKCR\CLSID\{guid} for write access
RegistryKey k = Registry.ClassesRoot.OpenSubKey(sb.ToString(), true);

// Delete the 'Control' key, but don't throw an exception if it does not exist
k.DeleteSubKey("Control", false);

// Next open up InprocServer32
RegistryKey inprocServer32 = k.OpenSubKey("InprocServer32", true);

// And delete the CodeBase key, again not throwing if missing
k.DeleteSubKey("CodeBase", false);

// Finally close the main key
k.Close();
}


6、这样就差不多了,业务需要 ActiveX回调数据到JavaScript上,百度activex调用js得到的方法:

//获取html的window对象
Type typeIOleObject = this.GetType().GetInterface("IOleObject", true);
object oleClientSite = typeIOleObject.InvokeMember("GetClientSite",BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.Public, null, this, null);
IOleClientSite oleClientSite2 = oleClientSite as IOleClientSite;
IOleContainer pObj;
oleClientSite2.GetContainer(out pObj);

IHTMLDocument pDoc2 = (IHTMLDocument)pObj;
HTMLWindow2Class htmlWin = (HTMLWindow2Class)pDoc2.Script;
string jsCode = string.Format("{0}({1})", func, param);
this._htmlWindows.execScript(jsCode, "JScript");


这里的IOleClientSite、IOleContainer需要自己生成接口:

[ComImport,
Guid("0000011B-0000-0000-C000-000000000046"),
InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
public interface IOleContainer
{
void EnumObjects([In, MarshalAs(UnmanagedType.U4)] int grfFlags,
[Out, MarshalAs(UnmanagedType.LPArray)] object[] ppenum);
void ParseDisplayName([In, MarshalAs(UnmanagedType.Interface)] object pbc,
[In, MarshalAs(UnmanagedType.BStr)] string pszDisplayName,
[Out, MarshalAs(UnmanagedType.LPArray)] int[] pchEaten,
[Out, MarshalAs(UnmanagedType.LPArray)] object[] ppmkOut);
void LockContainer([In, MarshalAs(UnmanagedType.I4)] int fLock);
}

[ComImport,
Guid("00000118-0000-0000-C000-000000000046"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IOleClientSite
{
void SaveObject();
void GetMoniker(uint dwAssign, uint dwWhichMoniker, object ppmk);
void GetContainer(out IOleContainer ppContainer);
void ShowObject();
void OnShowWindow(bool fShow);
void RequestNewObjectLayout();
}


但奇怪的事情来了,我的ActiveX并没有继承和实现这两个接口,但上面的

Type typeIOleObject = this.GetType().GetInterface(“IOleObject”, true);

并没有出错,而是获取到对象了。

7、到最后设置当前项目属性

1、 -> 应用程序 -> 程序集信息 -> 勾上 使程序集COM可见。

2、 -> 生成 -> 勾上 为COM互操作注册 。

测试成果:

方法一、手动注册我们的activex:

regasm c:/windows/SysWOW64/CustomActiveX.dll

路径: C:\Windows\Microsoft.NET\Framework64\v4.0.30319\regasm.exe

注意:c#编的ActiveX一定要用regasm,不是regsvr32。

方法二:制作安装包

使用InstallShield Limited Edition for Visual Studio 2013,参考http://www.cnblogs.com/flydoos/p/3430922.html

主要部分:

Installation Requirements - 设置我们需要的 .Net框架。

Application Files - 添加主输出、将项目涉及的程序集添加进来(包括ocx)、将COM组件设置属性为“Extract COM

information”(网上的教程都说是”Self-Register”,但我安装时会出问题。)

2.Specify Application Data > Redistributables - 将.Net框架打包到安装文件

6.Prepare for Release > Release CD_ROM-Setup.exe-InstallShield Prerquisites Location设为Extract From Setup.exe 、SingleImage也一样。(这里的CD_ROM会生成.msi和exe文件,打包为cab需要用,SingleImage只会生成一个setup.exe)

调用:

<object id="obj" classid="clsid:F590031C-04A1-4100-921D-728340D7A21D" width="550" height="450"></object>
clsid 为我们ActiveX的GUID,可以用VS-工具-创建GUID,快速生成。


cab打包方法:

添加install.inf

[version]
signature="$CHICAGO$"
AdvancedINF=2.0
[Setup Hooks]
hook1=hook1
[hook1]
run=msiexec.exe /i "%EXTRACT_DIR%\ActiveXSetup.msi" /qn


build.bat

"cabarc.exe"  n test.cab ActiveXSetup.msi install.inf


因为打包是用.msi文件,而我需要将.net框架打包到客户端,所以就直接使用setup.exe了,大概68M左右。到此已可以实现我的需求,如果想签名发布,可以google一下也很多例子。

我本来不是做web开发的,只是懂一点点c# 临时分配了任务,所以总结得不太好和很多知识点都没有深究,但希望能帮助大家。第一篇blog,以后继续努力。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c# ActiveX