如何用C#创建一个ActiveX控件?

7

我无法在C#中创建一个运行的ActiveX控件;我尝试按照教程进行了多次,但都没有成功。

我创建了一个示例Class Library项目,其中包含以下代码:

namespace AACWCSurvey
{
    [ProgId("Prisoner.PrisonerControl")]
    [ClassInterface(ClassInterfaceType.AutoDual)]
    public class Class1
    {
        public Class1()
        {
            MessageBox.Show("FIRETRUCK!!!");
        }
    }
}

我接着执行了以下步骤:
  1. 属性 => 应用程序 => 程序集信息 => 使程序集可见
  2. 生成 => 注册 COM 互操作 TRUE(选中)
  3. 为程序集创建强名称(签名)
  4. 构建项目
  5. regasm MyDll.dll /tlb /codebase

  6. 在 tstcon32 中看不到 Prisoner.PrisonerControl =(

我的操作系统是 WinXP x86。


更新: 它可以从 VBScript 中工作:

Dim objJava
Set objJava = WScript.CreateObject("Prisoner.PrisonerControl")

但是在tstcon32中不可见。


打开注册表编辑器,查找HKEY_CLASSES_ROOT下包含您的progid“Prisoner.PrisonerControl”的文件夹。如果有,请获取该键下面的CLSID值。现在在HKEY_CLASSES_ROOT / CLSID下查找该CLSID。假设找到了,然后在InprocServer32下查找,其中应该包含您程序集的路径、版本、ID等信息。如果所有这些看起来都很好,那么程序集已成功注册为com对象。 - user957902
3个回答

3
你已经创建了一个COM服务器,但没有创建一个ActiveX控件,这是一种更复杂的COM对象,你可以使用tstcon32.exe来测试。它必须实现一堆接口,其中关键的是IOleObject和IOleWindow。这些接口允许它与ActiveX主机进行所需的协商并创建可见窗口。Winforms Control类是创建一个ActiveX控件的最佳选择。

2
如果您阅读了使用Prisoner.PrisonerControl控件的实际文章,则会在具有控件GUID的键内创建名为Control的子键。
在我的机器上,GUID为{9DEA5F06-E324-31A7-837B-D0F3BDE91423},创建该键。
HKEY_CLASSES_ROOT\CLSID\{9DEA5F06-E324-31A7-837B-D0F3BDE91423}\Control

将控件显示在 tstcon32 中。即使没有它,ActiveX 也可用于 JavaScript。

var x = new ActiveXControl("Prisoner.PrisonerControl");

实际上,我不得不在我的x64机器上对JavaScript执行和注册表路径进行测试,这是因为我需要与Windows作斗争,但这是另一个故事了。


谢谢!好的,所以我需要手动创建它,这不太好 :) 即使在Java中也更容易 :) - VextoR

1
以下是相关步骤在外部文档中记录。这里进行了总结,省略了一些论述但没有任何必要的步骤。
这个例子也非常类似于Garry Trinder在2008年11月25日发表的文章“使用托管控件作为ActiveX控件”,我也包含了一些来自这篇文章的笔记。
引用块: 将Windows Forms控件公开为ActiveX控件 本文将描述如何在.NET之外利用Windows Forms控件。 编写控件 从Visual Studio中创建一个新的控件项目-我的示例都是用C#编写的,但VB.NET也可以使用。
在这篇文章中,Garry建议:“首先,创建一个托管的用户控件项目 - 可以是Windows窗体类库或控件库项目。使用用户控件设计器按照您的意愿设计自定义用户控件(使用任何您喜欢的标准控件)。"
  1. Add controls etc to the form, put in the code etc.

  2. Add in the following using clauses...

using System.Runtime.InteropServices;
using System.Text;
using System.Reflection;
using Microsoft.Win32;
  1. Attribute your class so that it gets a ProgID. This isn't strictly necessary as one will be generated, but it's almost always best to be explicit.

[ProgId("Prisoner.PrisonerControl")]
[ClassInterface(ClassInterfaceType.AutoDual)]

This assigns the ProgID, and also defines that the interface exposed should be 'AutoDual' - this crufts up a default interface for you from all public, non-static members of the class. If this isn't what you want, use one of the other options.

  1. Update the project properties so that your assembly is registered for COM interop.

If you're using VB.NET, you also need a strong named assembly. Curiously in C# you don't - and it seems to be a feature of the environment rather than a feature of the compiler or CLR.

  1. Add the following two methods into your class.

[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 ( ) ;
}

The RegisterClass function is attributed with ComRegisterFunction - this static method will be called when the assembly is registered for COM Interop. All I do here is add the 'Control' keyword to the registry, plus add in the CodeBase entry.

CodeBase is interesting - not only for .NET controls. It defines a URL path to where the code can be found, which could be an assembly on disk as in this instance, or a remote assembly on a web server somewhere. When the runtime attempts to create the control, it will probe this URL and download the control as necessary. This is very useful when testing .NET components, as the usual caveat of residing in the same directory (etc) as the .EXE does not apply.

[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 ( ) ;
}

The second function will remove the registry entries added when (if) the class is unregistered - it's always a good suggestion to tidy up as you go.

Now you are ready to compile & test your control.

Garry的博客中有一些额外的注释:

可以使用.REG脚本创建附加的注册表项:ControlMiscStatusTypeLibVersion,但通常最好编写在注册/注销时调用的函数。

他详细描述了注册表键:

Control 是一个空的子键。 TypeLib 映射到 TypeLib 的 GUID(这是程序集级别的 GUID,在 assemblyinfo.cs 中)。 Version 是来自程序集版本的主要和次要版本号。唯一有点有趣的子键是 MiscStatus。这需要设置为由 OLEMISC 枚举中的(按位)值组成的值,在此处记录。要使此枚举可用,请添加对 Microsoft.VisualStudio.OLE.Interop 的引用(以及适当的命名空间“using”语句)。

他最后的注意事项是一个警告:

注意:这似乎对于Excel(经过非常有限的测试)可以正常工作,部分适用于PowerPoint,但在Word中失败了。可能,一些更多的OLEMISC值可以改善这种情况;可能有一些消息需要挂钩;可能还需要实现更多接口…我只是勉强让它以非常有限的方式工作,这说明这可能不是你想要在任何严肃的场合使用的技术。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接