Создать внепроцессный COM в C # /. Net? - PullRequest
25 голосов
/ 15 января 2009

Мне нужно создать внепроцессный COM-сервер (.exe) в C #, к которому будут обращаться несколько других процессов в одном окне. Компонент должен быть единым процессом, поскольку он будет кэшировать информацию, которую он предоставляет своим потребителям, в памяти.

Примечание: процессы, которые будут обращаться к моему COM-серверу, в основном являются процессами Matlab, поэтому необходимость для интерфейса COM.

Я видел потоки, касающиеся создания внутрипроцессных COM-компонентов в .Net при переполнении стека ( Create COM ... ) и в Интернете, но мне сложно найти способ создания компоненты вне процесса с .Net.

Как это достижимо? Любые предлагаемые ссылки?

Спасибо.

Ответы [ 9 ]

12 голосов
/ 15 января 2009

У нас также были некоторые проблемы много лет назад с 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, создает параметры реестра для COM-объекта локального EXE-сервера, и его можно сравнить с regasm, если вы используете REGEDIT и найдете соответствующие ключи.

Я прокомментировал три вызова метода \\Registry.ClassesRoot.CreateSubKey, поскольку это было еще одной причиной, по которой нам нужно было зарегистрировать тип самостоятельно, поскольку это был OPC-сервер, и сторонние OPC-клиенты используют эти реализованные категории для поиска совместимых OPC-серверов. REGASM не добавит их для нас, если мы сами не выполним эту работу.

Вы можете легко увидеть, как это работает, если поставить точки останова на функции при запуске.

В нашей реализации использовался интерфейс, который уже был зарегистрирован в COM. Для подачи заявления вам необходимо: -

  1. Расширьте методы регистрации, перечисленные выше, чтобы зарегистрировать интерфейс с помощью COM
  2. Или создайте отдельную DLL с определением интерфейса, а затем экспортируйте это определение интерфейса в библиотеку типов и зарегистрируйте его, как описано в ссылке StackOverflow, которую вы добавили в вопросе.
11 голосов
/ 09 января 2013
5 голосов
/ 21 октября 2017

ActiveX.NET , представляет собой настоящую реализацию внепроцессного (EXE) COM-сервера в C # .NET. У этого есть более чистая реализация по сравнению с оригинальным CSExeCOMServer , опубликованным в Code.MSDN.

ActiveX.NET имеет такие функции, как использование насоса сообщений .NET (вместо собственного) и использование модели плагинов MEF, так что сервер EXE развязан и может использоваться несколькими подключаемыми модулями COM, которые могут быть разработаны независимо

5 голосов
/ 15 января 2009

Один из вариантов - обслуживаемые компоненты - т.е. разместить его в COM + в качестве исполняемой оболочки. См. Также здесь .

5 голосов
/ 15 января 2009

IMO, один из способов, с помощью которого это можно сделать, - создать обычный COM-Dll в соответствии с методом, который вы упомянули в ссылке, а затем, после регистрации вашего COM-DLL, заменить его на суррогатную DLL. Это можно сделать очень легко с помощью утилиты OLEView, хотя вы можете сделать это и вручную, изменив записи реестра, а также с помощью метода, упомянутого в http://msdn.microsoft.com/en-us/library/ms686606(VS.85).aspx.

Делая эту суррогатную DLL, она будет работать в своем собственном dllhost.exe и, следовательно, будет вне процесса.

3 голосов
/ 16 ноября 2009

Вы можете использовать RegistrationServices.RegisterTypeForComClients, который является управляемым эквивалентом CoRegisterClassObject - пример кода см. http://www.andymcm.com/blog/2009/10/managed-dcom-server.html

2 голосов
/ 21 января 2017

Визуальный студийный проект "CSExeCOMServer", который вы можете найти здесь (All-in-One), дает полный пример.

2 голосов
/ 27 мая 2010

Это можно сделать, используя неуправляемую инфраструктуру 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.

1 голос
/ 18 марта 2011

Используйте флаг REGCLS_MULTIPLEUSE с CoRegisterClassObject (), чтобы сделать coclass мультииспользуемым классом. Вот больше информации: http://support.microsoft.com/kb/169321.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...