|
I've managed to get the example working (thanks!), but I'm trying to install another activex component inside this one. When I do that, it DOES load in the tstcon app, but not in IE (which is obviously where I need it).
Any suggestions?
|
|
|
|
|
I've followed the instructions. Compiled the sample project, ran it with tstcon32.exe.
Great, everything works with the test tool.
However:
1/ The resulting dll cannot be registered with regsvr32
2/ I tried creating the object with Delphi (using either its name or guid) and got 'Invalid arithmetic operation exceeded 32 bits'.
This doesn't seem to be the behaviour of a nice ActiveX control.
|
|
|
|
|
I'm using VS2003. I downloaded the test project, compiled it, and created a .DLL file. This .DLL isn't recognized as a com dll and cannot be registered hence cannot be tested.
I've also tried producing the .DLL using the explanations and got the same issue. The generated .DLL cannot be registered.
Any step I'm missing here?
|
|
|
|
|
Yes, because it is not ordinary active-x dll. It's really registered by visual studio and you can find that object registered with ActiveX Test Container.
|
|
|
|
|
Hi ! ,
I needed to call the control from an HTML page ie, embed it in IE .
I tried the following Object tag :
**********************************************************
<object id="prisonerControl1"
classid="http:Prisoner.dll#Prisoner.PrisonerControl"
height="300" width="300" viewastext="">
**********************************************************
I have kept the dll file in the same directory as the web page .
But the web page does not load it .
I use IE 6 .
Can anybody please help me figure out how to embed the control in IE ?
Pranab
|
|
|
|
|
you can't have execute permissions on the directory that the dll is hosted in, also, you need to check your classID since it looke like it points to the dll as the server. You need to likely add a server to yours as such http://myserver/Prisoner.dll#Prisoner.PrisonerControl
|
|
|
|
|
You'll need to creat a virtual directory in IIS...
In this directory put the dll and the html.
If you don't want to use IIS go see this article
http://support.microsoft.com/default.aspx?scid=kb%3Ben-us%3B305624
|
|
|
|
|
You need to search the registry for the correct CLSID
( Search for "PrisonerControl", and you'll find a GUID near it )
''--ASP code, its just html at the end of the day.
<%
Function Out(str)
On Error Resume Next
Response.Write(str)
Out = str
End Function
Out("<object ID='Prisoner___PrisonerControl' CLASSID='CLSID:DB5E3072-DC36-37D3-9077-77075CAFD602' width='100%' height='75%'>")
Out("<param name='UserName' value='Johnny'>")
Out("</object>" & vbCrLf)
%>
I have NOT figured out how to pass parameters into it.
That would be a great help.
|
|
|
|
|
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.
|
|
|
|
|