|
When I use ClassInterfaceType.AutoDual, I see a bunch of warnings emitted by regasm, regarding CLR types that are not visible to COM.
The control works fine, but if I change ClassInterface to ClassInterfaceType.None, then I don't get these warnings, and the only methods that are exposed are those that I explicitly declare on the Control class, and implement.
The control works in either case, but I was just wondering if someone can tell me which is preferred.
|
|
|
|
|
check this below in the forum:
http://www.codeproject.com/cs/miscctrl/exposingdotnetcontrols.asp?forumid=2373&select=297660&app=50&df=100#xx297660xx
|
|
|
|
|
Hi,
hope someone is still using this control.
I have used the method to use the .NET DataGrid as an activeXcontrol in ATL dialogs.
I have a issue here, the controls' Leave_Focus event doesnt get fired, when the control actually loses focus.
Because of this, the dialog that iam working on hangs.
I would really appreciate if you could give me a soultion to this.
|
|
|
|
|
Hi,
I've created a COM control in C#, exposing methods and events. In my MFC application, I added this control (as an ActiveX object), and added some event handlers. Thing is, when the event is supposed to fire, it doesn't. Here's the C# code:
<br />
using System;<br />
using System.Collections;<br />
using System.ComponentModel;<br />
using System.Drawing;<br />
using System.Data;<br />
using System.Windows.Forms;<br />
using System.Runtime.InteropServices;<br />
using System.Text;<br />
using System.Reflection;<br />
using Microsoft.Win32;<br />
<br />
namespace CSharpCOMEvent<br />
{<br />
<br />
[Guid("BA7EA8FE-3D7C-4a19-A4C7-D8B3687C8170")]<br />
[InterfaceType(ComInterfaceType.InterfaceIsDual)]<br />
public interface ICSharpCOMInterface<br />
{<br />
void MethodA();<br />
void MethodB(int a);<br />
}<br />
<br />
[ComVisible(false)]<br />
public delegate void CSharpEventHandlerA ();<br />
[ComVisible(false)]<br />
public delegate void CSharpEventHandlerB (int a);<br />
<br />
[Guid("71BC0ECA-344D-4f6a-8184-2B497ECBA7FA"), <br />
InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]<br />
public interface ICSharpCOMEvents<br />
{<br />
void EventA();<br />
void EventB(int a);<br />
}<br />
<br />
[GuidAttribute("20F26BAB-C98F-4b5f-8DEF-E20D9151A3AB")]<br />
[ProgId("CSharpCOMEvent.CSharpUserControl")]<br />
[ComSourceInterfacesAttribute(typeof (ICSharpCOMEvents))]<br />
[ClassInterface(ClassInterfaceType.None)]<br />
public class CSharpUserControl : System.Windows.Forms.UserControl, ICSharpCOMInterface<br />
{<br />
private System.Windows.Forms.TextBox textBox1;<br />
private System.Windows.Forms.Button button1;<br />
private System.ComponentModel.Container components = null;<br />
<br />
[Category("Action")]<br />
public event CSharpEventHandlerA EventA;<br />
[Category("Action")]<br />
public event CSharpEventHandlerB EventB;<br />
<br />
public CSharpUserControl()<br />
{<br />
InitializeComponent();<br />
<br />
<br />
}<br />
<br />
protected override void Dispose( bool disposing )<br />
{<br />
if( disposing )<br />
{<br />
if( components != null )<br />
components.Dispose();<br />
}<br />
base.Dispose( disposing );<br />
}<br />
<br />
public void MethodA()<br />
{<br />
this.textBox1.Text = "MethodA";<br />
OnMyEventA();<br />
}<br />
<br />
public void MethodB(int a)<br />
{<br />
OnMyEventB(a);<br />
}<br />
<br />
protected virtual void OnMyEventA()<br />
{<br />
if (EventA != null)<br />
{<br />
this.textBox1.Text = "Event fired!";<br />
EventA();<br />
}<br />
}<br />
<br />
protected virtual void OnMyEventB(int a)<br />
{<br />
if (EventB != null)<br />
EventB(a);<br />
}<br />
<br />
[ComRegisterFunction()]<br />
public static void RegisterClass ( string key )<br />
{ <br />
StringBuilder sb = new StringBuilder ( key ) ;<br />
sb.Replace(@"HKEY_CLASSES_ROOT\","") ;<br />
<br />
RegistryKey k = Registry.ClassesRoot.OpenSubKey(sb.ToString(),true);<br />
<br />
RegistryKey ctrl = k.CreateSubKey ( "Control" ) ; <br />
ctrl.Close ( ) ;<br />
<br />
RegistryKey inprocServer32 = k.OpenSubKey ( "InprocServer32" , true ) ; <br />
inprocServer32.SetValue ( "CodeBase" , Assembly.GetExecutingAssembly().CodeBase ) ; <br />
inprocServer32.Close ( ) ;<br />
<br />
RegistryKey vers = k.CreateSubKey("Version");<br />
vers.SetValue("", "1.0");<br />
vers.Close();<br />
<br />
RegistryKey tlib = k.CreateSubKey("TypeLib");<br />
tlib.SetValue("", "{92C5B404-FDF3-4f1f-AEEF-50B801C69AE4}");<br />
tlib.Close();<br />
k.Close ( ) ;<br />
}<br />
<br />
[ComUnregisterFunction()]<br />
public static void UnregisterClass ( string key )<br />
{<br />
StringBuilder sb = new StringBuilder ( key ) ;<br />
sb.Replace(@"HKEY_CLASSES_ROOT\","") ;<br />
<br />
RegistryKey k = Registry.ClassesRoot.OpenSubKey(sb.ToString(),true);<br />
<br />
k.DeleteSubKey ( "Control" , false ) ;<br />
<br />
RegistryKey inprocServer32 = k.OpenSubKey ( "InprocServer32" , true ) ;<br />
<br />
k.DeleteSubKey ( "CodeBase" , false ) ;<br />
<br />
k.DeleteSubKey ( "TypeLib" , false ) ;<br />
<br />
k.DeleteSubKey ( "Version" , false ) ;<br />
<br />
k.Close ( ) ;<br />
}<br />
#region Component Designer generated code<br />
private void InitializeComponent()<br />
{<br />
this.textBox1 = new System.Windows.Forms.TextBox();<br />
this.button1 = new System.Windows.Forms.Button();<br />
this.SuspendLayout();<br />
this.textBox1.Location = new System.Drawing.Point(8, 8);<br />
this.textBox1.Name = "textBox1";<br />
this.textBox1.Size = new System.Drawing.Size(192, 20);<br />
this.textBox1.TabIndex = 0;<br />
this.textBox1.Text = "textBox1";<br />
this.button1.Location = new System.Drawing.Point(8, 32);<br />
this.button1.Name = "button1";<br />
this.button1.Size = new System.Drawing.Size(192, 24);<br />
this.button1.TabIndex = 1;<br />
this.button1.Text = "button1";<br />
this.button1.Click += new System.EventHandler(this.button1_Click);<br />
this.Controls.AddRange(new System.Windows.Forms.Control[] {<br />
this.button1,<br />
this.textBox1});<br />
this.Name = "CSharpUserControl";<br />
this.Size = new System.Drawing.Size(208, 64);<br />
this.ResumeLayout(false);<br />
<br />
}<br />
#endregion<br />
<br />
private void button1_Click(object sender, System.EventArgs e)<br />
{<br />
OnMyEventA();<br />
}<br />
}<br />
}<br />
When OnMyEventA() is being executed, EventA seems to register null.
In my VC++ code, the event sink appear to be in place:
<br />
BEGIN_EVENTSINK_MAP(CVCTestDlg, CDialog)<br />
ON_EVENT(CVCTestDlg, IDC_CSHARPUSERCONTROL1, 1610743808, EventACsharpusercontrol1, VTS_NONE)<br />
END_EVENTSINK_MAP()<br />
<br />
void CVCTestDlg::EventACsharpusercontrol1()<br />
{<br />
AfxMessageBox("Dlg EventA");<br />
}<br />
1610743808 is what MFC is reading as the event id.
The event fires okay on VB, but I'm not really sure it's really COM (as I'm refering to CSharpUserControl1.EventA directly).
<br />
Public Sub CSharpUserControl1_EventA() Handles CSharpUserControl1.EventA<br />
MsgBox("blah")<br />
End Sub<br />
Can anybody figure out how I can get it working with VC++?
BTW, I'm doing everying with the .NET Studio. Just want to get this working as proof of concept that a C# COM component can work just like an ActiveX control with a C++ client.
Thanks,
Vimedy.
|
|
|
|
|
You forgot to provide DispId's for your events and method in interface declarations: Like that
...
[DispId(1)] void Event1(int nTriggerId);
...
If you do that: you'll get constant dispiId's on your sink..
|
|
|
|
|
Actually, on later trials, I did use Dispids in the events declaration and I did get constant dispids on my sink. However, I still couldn't get the event to fire properly.
Funny enough, using the ActiveX Test Container works - the events fire okay. It just wouldn't fire in my MFC app. In my MFC app, I'm treating the C# COM object like any normal ActiveX object.
|
|
|
|
|
I know why:
There is a BUG in .NET 1.1(1.0 too) inside WinForm Control SetClientSite implementation -- it's getting called from Control::QuickActivate.
What happens is .NET SetClientSite checks for control running inside IE if global control counter is 1... Here they have a bug -- that produces an exception.
TstCon however tryes to call QuickActivate and if this one fails it establish SINK later through connection point interface, that's why it works in TSTCON...
To conform what I'm saying: try to create MFC dialog with 2 same C# ActiveX controls -- see what happens on the second one.
Regards
"...Ability to type is not enough to become a Programmer. Unless you type in VB. But then again you have to type really fast..."
Me
|
|
|
|
|
Thanks for your suggestions.
I've tried putting extra instances of the C# ActiveX control on the same dialog.
The events still won't fire from any of them.
Do you know of any workarounds?
|
|
|
|
|
Send me your email address through "email" -- I want to send you some stuff to try...
"...Ability to type is not enough to become a Programmer. Unless you type in VB. But then again you have to type really fast..."
Me
|
|
|
|
|
Hi,
I was just using the method now, and came across this link.
I too had the same issue of the events not getting fired in MFC applications.
Later, I decided to write a separate sink class for the event, instead of using the
BEGIN_EVENT_SINK_MAP macors.
In the sink class, say CEventSink, i derived it from CCmdTarget class, with automation support enabled.
(include afxctl.h in the stdafx.h )
In the EventSink.cpp file change the macro
BEGIN_INTERFACE_MAP(CEventSink, CCmdTarget)
INTERFACE_PART(CEventSink, IID_IEventSink, Dispatch)
END_INTERFACE_MAP()
as (say the control's interface is IControlEvents)
BEGIN_INTERFACE_MAP(CEventSink, CCmdTarget)
INTERFACE_PART(CEventSink, DIID__IControlEvents, Dispatch)
END_INTERFACE_MAP()
Now declare the event methods in the .h file.
eg. void OnClick().
Add them to the BEGIN_DISPATCH_MAP . eg:
DISP_FUNCTION_ID(CEventSink,"OnClick",1,OnClick,VT_EMPTY,VTS_NONE) where 1 is the DispathId of the control event.
Next call AfxConnection advise on the interface pointer.
// Create an instance of the event sink class
m_pEventSink = new CEventSink();
// Get a pointer to the sink IDispatch interface
LPUNKNOWN pUnknownSink = m_pEventSink->GetIDispatch(FALSE);
// Connect the event source to the sink
AfxConnectionAdvise(m_pIControl,
DIID__IcontrolEvents,
pUnknownSink,
FALSE,
&m_dwEventSink);
and dont forget to disconnect once the event use is done.
This should definitely work.
Thanks,
Sam.
|
|
|
|
|
Hi,
I was trying to develop the CEventSink class, but I have a doubt. I can not follow you from "Next call AfxConnection advise on the interface pointer." Where can I get the m_pIControl and the DIID__IcontrolEvents? where must I insert the rest of the code?
thanks in advance
Manuel.
|
|
|
|
|
Hi Vimedi,
did you ever get this to work properly? I am getting exactly the same problem; events not firing in an MFC client but they do work in the test container. Also the properties and methods are not available from MFC but are from the test container.
Any help would be appreciated.
Cheers,
Neil.
|
|
|
|
|
Although the solution given here works in the test container, it seems very flaky in a real application, e.g. Outlook forms, where I need my .NET control to appear.
Another idea would be to write an ActiveX control to act as a wrapper for a .NET control. This can be done in Visual C++ 2003 which now supports hosting .NET controls in unmanaged code. Ideally, it would be possible to create an ActiveX control that provides access to any given .NET control and its methods. This is what I'm currently looking into.
See the March 2003 issue of MSDN magazine ".NET Framework 1.1 provides expanded namespace, security, and language support for your projects" which is available online with source code.
|
|
|
|
|
Have you done this already? Can you give an example of how to do it?
Thanks
Dirk
|
|
|
|
|
Yes, I have done this. My code is mostly just as in the MSDN Magazine example, which can be found here:-
http://download.microsoft.com/download/b/d/9/bd91d944-ef7c-4720-97f1-fa5b33a4b84c/windowsforms.exe
Basically, I just created a new MFC ActiveX control project, copied in the code from the MSDN article above, and added some other little bits. The main parts of interest in my own code are as follows:-
VARIANT_BOOL CAxWinFormsControlCtrl::InitControl(void)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
System::Windows::Forms::Control* ctrlWinForms;
System::Reflection::Assembly* asmWinForms;
CComPtr<iunknown> spunkControl;
int iCtrlWidth, iCtrlHeight;
// Attempt to load the Windows Forms control library
try
{
asmWinForms = System::Reflection::Assembly::LoadFrom (System::Runtime::InteropServices::Marshal::PtrToStringAnsi((char*) (LPCTSTR) strCtrlLibraryPath));
}
catch (...)
{
return VARIANT_FALSE;
}
if (asmWinForms == NULL)
return VARIANT_FALSE;
// Attempt to load the Windows Forms control class
try
{
ctrlWinForms = (System::Windows::Forms::Control*)asmWinForms->CreateInstance(System::Runtime::InteropServices::Marshal::PtrToStringAnsi((char*) (LPCTSTR) strCtrlClass));
}
catch (...)
{
return VARIANT_FALSE;
}
if (ctrlWinForms == NULL)
return VARIANT_FALSE;
spunkControl.Attach((IUnknown*)System::Runtime::InteropServices::Marshal::GetIUnknownForObject(ctrlWinForms).ToPointer());
GetControlSize (&iCtrlWidth, &iCtrlHeight);
// Wrap control and place it on the parent window
ctrlwndWinFormsCtrl.Create(spunkControl, WS_VISIBLE | WS_TABSTOP, CRect (0, 0, iCtrlWidth, iCtrlHeight), this, 0);
boolCtrlInitialised = true;
return VARIANT_TRUE;
}
void CAxWinFormsControlCtrl::OnSize(UINT nType, int cx, int cy) {
COleControl::OnSize(nType, cx, cy);
if (ctrlwndWinFormsCtrl)
ctrlwndWinFormsCtrl.MoveWindow (0, 0, cx, cy); }
'strCtrlLibraryPath' and 'strCtrlClass' are set to the filename of the Windows Forms control's assembly and the control's fully-qualified class name, respectively. Most of the rest of it is just about as per the example.
The control's events are not handled by my wrapper control itself. I found a great library to do this, see http://home.att.net/~wshvbs/JohnsEventMapperPage.htm. I did have a go at integrating the code from this with my own wrapper control, but my COM knowledge was not quite good enough to make it work!
|
|
|
|
|
Hi Chris,
thanks for the answer. I already tried the same example you mention above, but my ActiveX Control crashed always. With your explanation I tried it again and have a working managed C++ wrapper control now.
1.) From a stock MFC ActiveX project, I had to add AfxEnableControlContainer(); to the InitInstance () function. (it was mentiond in the TRACE output, but I didn't looked at it
2.) After setting /CLR I have the linker warning "LNK4243 : DLL containing objects compiled with /clr is not linked with /NOENTRY; image may not run correctly".
I ignored that warning for now, since the second proposed fix in the MSDN will not work for me. The control crashes during a InterlockDecrement in the ATL String implementation.
3.) I did the creation of the windows forms control in the WM_CREATE message handler, like in the MfcWinFormsHost example.
Your solution via the InitControl function seems to be more general. But what path must one specify to get to the correct system.windows.forms.dll e.g.? Hardcoding the path into the GAC seems not to be a correct solution.
4.) Since I'm new to ActiveX Controls I had a few problems with the development roundtrip:
- Recompiling the OCX control wasn't always possible, after the visual studio activated the control in the resource editor, since the file was locked by the studio.
"LNK1104: cannot open file AxWinFormsControl.ocx"
- Is there any way to recreate the studio generated IDispatch wrapper file for the control? Deleting it and starting over from scratch is a little annoying
Thanks for fast answer
Dirk
|
|
|
|
|
Hi Dirk, glad I could be of help.
luedi wrote:
1.) From a stock MFC ActiveX project, I had to add AfxEnableControlContainer();
In my code, I also have:-
BOOL bInit = COleControlModule::InitInstance();
if (bInit)
{
AfxEnableControlContainer();
}
Sorry, forgot to mention that bit!
luedi wrote:
2.) After setting /CLR I have the linker warning "LNK4243 : DLL containing objects compiled with /clr is not linked with /NOENTRY; image may not run correctly".
I get that too, and ignore it.
luedi wrote:
3.) I did the creation of the windows forms control in the WM_CREATE message handler, like in the MfcWinFormsHost example.
Your solution via the InitControl function seems to be more general. But what path must one specify to get to the correct system.windows.forms.dll e.g.? Hardcoding the path into the GAC seems not to be a correct solution.
My solution does require the full path to the assembly containing the Windows Forms control, but there's no hardcoding involved. The way in which I'm using the wrapper control is to display a Windows Forms control on an Outlook form. The Windows Forms control in question happens to reside in the same assembly that is also used as a COM addin for Outlook, so when Outlook opens an item that uses the form, my COM addin traps this event and creates the wrapper control using the path to itself as the library path.
When I wrote the wrapper, I also assumed that the GAC would not be used (I'm deploying the project XCopy-style), so the Windows Forms control can reside in a directory relative to the application that creates an instance of the wrapper control.
luedi wrote:
4.) Since I'm new to ActiveX Controls I had a few problems with the development roundtrip:
- Recompiling the OCX control wasn't always possible, after the visual studio activated the control in the resource editor, since the file was locked by the studio.
"LNK1104: cannot open file AxWinFormsControl.ocx"
- Is there any way to recreate the studio generated IDispatch wrapper file for the control? Deleting it and starting over from scratch is a little annoying
I'm not a seasoned expert on developing ActiveX controls in C++; I too have had the above problems with other projects that I've worked on.
I'm not sure if the first point is a bug with Visual Studio; I've also had it lock assemblies containing Windows Forms controls when I've been editing them in the designer.
As for recreating the IDispatch wrapper file, do you mean the "...._i.c" file? I think it should always generate this file when you compile the OCX.
Chris.
|
|
|
|
|
Chris wrote:
- As for recreating the IDispatch wrapper file, do you mean the "...._i.c" file? I think it should always generate this file when you compile the OCX.
No I mean the IDispatch wrapper file for the typelibrary that gets automatically created when you assign a variable to the ActiveX control on a dialog e.g.
I think this is the same when you do
1.) Project/Add Class
2.) MFC/MFC class from ActiveX Conrol
Dirk
|
|
|
|
|
I would guess that this probably does need to be created every time; I don't know of any command-line utility to generate this. (That doesn't seem very good though!) I'm not using my wrapper control from a C++ project, so I don't have this problem. I'm using it in late-bound situations, such as the Outlook form and also on an Internet Explorer page.
I've noticed that when an ActiveX control is used in a .NET project, the project must be rebuilt twice if the ActiveX control is changed, before the changes are picked up by Intellisense.
|
|
|
|
|
Hi Chris
Thank You for Your comment. It helped me to start. I realy like the idea of C++ wrapper. I tried to make my own test wrapper and it worked fine. Still now I have problems with properties and events. Can You give me a suggestion how to access NET component properties and events? Concerning the events in previous comment You wrote about using JohnsEventMapper. The link is already broken (I downloaded it 2 weeks ago and it was ok..) and I dont know even how to use it... (Im new to COM programming...)
Thank You for any idea Tom
|
|
|
|
|
Hi Tom,
To access properties in your Windows Forms controls, you can add a property to your wrapper control that returns an IDispatch pointer to the contained WinForms control. Accessing properties from an ActiveX host is then as simple as calling properties in this IDispatch object.
Regarding "JohnsEventMapper", I don't have any code of my own here that uses it (it's at work), but there is a simple example in the readme file that comes with it (do you still have this?), that reads as follows:-
Example in VBScript
-------------------
Warning: To get a function pointer in VBScript, use getRef. This was introduced
in v5.0 of the scripting engine which will be shipped with IE5.0 and is currently
in beta. JScript has it in earlier versions although I'm not sure of the syntax.
Dim m_Mapper, m_Source
Sub Init()
Set m_Source = CreateObject("Some.ActiveX.Object")
Set m_Mapper = CreateObject("EventMapper.EventMapper")
m_Mapper.Init m_Source, "SomeEvent", getRef("SomeEventHandler")
End Sub
Sub Close()
If m_Mapper.Initialized Then m_Mapper.Close
End Sub
Sub SomeEventHandler()
...
End Sub
----------------------------------------------
(End of example)
This assumes that your .NET control has an event called "SomeEvent" and that its COM ProgID (specified by an attribute of the .NET class) is "Some.ActiveX.Object".
If you no longer have this file or any others from JohnsEventMapper.zip, let me know your email address and I would be glad to send them.
I was sad to see the reason that JohnsEventMapper is no longer available - if the author is reading this, please post your program somewhere else where people can find it, it's very, very useful!
|
|
|
|
|
Hi Chris
Thank You for suggestions. I have already solved the problem with use of direct COM classes and interfaces( it's not perfect but it works after two days of learning COM ). Now I'm able to access NET component properties and handle its events, wrap them and expose to container.
What I need to do now is to automatize the process (because there will be a lot of .NET components used in third party application which can act only as ActiveX container and doesn't support .NET). I plan to write generator application which will take any .NET component (VB,C#,C++) to input and produce ActiveX wrapper for it(and expose properties and events ofcourse ).
Maybe there's an easier way how make this... If anybody has idea please post it...
I will also try to make an example for my solution and place it somewhere on the internet.
Tom
|
|
|
|
|
Hi Chris and Tom,
I went into events handle problem with Windows form control as well. And I couldn't find JohnsEventMapper.zip anywhere. Would you mind email me this file or any other example if possiable?
Many Thank
Yao
Email: yaofu2@aol.com
|
|
|
|
|
Hi,
first - great work on the wrapper control, i used it and its realy clever.
But, when i run my MFC ActiveX wrapper, when ever i click a button on the C# control the whole application freezes.
Does anyone notice that behavior, or i'm doing something wrong?
Thanks.
Amir Harel
|
|
|
|
|
Thanks Amir, but I can't really take credit for it, it's pretty much as is from MSDN Magazine!
I'm not sure why that would be happening, feel free to post/send me your code if you'd like me to try and help.
|
|
|
|
|