Ответ 1
Один из вариантов - обслуживаемые компоненты - то есть разместить его в COM + в качестве оболочки exe. См. Также здесь.
Мне нужно создать COM-сервер вне процесса (.exe) в С#, к которому будут доступны несколько других процессов в одном и том же поле. Компонент должен быть одним процессом, поскольку он кэширует информацию, которую он предоставляет своим потребителям в памяти.
Примечание. Процессы, которые будут обращаться к моему серверу COM, - это в основном процессы Matlab, поэтому необходимость для COM-интерфейса.
Я видел потоки относительно создания COM-компонентов in-process в .Net при переполнении стека (Создать COM...) и в Интернете, но у меня есть трудно найти способ создания внепроцессных компонентов с помощью .Net.
Как это достижимо? Любые предлагаемые ссылки?
Спасибо.
Один из вариантов - обслуживаемые компоненты - то есть разместить его в COM + в качестве оболочки exe. См. Также здесь.
У нас также были некоторые проблемы много лет назад с 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-идентификаторами COM. Вы можете использовать меню "Инструменты" в 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-объекта Local EXE Server, и это можно сравнить с regasm, если вы используете REGEDIT
и находите соответствующие ключи.
Я прокомментировал три метода вызова \\Registry.ClassesRoot.CreateSubKey
, поскольку это была еще одна причина, по которой нам нужно было регистрировать тип самостоятельно, поскольку это был OPC-сервер, а сторонние OPC-клиенты использовали эти реализованные категории для сканирования совместимых OPC-серверов. REGASM не добавит их нам, если мы сами не сделаем эту работу.
Вы можете легко увидеть, как это работает, если вы положите точки останова на функции при запуске.
В нашей реализации использовался интерфейс, который уже был зарегистрирован в COM. Для вашего приложения вам потребуется либо: -
Официальный ответ в статье KB Как разработать COM-компонент вне процесса с помощью Visual С++, Visual С# или Visual Basic.NET.
IMO, один из способов, которым это можно сделать, - создать обычную COM-Dll в соответствии с методом, указанным в ссылке, а затем после регистрации вашей DLL-библиотеки заменить его на суррогатную DLL. Это можно сделать очень легко с помощью утилиты OLEView, хотя вы также можете сделать это вручную, изменив записи реестра, а также метод, упомянутый в http://msdn.microsoft.com/en-us/library/ms686606(VS.85).aspx.
Сделав это суррогатной DLL, она запустит в нее собственный dllhost.exe и, следовательно, будет вне процесса.
Это можно сделать, используя устаревшую структуру 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;
}
}
Часть С# (класс MyCSharpClass) живет в отдельном проекте с типом Class Class типа вывода.
Вы можете использовать RegistrationServices.RegisterTypeForComClients, который является управляемым эквивалентом CoRegisterClassObject - для примера кода см. http://www.andymcm.com/blog/2009/10/managed-dcom-server.html
Используйте флаг REGCLS_MULTIPLEUSE с CoRegisterClassObject(), чтобы сделать классный класс multiuse. Вот еще информация: http://support.microsoft.com/kb/169321.
Проект визуальной студии "CSExeCOMServer", который вы можете найти здесь ( "Все-в-одном" ), дает полный пример.
ActiveX.NET - это настоящая реализация COM-сервера Out-Of-Proc (EXE) в С#.СЕТЬ. У этого есть более чистая реализация по сравнению с оригинальным CSExeCOMServer, опубликованным в Code.MSDN.
ActiveX.NET имеет такие функции, как он использует .NET Message Pump (вместо собственного) и использует модель плагина MEF, так что EXE-сервер отключен и может использоваться совместно несколькими плагинами COM, которые могут быть разработаны независимо