在C#/ .Net中创建进程外COM?

时间:2009-01-15 11:25:25

标签: c# com

我需要在C#中创建一个进程外COM服务器(.exe),它将被同一个盒子上的多个其他进程访问。该组件必须是一个进程,因为它会将它提供的信息缓存到内存中的消费者。

注意:访问我的COM服务器的进程主要是Matlab进程,因此COM接口的必需

我已经看到了关于在堆栈溢出(Create COM ...)和Web上创建.Net中的进程内COM组件的线程,但我很难找到创建进程外组件的方法与.Net。

这是如何实现的?有建议的参考文献吗?

感谢。

9 个答案:

答案 0 :(得分:12)

多年前我们也遇到了一些问题,包括regasm并将COM类作为本地EXE服务器运行。

这是一个黑客,我欢迎任何建议,使其更优雅。它是在.NET 1.0天内为一个项目实现的,从那时起就没有被触及过!

基本上,每次应用程序启动时它都会执行一种regasm注册方式(在COM容器应用程序中实例化COM对象之前,需要运行一次以生成注册表项。)

我从我们的实现中复制了以下重要部分,并重命名了几个类来说明这个例子。

从Form Loaded事件调用以下方法来注册COM类(在此示例中重命名为MyCOMClass

private void InitialiseCOM()
    {
        System.Runtime.InteropServices.RegistrationServices services = new System.Runtime.InteropServices.RegistrationServices();
        try
        {
            System.Reflection.Assembly ass = Assembly.GetExecutingAssembly();
            services.RegisterAssembly(ass, System.Runtime.InteropServices.AssemblyRegistrationFlags.SetCodeBase);
            Type t = typeof(MyCOMClass);
            try
            {
                Registry.ClassesRoot.DeleteSubKeyTree("CLSID\\{" + t.GUID.ToString() + "}\\InprocServer32");
            }
            catch(Exception E)
            {
                Log.WriteLine(E.Message);
            }

            System.Guid GUID = t.GUID;
            services.RegisterTypeForComClients(t, ref GUID );
        }
        catch ( Exception e )
        {
            throw new Exception( "Failed to initialise COM Server", e );
        }
    }

对于有问题的类型MyCOMObject,需要一些特殊属性才能兼容COM。一个重要的属性是指定一个固定的GUID,否则每次编译注册表都会填满孤立的COM GUID。您可以使用VisualStudio中的“工具”菜单为您创建唯一的GUID。

  [GuidAttribute("D26278EA-A7D0-4580-A48F-353D1E455E50"),
  ProgIdAttribute("My PROGID"),
  ComVisible(true),
  Serializable]
  public class MyCOMClass : IAlreadyRegisteredCOMInterface
  {
    public void MyMethod()
    {
    }

    [ComRegisterFunction]
    public static void RegisterFunction(Type t)
    {
      AttributeCollection attributes = TypeDescriptor.GetAttributes(t);
      ProgIdAttribute ProgIdAttr = attributes[typeof(ProgIdAttribute)] as ProgIdAttribute;

      string ProgId = ProgIdAttr != null ? ProgIdAttr.Value : t.FullName;

      GuidAttribute GUIDAttr = attributes[typeof(GuidAttribute)] as GuidAttribute;
      string GUID = "{" + GUIDAttr.Value + "}";

      RegistryKey localServer32 = Registry.ClassesRoot.CreateSubKey(String.Format("CLSID\\{0}\\LocalServer32", GUID));
      localServer32.SetValue(null, t.Module.FullyQualifiedName);

      RegistryKey CLSIDProgID = Registry.ClassesRoot.CreateSubKey(String.Format("CLSID\\{0}\\ProgId", GUID));
      CLSIDProgID.SetValue(null, ProgId);

      RegistryKey ProgIDCLSID = Registry.ClassesRoot.CreateSubKey(String.Format("CLSID\\{0}", ProgId));
      ProgIDCLSID.SetValue(null, GUID);

      //Registry.ClassesRoot.CreateSubKey(String.Format("CLSID\\{0}\\Implemented Categories\\{{63D5F432-CFE4-11D1-B2C8-0060083BA1FB}}", GUID));
      //Registry.ClassesRoot.CreateSubKey(String.Format("CLSID\\{0}\\Implemented Categories\\{{63D5F430-CFE4-11d1-B2C8-0060083BA1FB}}", GUID));
      //Registry.ClassesRoot.CreateSubKey(String.Format("CLSID\\{0}\\Implemented Categories\\{{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}}", GUID));
    }

    [ComUnregisterFunction]
    public static void UnregisterFunction(Type t)
    {
      AttributeCollection attributes = TypeDescriptor.GetAttributes(t);
      ProgIdAttribute ProgIdAttr = attributes[typeof(ProgIdAttribute)] as ProgIdAttribute;

      string ProgId = ProgIdAttr != null ? ProgIdAttr.Value : t.FullName;

      Registry.ClassesRoot.DeleteSubKeyTree("CLSID\\{" + t.GUID + "}");
      Registry.ClassesRoot.DeleteSubKeyTree("CLSID\\" + ProgId);
    }

  }

主窗体中的InitialiseCOM方法使用RegistrationServices来注册类型。然后,框架使用反射来查找标有ComRegisterFunction属性的方法,并使用要注册的类型调用该函数。

ComRegisterFunction标记方法,hand为本地EXE服务器COM对象创建注册表设置,如果您使用REGEDIT并查找相关密钥,则可以将此与regasm进行比较。

我已经注释掉了三个\\Registry.ClassesRoot.CreateSubKey方法调用,因为这是我们自己注册类型的另一个原因,因为这是OPC服务器,第三方OPC客户端使用这些实现的类别来扫描兼容的OPC服务器。除非我们自己完成工作,否则REGASM不会为我们添加这些内容。

如果你在函数开始时给它们设置断点,你可以很容易地看到它是如何工作的。

我们的实现使用了已在COM注册的接口。对于您的应用,您需要: -

  1. 扩展上面列出的注册方法以使用COM
  2. 注册接口
  3. 或者使用接口定义创建一个单独的DLL,然后将该接口定义导出到类型库并按照您在问题中添加的StackOverflow链接中的说明进行注册。

答案 1 :(得分:11)

答案 2 :(得分:5)

IMO,其中一种方法是按照你在链接中提到的方法创建一个普通的COM Dll,然后在注册你的COM dll之后,将它改为代理DLL。这可以通过OLEView实用程序轻松完成,但您也可以通过更改注册表项以及http://msdn.microsoft.com/en-us/library/ms686606(VS.85).aspx中提到的方法手动完成。

通过使它成为代理DLL,它将在它自己的dllhost.exe中运行,因此将处于进程外。

答案 3 :(得分:5)

一个选项是serviced components - 即将它作为shell exe托管在COM +中。另请参阅howto here

答案 4 :(得分:5)

ActiveX.NET,是C#.NET中真正的Out-Of-Proc(EXE)COM服务器实现。与在Code.MSDN中发布的原始CSExeCOMServer相比,这个实现更清晰。

ActiveX.NET 具有使用.NET消息泵(而不是本机)并使用MEF插件模型的功能,因此EXE服务器可以解耦并可以在多个COM插件之间共享,可以独立开发

答案 5 :(得分:3)

您可以使用RegistrationServices.RegisterTypeForComClients,它是CoRegisterClassObject的托管等效项 - 示例代码见http://www.andymcm.com/blog/2009/10/managed-dcom-server.html

答案 6 :(得分:2)

可以使用非托管ATL框架并使用托管代码进行管理 (通过将结果项目属性更改为/ clr来简单)。

以下是说明性摘要:

.H-part:

\#include < vcclr.h >

\#using < MyCSharpModule.dll >

using namespace System;

class ATL_NO_VTABLE MyCSharpProxyServer :
    public CComObjectRootEx< CComMultiThreadModel >,

.....

{

      HRESULT FinalConstruct();

      STDMETHODIMP LoadMyFile(BSTR FileName);
.....
      gcroot<MyCSNamespace::MyCSharpClass^> m_CSClass;

}

.CPP-part:

using namespace System::Collections;

using namespace MyCSNamespace;

HRESULT MyCSharpProxyServer::FinalConstruct()
{

    m_CSClass = gcnew MyCSharpClass();

}

STDMETHODIMP MyCSharpProxyServer::LoadMyFile(BSTR FileName)
{

    try {
       int hr = m_CSClass->LoadFile(gcnew String( FileName));
        return hr;
    }
    catch( Exception^ e )  {
        return E_FAIL;
    }
}

C#部分(MyCSharpClass类)位于一个单独的项目中,输出类型为Class Library。

答案 7 :(得分:2)

视觉工作室项目&#34; CSExeCOMServer&#34;你可以找到here(All-in-One),给出一个完整的例子。

答案 8 :(得分:1)

将标志REGCLS_MULTIPLEUSE与CoRegisterClassObject()一起使用,使coclass成为一个多用途类。以下是更多信息:http://support.microsoft.com/kb/169321