Могу ли я получить доступ к своим объектам C# из моего внешнего.dll? Может быть, с помощью ROT
У меня есть приложение aC#, которое работает локально, и у меня есть.dll, написанные на C#.
MyApplication содержит
namespace MyApplication
{
public interface IMyInterface
{
IMyInterface Instance { get; }
int MyProp { get; }
}
class MyClass : IMyInterface
{
public int MyProp { get; private set; }
private MyClass instance
public static IMyInterface
{
get
{
if (instance == null)
{
instance = new MyClass();
}
return instance;
}
}
private MyClass() { MyProp = 1; }
}
}
Моя библиотека:
namespace MyLibrary
{
public LibClass
{
public static int GetProp()
{
//I want to get the running instance of MyApplication
//If MyApplication is not running I want to start a new instance
//From my application instance I want to
//return the value from MyInterface.Instance.MyProp
}
}
}
Некоторое прибегание к гуглу заставляет меня смотреть на своего рода COM-сервер, но неясно, был ли это лучший подход. У меня возникли проблемы, даже зная, что Google для этого. в конечном счете MyInterface
Будет намного сложнее и будет включать события для уведомления MyLibrary
обновить данные. Как лучше всего это сделать? Это COM сервер? Хочу ли я создать какой-то API в MyApplication
тот MyLibrary
использует?
Дополнительная информация:
Мой.dll создается в качестве сервера данных в режиме реального времени для Excel. Я хочу иметь доступ к данным в моем приложении из Excel и уведомлять Excel об обновлениях. Я не хочу иметь несколько экземпляров моего приложения, так как пользовательский ввод будет определять значения, отображаемые в Excel. Я могу создать сервер rtd, однако я не уверен, как лучше всего получить доступ к моим внешним данным.
РЕДАКТИРОВАТЬ:
После нескольких исследований я думаю, что заинтересован в использовании GetActiveObject("MyApplication.IMyInterface")
внутри MyLibrary
выглядеть как
namespace MyLibrary
{
public LibClass
{
public static int GetProp()
{
running_obj = System.Runtime.InteropServices.Marshal.GetActiveObject("MyApplication.IMyInterface")
return ((IMyInterface) running_obj).MyProp;
}
private object running_obj = null;
}
}
Но я не уверен, как зарегистрироваться MyApplication.MyClass
в ROT. Код как есть выдает исключение
Invalid class string (Exception from HRESULT: 0x800401F3 (CO_E_CLASSSTRING))
2 ответа
Мое решение
- Если кто-нибудь знает лучший способ сделать это, я хотел бы знать.
- У меня также все еще есть проблемы с добавлением события в мой COMinterface. Я хотел бы добавить
public event ComEvent MyApplicationClose;
внутриMyApplication
быть вызванным, когда это закрыто.
редактировать
Мне удалось заставить события работать, используя управляемые приемники событий. Изменения отражены в коде ниже.
Хелпер класс
namespace ole32
{
public class Ole32
{
[DllImport( "Ole32.Dll" )]
public static extern int CreateBindCtx( int reserved, out IBindCtx
bindCtx );
[DllImport( "oleaut32.dll" )]
public static extern int RegisterActiveObject( [MarshalAs( UnmanagedType.IUnknown )] object punk,
ref Guid rclsid, uint dwFlags, out int pdwRegister );
[DllImport( "ole32.dll", EntryPoint = "GetRunningObjectTable" )]
public static extern int GetRunningObjectTable( int reserved, out IRunningObjectTable ROT );
[DllImport( "ole32.dll", EntryPoint = "CreateItemMoniker" )]
public static extern int CreateItemMoniker( byte[] lpszDelim, byte[] lpszItem, out IMoniker ppmk );
/// <summary>
/// Get a snapshot of the running object table (ROT).
/// </summary>
/// <returns>A hashtable mapping the name of the object
// in the ROT to the corresponding object</returns>
public static Hashtable GetRunningObjectTable()
{
Hashtable result = new Hashtable();
IntPtr numFetched = IntPtr.Zero;
IRunningObjectTable runningObjectTable;
IEnumMoniker monikerEnumerator;
IMoniker[] monikers = new IMoniker[1];
GetRunningObjectTable( 0, out runningObjectTable );
runningObjectTable.EnumRunning( out monikerEnumerator );
monikerEnumerator.Reset();
while ( monikerEnumerator.Next( 1, monikers, numFetched ) == 0 )
{
IBindCtx ctx;
CreateBindCtx( 0, out ctx );
string runningObjectName;
monikers[0].GetDisplayName( ctx, null, out runningObjectName );
object runningObjectVal;
runningObjectTable.GetObject( monikers[0], out runningObjectVal );
result[runningObjectName] = runningObjectVal;
}
return result;
}
}
}
Моя заявка зарегистрирована в ROT
Мое приложение будет действовать как сервер данных. Он получает и обрабатывает данные из нескольких источников. Доступ к этим данным выставлен через COM. Наличие только одного экземпляра MyApplication уменьшает избыточность соединений с внешними источниками данных и их обработку, позволяя нескольким клиентам использовать полученные данные.
namespace MyNamespace
{
[ComVisible( true ),
GuidAttribute( "14C09983-FA4B-44e2-9910-6461728F7883" ),
InterfaceType( ComInterfaceType.InterfaceIsDual )]
public interface ICOMApplication
{
[DispId(1)]
int GetVal();
}
//Events for my com interface. Must be IDispatch
[Guid( "E00FA736-8C24-467a-BEA0-F0AC8E528207" ),
InterfaceType( ComInterfaceType.InterfaceIsIDispatch ),
ComVisible( true )]
public interface ICOMEvents
{
[DispId( 1 )]
void ComAppClose( string s );
}
public delegate void ComEvent( string p );
[ComVisible(true)]
[Guid( "ECE6FD4C-52FD-4D72-9668-1F3696D9A99E" )]
[ComSourceInterfaces( typeof( ICOMWnEvents) )]
[ClassInterface( ClassInterfaceType.None )]
public class MyApplication : ICOMApplication, IDisposable
{
//ICOMEvent
public event ComEvent ComAppClose;
protected MyApplication ()
{
//check if application is registered.
//if not registered then register the application
if (GetApiInstance() == null)
{
Register_COMI();
}
}
// UCOMI-Version to register in the ROT
protected void Register_COMI()
{
int errorcode;
IRunningObjectTable rot;
IMoniker moniker;
int register;
errorcode = Ole32.GetRunningObjectTable( 0, out rot );
Marshal.ThrowExceptionForHR( errorcode );
errorcode = BuildMoniker( out moniker );
Marshal.ThrowExceptionForHR( errorcode );
register = rot.Register( 0, this, moniker );
}
public void Dispose()
{
Close( 0 ); //close and clean up
}
//Will look for an existing instance in the ROT and return it
public static ICOMApplication GetApiInstance()
{
Hashtable runningObjects = Ole32.GetRunningObjectTable();
IDictionaryEnumerator rotEnumerator = runningObjects.GetEnumerator();
while ( rotEnumerator.MoveNext() )
{
string candidateName = (string) rotEnumerator.Key;
if ( !candidateName.Equals( "!MyNamespace.ICOMApplication" ) )
continue;
ICOMApplication wbapi = (ICOMApplication ) rotEnumerator.Value;
if ( wbapi != null )
return wbapi;
//TODO: Start the application so it can be added to com and retrieved for use
}
return null;
}
//Builds the moniker used to register and look up the application in the ROT
private static int BuildMoniker( out IMoniker moniker )
{
UnicodeEncoding enc = new UnicodeEncoding();
string delimname = "!";
byte[] del = enc.GetBytes( delimname );
string itemname = "MyNamespace.ICOMApplication";
byte[] item = enc.GetBytes( itemname );
return Ole32.CreateItemMoniker( del, item, out moniker );
}
protected void Close( int i )
{
//Deregistering from ROT should be automatic
//Additional cleanup
if (ComAppClose != null) ComAppClose("");
}
~MyApplication()
{
Dispose();
}
//implement ICOMApplication interface
private static int i = 0;
public int GetVal()
{
return i++; //test value to return
}
}
}
Примечание. Содержит событие Post-build для регистрации сборки.
C:\Windows\Microsoft.NET\Framework\v2.0.50727\RegAsm.exe $(TargetFileName) /codebase /tlb:$(TargetName)TypeLib.tlb
Использование MyApplication через COM
Так как это через COM, оно должно работать с любым языком COM, однако я пробовал только C#. Первоначально этот код будет помещен в RTDserver для Excel. Это позволит пользователю создавать сложные рабочие листы, которые используют данные моего приложения в режиме реального времени.
Сначала я создаю оболочку в точечной сети для моего COM-объекта.
namespace COMTest
{
//extend both the com app and its events and use the event sink to get the events
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public sealed class MyAppDotNetWrapper, ICOMEvents, ICOMApplication
{
private ICOMApplication comapp;
public MyAppDotNetWrapper()
{
StartEventSink()
}
//Manage the sink events
private void StartEventSink()
{
//get the instance of the app;
comapp = MyApplication .GetApiInstance();
if (comapp != null)
{
serverconnected = true;
//Start the event sink
IConnectionPointContainer connectionPointContainer = (IConnectionPointContainer) comapp;
Guid comappEventsInterfaceId = typeof (ICOMApplicationEvents).GUID;
connectionPointContainer.FindConnectionPoint(ref comappEventsInterfaceId, out connectionPoint);
connectionPoint.Advise(this, out cookie);
}
}
private void StopEventSink()
{
if (serverconnected)
{
//unhook the event sink
connectionPoint.Unadvise(cookie);
connectionPoint = null;
}
}
//Implement ICOMApplication methods
public int GetVal()
{
return comapp.GetVal();
}
//receive sink events and forward
public event ComEvent ComAppCloseEvent;
public void ComAppClose(string s)
{
serverconnected = false;
ComAppCloseEvent(s);
}
private ICOMApplication comapp;
IConnectionPoint connectionPoint;
private int cookie;
private bool serverconnected;
}
}
Теперь я могу использовать свою обертку в моем приложении.net
namespace COMTest
{
class Program
{
private static MyAppDotNetWrapper app;
static void Main( string[] args )
{
//create a new instance of the wrapper
app = new MyAppDotNetWrapper();
//Add the onclose event handler
app.ComAppCloseEvent += OnAppClose;
//call my com interface method
Console.WriteLine("Val = " + app.GetVal().ToString());
Console.WriteLine("Val = " + app.GetVal().ToString());
string s = Console.ReadLine();
}
static voic OnClose(string s)
{
Console.WriteLine("Com Application Closed.");
}
}
}
Где вывод
Val = 1
Val = 2
А после закрытия приложения COM-сервера вы увидите сообщение о закрытии.
Val = 1
Val = 2
Com Application Closed.
Рекомендации
Таблица работающих объектов: поставщик в.NET, потребитель в MFC
Автоматизация конкретного экземпляра Visual Studio .NET с использованием C#
Вам нужно зарегистрировать свою библиотеку.net как объект COM, а затем создать ее экземпляр и вызвать ее из кода Excel. Я думаю, что здесь есть хороший стартер по взаимодействию COM и.net.
Также есть хороший учебник, доступный специально для Excel - .NET Interop.