I am trying to implement an ATL COM module with a connection point. The source code for this is all pretty much boilerplate that I copied by example from chapter 12 of Developer's Workshop to COM and ATL 3.0 by Andrew W. Troelsen.
That's a lot of code, with three source files (almost all Visual Studio rendered boilerplate).
I think have got everything that I need, though. There is a subclass called _IFooEvents_CP.h to handle events and the class implemments IConnectionPointImpl:
using namespace ATL;
template <class T>
class CProxy_IFooEvents : public IConnectionPointImpl<T, &__uuidof( _IFooEvents ), CComDynamicUnkArray>
{
// WARNING: This class may be regenerated by the wizard
public:
HRESULT Fire()
{
//...
// event proxy code
//...
}
}
Then, in the header to my coclass (Foo, of course):
// Foo.h : Declaration of the CFoo
#pragma once
#include "resource.h" // main symbols
#include "ConnectionPointTest_i.h"
#include "_IFooEvents_CP.h"
#if defined(_WIN32_WCE) && !defined(_CE_DCOM) && !defined(_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA)
#error "Single-threaded COM objects are not properly supported on Windows CE platform, such as the Windows Mobile platforms that do not include full DCOM support. Define _CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA to force ATL to support creating single-thread COM object's and allow use of it's single-threaded COM object implementations. The threading model in your rgs file was set to 'Free' as that is the only threading model supported in non DCOM Windows CE platforms."
#endif
using namespace ATL;
// CFoo
class ATL_NO_VTABLE CFoo :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CFoo, &CLSID_Foo>,
public IConnectionPointContainerImpl<CFoo>,
public IDispatchImpl<IFoo, &IID_IFoo, &LIBID_ConnectionPointTestLib, /*wMajor =*/ 1, /*wMinor =*/ 0>,
public CProxy_IFooEvents<CFoo>
{
public:
CFoo()
{
}
DECLARE_REGISTRY_RESOURCEID(IDR_FOO)
BEGIN_COM_MAP(CFoo)
COM_INTERFACE_ENTRY(IFoo)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(IConnectionPointContainer)
END_COM_MAP()
BEGIN_CONNECTION_POINT_MAP(CFoo)
CONNECTION_POINT_ENTRY(__uuidof(_IFooEvents))
END_CONNECTION_POINT_MAP()
Furthermore, the idl file has the event source forward declared and all set thanks to the ATL connection point wizard that I used to make this thing:
library ConnectionPointTestLib
{
importlib("stdole2.tlb");
[
uuid(25EAB56B-884A-4AA9-B470-BAA975E08343)
]
dispinterface _IFooEvents
{
properties:
methods:
[id(1), helpstring("test")] HRESULT bar();
};
[
uuid(54B6050F-1090-4660-9DF0-D8A0853F96CF)
]
coclass Foo
{
[default] interface IFoo;
[default, source] dispinterface _IFooEvents;
};
};
So, I have an implementation of the connection point container interface, I have a connection point in the connection point map, and I have forward declared an event function to implement on the client side.
It compiles without warning or error. Then on the C# side I have the following:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Runtime.InteropServices.ComTypes;
using System.Runtime.InteropServices;
using ConnectionPointTestLib;
namespace TestConnectionPoint
{
class EventSink
{
public EventSink() { }
public void bar()
{
Console.WriteLine("hello");
Console.ReadLine();
}
}
class Program
{
static void Main(string[] args)
{
try
{
ConnectionPointTestLib.IFoo cpTest = new ConnectionPointTestLib.Foo();
IConnectionPointContainer icpc;
icpc = (IConnectionPointContainer)cpTest;
IConnectionPoint icp;
Guid id = typeof(Foo).GUID;
icpc.FindConnectionPoint(ref id, out icp);
EventSink es = new EventSink();
int cookie;
icp.Advise(es, out cookie);
cpTest.testCP();
Console.ReadLine();
}
catch (COMException e)
{
Console.WriteLine(e);
Console.ReadLine();
}
}
}
}
The exception is thrown on this line:
icpc.FindConnectionPoint(ref id, out icp);
and the error code is exotic: 0x80040200. I couldn't locate this code anywhere, but I think it is not an official Windows code. At least, I couldn't find it on MSDN's list.
I copied the client code from an example I found online, so it could be that I am missing a step.
Later:
As #Hans Passant pointed out, the error code is CONNECT_E_NOCONNECTION. My understanding is that this means that there are no connections exposed by the interface. But, as far as I can tell (and I am new to this game) I have done everything I need to do in order to have an outgoinginterface here.
I feel like I'm missing some tiny magical step.
Related
There is an unexplained ambiguity in C#, where I explicitly try to call a constructor but the compiler thinks it is a different constructor. I will start with showing a short C# architecture we use. Then show a small "working" example I created, and the possible solution to this, but still I like to understand why this happens.
The Architecture:
CLR DLL which bridges the C++ API.
C# API which uses the bridge level.
C# Client applications that use the C# API.
Note that the C# Clients are not allowed to use the CLR level.
Example I created
A class in the CLR DLL:
#pragma once
#include <string>
using namespace System;
namespace Inner {
public ref class AInner
{
public:
AInner() : _data(new std::wstring(L"")) {}
~AInner() {
delete _data;
}
property String^ Val
{
String^ get()
{
return gcnew String((*_data).data());
}
void set(String^ value) {
System::IntPtr pVal = System::Runtime::InteropServices::Marshal::StringToHGlobalUni(value);
*_data = (const wchar_t*)pVal.ToPointer();
System::Runtime::InteropServices::Marshal::FreeHGlobal(pVal);
}
}
private:
std::wstring* _data;
};
}
Class wrapping the CLR level, in a DLL:
using System;
using Inner;
namespace Outer
{
public class A
{
public A()
{
_inner.Val = String.Empty;
}
public A(string val)
{
init(val);
}
public string Val
{
get
{
return _inner.Val;
}
set
{
_inner.Val = value;
}
}
internal A(AInner inner)
{
_inner = inner;
}
private void init(string Val)
{
_inner = new AInner();
_inner.Val = String.Empty;
}
private AInner _inner;
}
}
Note that there is an internal constructor and a public constructor.
Executable Client using the C# API DLL:
using Outer;
namespace OneClient
{
class Program
{
static void Main(string[] args)
{
string myString = "Some String";
A testA = new A(myString);
}
}
}
Twist in the story:
In the DLL wrapping the CLR level, not ALL API should be used by external clients, but can be used by internal clients, thus the internals are exposed to the internal clients by adding [assembly: InternalsVisibleTo("OneClient")] to the 'AssemblyInfo.cs' of the DLL wrapping the CLR level.
The issue
When compiling the Client code I get the following error:
error CS0012: The type 'AInner' is defined in an assembly that is not referenced. You must add a reference to assembly 'InnerOne, Version=1.0.7600.28169, Culture=neutral, PublicKeyToken=null'.
I cannot use InnerOne because clients are not allowed to use this level.
The client is exposed to both A(string val) and A(AInner inner) constructors.
Possible Workarounds:
Remove the [assembly: InternalsVisibleTo("OneClient")] - This is unacceptable due to other classes internals that the specific client needs to use.
Change the A(string val) constructor to A(string val, bool unique=true) and use it A testA = new A(myString, true) - Not a nice solution.
Use default constructor A() and call testA.Val = myString; - This is actually OK but to much code.
Change the client code from A testA = new A(myString) to A testA = new A(val:myString); - This is actually the chosen solution.
Question
Why does this ambiguity happen?
I call the A(string val) with the myString which is actually a string value
This is very strange.
Is this a bug in Microsoft compiler?
Example Sources:
Source Code One.zip
Why does this ambiguity happen?
Because to satisfy the constructor overload resolution, the compiler needs to know what all the argument types are, and it doesn't know what an AInner is.
Why not expose the AInner version as a factory method:
static internal A Create(AInner inner)
{
return new A { _inner = inner };
}
I don't see any issue in this, the problem is we are used to do the things in a wrong/briefly way.
The correct answer fot this is:
A testA = new A(val:myString);
Furthermore, all your calls (in this way is a call to a constructor/initializer but it's a call anyway) should be with the parameter name. No one (even me) writes them, but...
I have checked on stackoverflow (and what seems like everywhere else). I would like to get a COM solution working so that a jscript file can be written as
var T = new ActiveXObject("MySimulator.World");
T.MyMethod();
It would be executed at the command prompt by
cscript mytest.js
In this case, I get the error "Automation server can't create object".
In C#, I have followed various suggestions, with the latest interface being:
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsDual), Guid("EAA4976A-45C3-4BC5-BC0B-E474F4C3C83B")]
public interface IComMyReaderInterface
{
void MyFunction();
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None), Guid("0D53A3E8-E51A-49C7-944E-E72A2064F9DD"), ProgId("MySimulator.World")]
[ComDefaultInterface(typeof(IComMyReaderInterface))]
public class MyReader : IComMyReaderInterface
{
public MyReader()
{
...
}
public void MyFunction()
{
...
}
...
}
Thanks and just let me know if more information is needed.
I'd assume the following. Your development environment is probably a 64-bit OS and your C# DLL project is probably configured to compile with Any CPU as Platform Target. Read on if that's the case.
Choose either x86 or x64 and compile the project. If you go with x86, then register your assembly with the 32-bit version of RegAsm.exe:
C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe /codebase assembly.dll
Then run your JavaScript test with the 32-bit version of cscript.exe:
C:\Windows\SysWOW64\cscript.exe mytest.js
If you go with x64, that would be:
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe /codebase assembly.dll
C:\Windows\System32\cscript.exe mytest.js
[EDITED] The following code has been verified to work using the above instructions.
C#:
using System;
using System.Runtime.InteropServices;
namespace ComLibrary
{
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsDual),
Guid("EAA4976A-45C3-4BC5-BC0B-E474F4C3C83B")]
public interface IComMyReaderInterface
{
void MyFunction();
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None),
Guid("0D53A3E8-E51A-49C7-944E-E72A2064F9DD"),
ProgId("MySimulator.World")]
[ComDefaultInterface(typeof(IComMyReaderInterface))]
public class MyReader : IComMyReaderInterface
{
public MyReader()
{
}
public void MyFunction()
{
Console.WriteLine("MyFunction called");
}
}
}
JavaScript (mytest.js):
var T = new ActiveXObject("MySimulator.World");
T.MyFunction();
Output:
MyFunction called
I've been working on a simple dll library that is com-accessible so that other softwares can use our library (from any managed or unmanaged language).
Creating a com-accessible dll is fairy easy:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace MyNamespace
{
//This interface defines purely the events. DotNetEventSender should implement this interface with the ComSourceInterfaces() attribute
//to become an Event Source.
[ComVisible(true), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface COMEventsInterface
{
//[DispId(1)]
// we don't have any events, but if needed, include them here
}
[ComVisible(true)]
public interface ICOM
{
//Methods
int Sum(int[] intsToSum)
}
//Identifies this interfaces that are exposed as COM event sources for the attributed class.
[ComSourceInterfaces(typeof(COMEventsInterface))]
//Tells the compiler not to generate an interface automatically and that we are implementing our own interface (IDotNetEventSender)
[ClassInterface(ClassInterfaceType.None)]
[ComVisible(true)]
public class COM : ICOM
{
// Methods
public int Sum(int[] intsToSum)
{
int sum = 0;
foreach ( int i in intsToSum )
{
sum += i;
}
return sum;
}
}
}
In debug mode in would now mark this project to register for com-interop via Project>Properties>Build>Register for com interop.
In release mode I have an installer which marks the primary output from my project as "vsdrpCOM".
And this works great, in most cases. But somehow on some machines (all American) this won't work. The com class gets registered but I constantly get the error: HRESULT 0x80131534, which is actually already descibed here on SO: Error when instantiating .NET/COM interop class via classic ASP
But realy, I don't see any solution here. I've checked for user rights, domain rights, ...
EDIT:
The constructor of my real class does this one thing:
(I've added the try catch because I found on SO that this is an error in the constructor...)
// Constructor
public COM()
{
try
{
// register itself with the application
MyApplication.COMObject = this;
}
catch(Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
It just registers itself to a static class' property COMObject:
private static COM _comObject;
public static COM COMObject
{
get
{
return _comObject;
}
set
{
_comObject = value;
}
}
Although, the COM class doesn't really need to register itself, i've done this for future use if I would like to trigger Events
Well, I happens to be that i have faulty declared a DateTime in one of my declarations of a static class... private DateTime myDateTime = Convert.ToDateTime("15/09/2013 12:00:00");
And ofcourse, on a EU system, this will work, but on an American (or even others) this gives an error because there is no 15th month...
This gets triggered even before the constructor of my com-accessible class and that's why the error couldn't be handled.
Dumb mistake, but proves that sometimes errors look very complex while they are very simple.
I thought I knew how to do this, but obviously not so I'd appreciate some help!
I can't get my dll to register so I can instantiate it in a VBS, or elsewhere.
I wrote the following sample class, checked "Make assembly COM Visible", checked "Register for COM Interop", then built it.
When I try to instantiate it from VBS I get the "Activex component can't create object" error.
This is the class code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Smurf
{
public class Pants
{
public string Explode(bool Loud)
{
string result;
if (Loud)
result = "BANG";
else
result = "pop";
return result;
}
}
}
...and this is the VBS:
Dim a
Set a = CreateObject("Smurf.Pants")
msgbox("ok")
What else do I need to do?
Thanks :)
[edit]
Forgot to mention, after the first failure I tried REGSVR32 and REGASM - no help!
[/edit]
Note that when I try REGSVR32, I get this message:
The Module "C:...\Smurf.dll" was loaded but the entry-point DllRegisterServer was not found.
Make sure that "C:...\Smurf.dll" is a valid DLL or OCX file and then try again.
How helpful is that??
This is the latest version of the code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace Smurf
{
[Guid("EAA4976A-45C3-4BC5-BC0B-E474F4C3C83F")]
public interface IPants
{
[DispId(1)]
string Explode(bool Loud);
}
[Guid("7BD20046-DF8C-44A6-8F6B-687FAA26FA71"),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IPantsEvents
{
string Explode(bool Loud);
}
[ComVisible(true)]
[Guid("0D53A3E8-E51A-49C7-944E-E72A2064F938"),
ClassInterface(ClassInterfaceType.None),
ComSourceInterfaces(typeof(IPantsEvents))]
public class Pants : IPants
{
public Pants() { }
[ComVisible(true)]
[ComRegisterFunction()]
public static void DllRegisterServer(string key) { }
[ComVisible(true)]
[ComUnregisterFunction()]
public static void DllUnregisterServer(string key) { }
[ComVisible(true)]
public string Explode(bool Loud)
{
string result;
if (Loud)
result = "BANG";
else
result = "pop";
return result;
}
}
}
There could be a few different things at play here. First, you'll want to use the regasm tool with the /codebase /tlb switch from an elevated command prompt (assuming Windows Vista, 7 or Windows Server 2008). Something like:
regasm "Path to Smurf.dll" /codebase /tlb
Once you have registered the dll using regasm you should be able to invoke it using VBS, VBA or VB6.
I was able to use early binding and late binding from VBA to call the Explode method. However, when I tried from VBScript I received the "ActiveX can't create object error as you did."
I'm running on Windows 7 64 bit, and I recalled that this can cause problems when compiling to 32 bit dlls and running them on 64 bit operating systems. On a whim, I fired up a command prompt and entered:
C:\Windows\SysWow64\CScript.exe "Path to VBScript"
The result was that the script ran correctly and displayed "Pop" on screen.
Here's the somewhat simplified C# code I used as well as the contents of the VBScript file.
namespace Smurf
{
[Guid("EAA4976A-45C3-4BC5-BC0B-E474F4C3C83F")]
public interface IPants
{
string Explode(bool Loud);
}
[Guid("7BD20046-DF8C-44A6-8F6B-687FAA26FA71"),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IPantsEvents
{
string Explode(bool Loud);
}
[ComVisible(true)]
[Guid("0D53A3E8-E51A-49C7-944E-E72A2064F938")]
[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(IPantsEvents))]
public class Pants : IPants
{
[ComVisible(true)]
public string Explode(bool Loud)
{
string result;
if (Loud)
result = "BANG";
else
result = "pop";
return result;
}
}
}
VBScript:
Dim x
Set x = CreateObject("Smurf.Pants")
MsgBox (x.Explode(False))
Set x = Nothing
I have a cpp project, a cpp cli project and a c# win forms project. I want to fire a method from my native cpp code and catch it in c# project. How can i do this?
There can be multiple approaches to answer this question, because the dependency requirement between those projects is important. I will try to answer for the most common (i guess) case: in which you already have a native C++ library and you want to use that library in a C# application. In that scenario the C# project depends on the native library project. In such a case you can utilize a gateway cli/c++ library to transform native c++ events to .NET events.
Here is a complete code sample, but before that, please note:
It may not be the shortest solution, but it works fine. Also it can provide more control on transforming native data to .net types.
I used this approach in VS 2005. I dont know if there is a better instrument in newer versions of VS for that specific interoperability purpose.
If your native event is triggered from a thread other than the GUI thread, then beware of that.
The Native Library:
#ifndef _NATIVE_CODE_H_
#define _NATIVE_CODE_H_
//NativeCode.h
//A simple native library which emits only one event.
#include <stdlib.h>
#include <iostream>
using namespace std;
#define NATIVELIBRARY_API __declspec(dllexport)
//An argument class to wrap event parameters
class NativeEventArgs{
public:
//a 32bit integer argument
//any other primitives can be here, just be careful about the byte size
int argInt32;
//null terminated ascii string
const char* argString;
//null terminated wide/unicode string
const wchar_t* argWString;
};
//A simple mechanism to fire an event from native code.
//Your library may have a DIFFERENT triggering mechanism (e.g. function pointers)
class INativeListener
{
public:
virtual void OnEvent(const NativeEventArgs& args)=0;
};
//The actual native library code, source of native events
class NATIVELIBRARY_API NativeCode
{
public:
NativeCode()
:theListener_(NULL)
{}
//Listener registration method
void registerListener(INativeListener* listener) {
theListener_ = listener;
}
//this is the very first source of the event
//native code emits the event via the listener mechanism
void eventSourceMethod() {
//... other stuff
//fire the native event to be catched
if(theListener_){
//prepare event parameters
NativeEventArgs args;
wstring wstr(L"A wide string");
string str("A regular string");
//build-up the argument object
args.argInt32 = 15;
args.argString = str.c_str();
args.argWString = wstr.c_str();
//fire the event using argument
theListener_->OnEvent( args );
}
}
private:
//native code uses a listener object to emit events
INativeListener* theListener_;
};
#endif
Gateway Library Sample:
//GatewayCode.h
//GatewayLibrary is the tricky part,
//Here we listen events from the native library
//and propagate them to .net/clr world
#ifndef _GATEWAY_CODE_H_
#define _GATEWAY_CODE_H_
#include "../NativeLibrary/NativeCode.h" //include native library
#include <vcclr.h> //required for gcroot
using namespace System;
using namespace System::Runtime::InteropServices;
namespace GatewayLibrary{
//.net equvelant of the argument class
public ref class DotNetEventArg{
internal:
//contructor takes native version of argument to transform
DotNetEventArg(const NativeEventArgs& args) {
//assign primitives naturally
argInt32 = args.argInt32;
//convert wide string to CLR string
argWString = Marshal::PtrToStringUni( IntPtr((void*)args.argWString) );
//convert 8-bit native string to CLR string
argString = Marshal::PtrToStringAnsi( IntPtr( (void*)args.argString) );
//see Marshal class for rich set of conversion methods (e.g. buffers)
}
private:
String^ argString;
String^ argWString;
Int32 argInt32;
public:
//define properties
property String^ ArgString {
String^ get() {
return argString;
}
}
property String^ ArgWString {
String^ get() {
return argWString;
}
}
property Int32 ArgInt32 {
Int32 get() {
return argInt32;
}
}
};
//EventGateway fires .net event when a native event happens.
//It is the actual gateway class between Native C++ and .NET world.
//In other words, It RECEIVES NATIVE events, TRANSFORMS/SENDS them into CLR.
public ref class EventGateway {
public:
//ctor, its implementation placed below
EventGateway();
//required to clean native objects
~EventGateway();
!EventGateway();
//the SENDER part
//.net event stuff defined here
delegate void DotNetEventHandler(DotNetEventArg^ arg);
event DotNetEventHandler^ OnEvent;
private:
//our native library code
//notice you can have pointers to native objects in ref classes.
NativeCode* nativeCode_;
//the required device to listen events from the native library
INativeListener* nativeListener_;
internal: //hide from .net assembly
//the RECEIVER part, called when a native event received
void OnNativeEvent(const NativeEventArgs& args){
//you can make necessary transformation between native types and .net types
//create .net argument using native argument
//required conversion is done by DotNetEventArg class
DotNetEventArg^ dotNetArgs = gcnew DotNetEventArg(args);
//fire .net event
OnEvent( dotNetArgs );
}
};
}
//A concrete listener class. we need this class to register native library events.
//Its our second gateway class which connects Native C++ and CLI/C++
//It basically gets events from NativeLibary and sends them to EventGateway
class NativeListenerImp : public INativeListener {
public:
NativeListenerImp(gcroot<GatewayLibrary::EventGateway^> gatewayObj ){
dotNetGateway_ = gatewayObj;
}
//this is the first place we know that a native event has happened
virtual void OnEvent(const NativeEventArgs& args) {
//inform the .net gateway which is responsible of transforming native event to .net event
dotNetGateway_->OnNativeEvent(args);
}
private:
//class member to trigger .net gateway.
//gcroot is required to declare a CLR type as a member of native class.
gcroot<GatewayLibrary::EventGateway^> dotNetGateway_;
};
////ctor and dtors of EventGateway class
GatewayLibrary::EventGateway::EventGateway()
{
nativeCode_ = new NativeCode();
//note; using 'this' in ctor is not a good practice
nativeListener_ = new NativeListenerImp(this);
//register native listener
nativeCode_->registerListener(nativeListener_);
}
GatewayLibrary::EventGateway::~EventGateway()
{
//call the non-deterministic destructor
this->!EventGateway();
}
GatewayLibrary::EventGateway::!EventGateway()
{
//clean up native objects
delete nativeCode_;
delete nativeListener_;
}
#endif
And the final application in C# (or in any other .net language):
//Program.cs
//C# the final evet consumer application
using System;
using System.Collections.Generic;
using System.Text;
using GatewayLibrary;
namespace SharpClient
{
class Program
{
static void Main(string[] args)
{
//create the gateway
EventGateway gateway = new EventGateway();
//listen on .net events using the gateway
gateway.OnEvent += new EventGateway.DotNetEventHandler(gateway_OnEvent);
}
static void gateway_OnEvent( DotNetEventArg args )
{
//use the argument class
Console.WriteLine("On Native Event");
Console.WriteLine(args.ArgInt32);
Console.WriteLine(args.ArgString);
Console.WriteLine(args.ArgWString);
}
}
}