I'm trying to get access to certain parts of my application through COM, mainly because I need a Delphi application to be able to invoke certain services on the .NET application.
I've managed to expose some methods via COM inheriting from ServicedComponent class and its working fine.
Now I need to be able to raise events to the client so I can alert him when certain things occur. I currently have something like this:
public interface IEvents
{
void OnMessage();
}
[EventClass]
[ComVisible(true)]
public class MyEventsClass : ServicedComponent, IEvents
{
public void OnMessage()
{
}
}
but when I import the TypeLibrary from Delphi I get this:
IEvents = interface(IDispatch)
['{E7605303-F968-3509-829B-BF70084053C4}']
procedure OnMessage; safecall;
end;
IEventsDisp = dispinterface
['{E7605303-F968-3509-829B-BF70084053C4}']
procedure OnMessage; dispid 1610743808;
end;
which I don't really know how to use. I was expecting an IUnknown interface that I can implement and provide to the service through a MyService.Register(IEvents events)...
I think I'm completely lost here... Any advice or reference about how to properly implement COM+ events with .NET?
I'm still unsure of what exactly is the use for EventClass by I've discovered you don't need to use it. I simply have declared this:
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IMyEventIface
{
}
And with that the interface is exported as an IUnknown interface to Delphi.
Then you define a method on your ServicedComponent class which a connect signature like this:
public void Connect(IMyEventIface eventHandler)
{
mEvents.Add(eventHandler);
}
which left you to manually handle the invoke of each client event handler but is the only way I've found.
Related
So we are working on new app for Windows Store and we're utilizing DirectX on C++/CX part of app and C# on other. As we needed some sort of communication, we created interface in shared library (C#) and class that implements it on C# side of the project. We then pass instance of that class to the C++. When C++ need to communicate with c#, it calls methods from shared interface and c# executes what was implemented in the class.
The problem arises when C++ calls methods from another thread than then object was passed on to it. Error:
The application called an interface that was marshalled for a different thread.
Here's how it works:
C# instance of shared interface (some class)
C# -> C++ \/ passed in parameter of public C++ method
C++ saves to variable
C++ creates new thread
C++ calls instance->SayHello();
C# exception
Shared interface: (C#)
public interface IUserMessageService
{
void WriteMessage(string message, MessageTypes type);
}
Implementation: (C#)
public sealed class UserMessageService : IUserMessageService
{ ... }
Assign method (C++)
void AssignUserMessageService(...::IUserMessageService ^service) { m_service = service; }
I then call AssignUserMessageService with instance of UserMessageService from UI thread. Error occurs when I try to access data passed by C++ when calling WriteMessage in another thread.
Is there any other way to pass instance or call method?
The problem was that I was using CoreApplication.MainWindow.CoreWindow.Dispatcher instead of Window.Current.Dispatcher
I have COM server with events implemented in C# and don't know how to write a C# client which hooks to events. I found several articles which describe how to do C# server and C++ clients but none (or I'm blind :-P ) which describe how to do the C# client using events. I'm able to connect to the COM server object but have no idea how to hook to the events.
Note I have two applications - one contains the C# COM server and another application which contains the C# client. The server is implemented as follows:
[ComVisible(true)]
[Guid("08214B02-512D-4785-9176-C4B4324FC340")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IMyServer
{
bool Play(string sFile);
}
[ComVisible(true)]
[Guid("141CAAEA-63CE-422E-BF00-BAF4DBEEA77A")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IMyServerEvents
{
[DispId(1)]
event OnPlayFinishedHandler OnPlayFinished;
}
[ComVisible(true)]
[ProgId("MyApp.MyServer")]
[Guid("D184855D-E425-46A6-9171-34C828353778")]
[ComSourceInterfaces(typeof(IMyServerEvents))]
[ClassInterface(ClassInterfaceType.None)]
public class MyServer : IMyServer, IDisposable
{
public MyServer()
{
}
[ComVisible(false)]
public delegate void OnPlayFinishedHandler();
public event OnPlayFinishedHandler OnPlayFinished;
public bool Play(string sFile)
{
if (OnPlayFinished != null)
OnPlayFinished();
return true;
}
}
and now the client, I'm obtaining the COM server object via Running Object Table (not important I think here how).
IMyServer oServer = GetServer();
// TODO: Connect to OnPlayFinised event
oServer.Play("C://File.txt");
I tried to cast oServer to IMyServerEvents but it is not allowed.
As Hans said, you cannot add a COM reference if it's a .NET assembly. You have to add it as a normal assembly reference. Have you seen this MSDN example of implementing a C# COM server with events:
http://msdn.microsoft.com/en-us/library/dd8bf0x3(v=vs.90).aspx
Following from that MSDN example, you would then add that C# assembly as a reference in your C# client app. To hook into the events that the COM server raises, you'd write the event handler and then subscribe to the event. This sample code refers to the above MSDN example.
public void ClickEventHandler(int x, int y)
{
// The "Click" event was raised on the COM server, handle the event here
}
EventSource.Button myButton = new EventSource.Button();
myButton.Click += new EventSource.ClickDelegate(ClickEventHandler); // subscribe to the event
Consider the automation-compatible COM library in C#, given below. It follows a common COM pattern of having a visible factory coclass FooFactory implementing ICreateFoos which creates an object of type IFoo. FooFactory is the only coclass in the type library. (The factory pattern is particularly useful with COM, as it does not allow for parameterized constructors).
In the code below, I'm finding that I cannot access the returned IFoo interface from jscript unless I make the FooImpl class ComVisible (by uncommenting commented lines; this causes it to appear as a coclass in the type library). There is no such problem accessing this from VBscript.
That is, I can run this VBScript:
set ff = CreateObject("jstest.FooFactory")
set foo = ff.CreateFoo(0)
foo.Foo
But this functionally identical JScript fails, with the error "C:\temp\jstest\jstest.js(4, 1) Microsoft JScript runtime error: 'foo' is null or not an object":
var ff = new ActiveXObject("jstest.FooFactory");
var foo = ff.CreateFoo(0)
//WScript.Stdout.WriteLine(null==foo)
foo.Foo();
If I uncomment the line, I can see that null==foo is false.
Why does this happen? Is it a bug? Note that I think this is a problem is a combination of JScript and the C#/.net-specific implementation (possibly of IDispatch), because I have other similar COM servers - implemented in C++ - that do not exhibit this problem from JScript.
The problem goes away if I uncomment the commented lines in the code below, making FooImpl visible as a coclass - but I specifically do not want to do this as I don't want to expose implementation details. A workaround seems to be to make FooImpl ComVisible, but mark its constructor internal, which prevents clients from being able to CoCreate it, but that's hardly elegant.
I'm running on WinXP SP3 with Visual Studio 2005, .net 2, and have been able to reproduce the issue on a completely fresh install of TinyXP on a VirtualBox (both with Windows Script Host 5.7), and also on Windows 7 Ultimate using .net SDKs 2.0, 3.0, 3.5 and 4.0 (WSH 5.8). All OSes were 32-bit.
The library code:
using System;
using System.Runtime.InteropServices;
[assembly: ComVisible(false)]
namespace jstest
{
[ComVisible(true)]
public interface ICreateFoos
{
IFoo CreateFoo(int importantNumber);
}
[ComVisible(true)]
public interface IFoo
{
void Foo();
}
[ComVisible(true)]
public class FooFactory : ICreateFoos
{
public IFoo CreateFoo(int importantNumber)
{ // in *this* version, we don't use importantNumber yet
return new FooImpl();
}
}
//[ComVisible(true)]
public class FooImpl : IFoo
{
public void Foo()
{
Console.WriteLine("Foo");
}
}
}
You can compile and register (you may have to run as admin to regasm) this with
csc /target:library jstest.cs
regasm /codebase jstest.dll
When QueryInterface is called against the IFoo object returned from CreateFoo for the IDispatch GUID it returns E_NOINTERFACE unless ComVisible is set for the actual implementing class.
When jscript prepares to call the Foo method it calls QueryInterface several times, including with this specific GUID, and since an error is returns it doesn't try to use Invoke.
When vbscript prepares to call the Foo method it does not check the interface supports IDispatch. QueryInterface is called, once, with the GUID for IDispatchEx but it seems to simply assume IDispatch will be supported.
I have a VB6 project(windows application) and I have to redevelop a module in the existing VB6 project in C#.net.
The module that I develop in C#.net should be a dll and should contain some windows forms. I was able to successfully call a c# console applicaiton dll from my vb6 project but I am facing issues when i try to call a C# class library with winforms from my VB6 project.
This is what I have done for my Proof Of Concept - This is a class file in my C#.net class library project.
namespace TestDll
{
public interface IClass1
{
void DisplayMessage();
}
public class Class1:IClass1
{
void IClass1.DisplayMessage()
{
MessageBox.Show ("Displyaing message");
}
}
}
I have a form in the same nemspace and I plan to instantiate Class1 and use its object on the page_load event of the C# winform.
In my VB6 project I want to display the form I have in my C#.net dll. I am calling it by this code -
Private Declare Sub DislayMessage Lib "TestDll.dll" ()
Private Sub Command1_Click() //On button click event of the VB6 windows form
DislayMessage
End Sub
I get an error - "Can't find a DLL entry point in DisplayMessage in TestDll.dll"
I am not sure how to solve this error. I am even skeptical if this is the way a C#.net dll which contains some winforms should be called from a VB6.0 windows applicaiton.
Should I instantiate Class1 in my VB6 code? How do I resolve this error?
Is my approach correct? Are there better ways to do this?
TIA.
You have to make your class COM-Visible. Here's how I would change your code:
namespace TestDll
{
[Guid("FB8AB9B9-6986-4130-BD74-4439776D1A3D")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[ComVisible(true)]
public interface IClass1
{
[DispId(50)]
void DisplayMessage();
}
[Guid("74201338-6927-421d-A095-3BE4FD1EF0B4")]
[ClassInterface(ClassInterfaceType.None)]
[ComVisible(true)]
[ProgId("TestDll.Class1")]
public class Class1:IClass1
{
void IClass1.DisplayMessage()
{
MessageBox.Show ("Displyaing message");
}
}
}
Note the [DispId(50)]. You want to specify the dispatch ID for your COM-visible methods, properties, and events. If you don't, the compiler will do it for you and you may end up breaking compatibility every time you compile. The number doesn't matter so much as it doesn't change between compiles.
You might want to check out Building COM Objects in C#. It's a pretty good getting started tutorial.
Some highlights:
Exposing the VC# objects to the COM
world requires the following …
* The class must be public
* Properties, methods, and events must be public.
* Properties and methods must be declared on the class interface.
* Events must be declared in the event interface.
Every Interface needs a GUID property
set before the interface name. To
generate the unique Guid , use the
guidgen.exe utility and select the
Registry Format.
The only way to do it is to expose your C# class as a COM object (also called a CCW - COM Callable Wrapper), and create an instance of that COM object in your VB6 code.
This should help you get started:
http://www.bing.com/search?q=C%23+CCW&go=&form=QBRE&qs=n
There some excellent help on msdn here:
https://learn.microsoft.com/en-us/dotnet/visual-basic/programming-guide/com-interop/
if this possible to write a com control or activex in C# and use it in MFC ?
Yes. First, you need to create COM object. Below is a very simple example.
[Guid("123565C4-C5FA-4512-A560-1D47F9FDFA20")]
public interface IDoSomething
{
[DispId(1)]
string Name { get; }
[DispId(2)]
int DoSomething();
}
[ComVisible(true)]
[Guid("12AC8095-BD27-4de8-A30B-991940666927")]
[ClassInterface(ClassInterfaceType.None)]
public sealed class DoSomething: IDoSomething
{
public DoSomething()
{
}
public string Name
{
get { return ""; }
}
public int DoSomething()
{
return 4; //random number
}
}
After that you need to regasm your assembly. The regasm tool will add the necessary registry COM entries:
regasm.exe /tlb component.dll
/tlb is necessary to generate the type library to be imported in your MFC application.
Once your assembly is registered, you can call DoSomething in your MFC application like any other COM objects.
Check this link for more information.
This is a bit outside my normal territory, as I don't interact with .NET Interop technology all that much.
It is possible to create what's called a COM Callable Wrapper around your C# control/class to make it accessible to any COM-aware program. I won't duplicate Francis B's answer because it's fairly complete as it stands.
The big question is whether a visual C# control works seamlessly within an MFC window. That's not something I can answer, but my best advice would be to prepare for a bumpy road ahead. Please see http://bytes.com/topic/net/answers/430618-c-control-mfc-window-frame for more detail.