I wrote C# class to COM but I could not use it from JavaScript. Example
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(ICommandsEvents))]
[ProgId("Scripting.Commands")]
public class Commands : ICommands
{
public Commands()
{
}
public int CreateChannel(string channelName)
{
return 0;
}
public int GetChannelID(string channelName)
{
return CreateChannel(channelName);
}
public event ChannelEventsHandler OnChannelEvents;
}
[ComVisible(false)]
public delegate void ChannelEventsHandler(string a);
[ComVisible(true)]
[Guid("E2147768-8BA8-400b-8602-A1FDC31E6AA5")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ICommands
{
[DispId(5)]
int CreateChannel(string channelName);
[DispId(6)]
int GetChannelID(string channelName);
}
[ComVisible(true)]
[Guid("22316373-A8DF-4ace-B48C-EA9953BD73FF")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ICommandsEvents
{
[DispId(1)]
void OnChannelEvents(string a);
}
and I checked "Register for COM interop" checkbox of project property.
when I want to Create this from JavaScript like this.
var a = ActiveXObject("Scripting.Commands");
I am getting "Automation Server Can't create object" exception. What is my wrong.
Thank you
There are a large number of reasons for this kind of error.
Ensure you have an assembly level GuidAttribute for the type library
First check the registry that interface, type library and coclass registration are correct.
Use Process Monitor to check the registration is being read correctly.
Attach a debugger to the process, so you can add breakpoints to your code.
Does a C# client (using COM, so you'll need to import tge typelib to create a PIA) work?
But I notice your class does not have a GuidAttribute, so coclass registration will have failed.
Make sure that your site is in 'Trusted Sites' on the client's machine.
Related
I have a class implemented in C# that I want to use from a native application. The C# class has a dependency described by an interface, which is exepected to be delivered by the code instantiating the class. I would like to realize this interface in the native application and pass it to the C# object via COM. Strongly simplified, the C# code looks like this:
[ComVisible(true)]
[Guid("910E8445-7A62-403F-BAEE-17AB0C169CA8")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IComWidget
{
void SetClient(IComWidgetClient client);
void DoStuff();
}
[ComVisible(true)]
[Guid("850F3EBB-CD18-4E16-881F-50B50DD5AEB0")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IComWidgetClient
{
int GetValue();
}
[ComVisible(true)]
[Guid("86B9EC33-6CDF-438F-9A67-57D009723027")]
[ClassInterface(ClassInterfaceType.None)]
public class ComWidget : IComWidget
{
private IComWidgetClient m_Client;
public void DoStuff()
{
var i = m_Client.GetValue();
Debug.WriteLine("value was {0}", i);
}
public void SetClient(IComWidgetClient client)
{
m_Client = client;
}
}
The native application loads the COM library as a side-by-side assembly with a proper manifest, and implements the IComWidgetClient interface. It seems to work, but when running automated tests on the system several tests fail with an unhandled ExecutionEngineExecption. The way it fails (the test process is aborted) smells like some kind of corruption during garbage collection.
I think I may be able to write a managed c++ unit test that mimics the steps that lead to the error. At least it fails the same way. The test looks like this:
[TestMethod]
void TestStuff()
{
IComWidgetPtr sut = NULL;
NativeClient* client = NULL;
try
{
sut = IComWidgetPtr(__uuidof(ComWidget));
client = new NativeClient();
IComWidgetClient* pvObject;
client->QueryInterface(IID_IComWidgetClient, (void**)&pvObject);
sut->SetClient(pvObject);
sut->Release();
GC::Collect();
GC::WaitForPendingFinalizers();
Assert::IsTrue(true); // If we get this far, everything went OK...
}
finally
{
sut = NULL;
delete client;
client = NULL;
}
};
Where NativeClient is a simple native object implementing IComWidgetClient
public class NativeClient: IComWidgetClient
{
...
}
What goes wrong? Is what I am trying to do at all possible?
Full source code can be found here:
https://drive.google.com/file/d/0B-D57qCpESa5MnpZUXZRN2pyNnc/view?usp=sharing
We have a stripped down Connect class which instantiates the addin's core from an other assembly. Our architectural design is to keep the UI and business logic separated from the loading module (= Connect class), but the restrictions of a shared Addin make us troubles.
what we did in Connect.cs:
[GuidAttribute("XYZ"), ProgId("XYZ")]
public class Connect : Object, IDTExtensibility2, ICustomQueryInterface
{
...
CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid,
out IntPtr ppv)
{
if (iid.Equals(new Guid("000C0396-0000-0000-C000-000000000046")))
{
ppv = Marshal.GetComInterfaceForObject(
UIObject,
typeof(IRibbonExtensibility),
CustomQueryInterfaceMode.Ignore);
return CustomQueryInterfaceResult.Handled;
}
ppv = IntPtr.Zero;
return CustomQueryInterfaceResult.NotHandled;
}
}
how the RibbonUI looks like:
public interface IOfficeFluentUI : IRibbonExtensibility
{
...
}
public class OfficeFluentUI : OfficeUIBase, IOfficeFluentUI
{
...
public string GetCustomUI(string RibbonID)
{
...
}
}
The GetInterface function works, it gets the com interface, but unfortunately the GetCustomUI function gets NEVER called.
What did we wrong? Thank You very much for any help.
[edit]
We already know the "Managed COM Aggregation" and "ICustomQueryInterface" articles. Unfortunately, they did not really help.
I create the following class library in C#:
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
//[Guid(<Generate GUID here>)]
public interface _None1
{
int retval { get; }
}
[ClassInterface(ClassInterfaceType.None)]
//[Guid(<Generate GUID here>)]
[ProgId("Lotr.Test")]
public class None : _None1
{
public int retval
{
get
{ return 1; }
}
}
Then I compile it using "Register for COM Interop" and "Make Assembly COM-visible" options.
When I try to access it using Excel 2007 VBA on my machine, works fine. However, if I take the .dll and .tlb files to another machine, and then use regasm to register it, the registration happens fine, I am able to reference this in Excel VBA via the tlb, the intellisense works like clockwork, but while execution, VBA runtime gives the following error:
"Runtime error:-2147024894 (80070002)"
"Automation Error: The system cannot find the file specified."
You probably need to use /codebase regasm key:
regasm your_assembly_name.dll /codebase
I've some things diferents in my COM assemblies:
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
//[Guid(<Generate GUID here>)]
public interface _None1
{
[DispId(1)]
int retval { get; }
}
[ClassInterface(ClassInterfaceType.None)]
//[Guid(<Generate GUID here>)]
[ProgId("Lotr.Test")]
public class None : _None1
{
[STAThread]
public int retval
{
get
{ return 1; }
}
}
and I don't know if in a COM interface you can use getter and setter operations... maybe someone knows and help us :)
Here is a code sample
[Guid("159463FB-A87A-4BBB-BCA1-064CD84495FB")]
public interface ISettingsFactory
{
ISettings GetSettings(string userName);
}
[Guid("FD11B979-0ED1-41fb-8EB0-1234512345D0")]
public interface ISettings
{
string PrivateKey {get;}
}
[Guid("782937826-705F-4be2-1234-A748332D6D1")]
[ClassInterface(ClassInterfaceType.None)]
public class SettingsFactory : ISettingsFactory
{
public ISettings GetSettings(string userName)
{
return new Settings(userName);
}
}
[Guid("8BDC1F18-48FD-4a49-8DF3-D81C6321657B")]
[ClassInterface(ClassInterfaceType.None)]
public class Settings : ISettings
{
private readonly PrivateData privateData;
public Settings(string userName)
{
privateData= PrivateData.Load(userName);
}
public string PrivateKey
{
get { return privateData.Key; }
}
}
The problem is when creating SettingsFactory COM class from VB6 code and calling method GetSettings(userName)
settings = factory.GetSettings(userName);
key = settings.PrivateKey //<--- Exception occurs saying "Read write from protected memory is not allowed" or something like this.
The problem disappears when in GetSettings method I save Settings instance in field of SettingsFactory, so that GC doesn't collect it. Does anyone know why is this happening? I mean why GC collects objects that are exposed to COM? Isn't RCW increases the ref number on the Settings object after GetSettings get called?
I think the problems is that privateData.Key is not being marshalled correctly. I can't see the definition for the type of Key but my guess is it is a ptr to some kind of data and tha this data isn't being marshalled correctly. If thats the case you probably need to use one of the Marhal.PtrToXXX fumctions.
A great place to start if you want to learn more about using COM objects from .net are the MSDN articles on Runtime Callable Wrappers
Really pulling my hair out with this one...
I have a C# project with an interface defined as:
/* Externally Accessible API */
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ISerial
{
[DispId(1)]
bool Startup();
[DispId(2)]
bool Shutdown();
[DispId(3)]
bool UserInput_FloorButton(int floor_number);
[DispId(4)]
bool Initialize();
}
/* Externally Accesssible Event API */
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ISerialEvent
{
[DispId(5)]
void DataEvent();
}
[ComSourceInterfaces(typeof(ISerialEvent), typeof(ISerial))]
[ClassInterface(ClassInterfaceType.None)]
public class SerialIface : ISerial
{
public delegate void DataEvent();
public event DataEvent dEvent;
public bool Initialize()
{
//testing the event callback
if (dEvent != null)
{
dEvent();
}
}
...
}
And the VB6 code looks like:
Private WithEvents objSerial As SerialIface
Private Sub objSerial_DataEvent()
'do something happy'
End Sub
Public Sub Class_Initialize()
Set objSerial = New SerialIface '<---this is the line that fails'
Call objSerial.Initialize '<--Initialize would trigger DataEvent, if it got this far'
End Sub
Well, the normal API-type functions appear to be working (if I declare objSerial without the WithEvents keyword), but I can't for the life of me get the "DataEvent" to work. It fails with the "object or class does not support the set of events" message.
I'd originally lumped the two interfaces together, but then C# complained that DataEvent was not defined in the class. The way it is currently, I am able to view all of the APIs and the one event perfectly in the VB6 object browser -- everything looks like it's there... I just can't make it actually work!
I'm sure I'm missing something obvious or doing something stupid -- but I'm new to the whole interop business, so it's just escaping me entirely.
Help!
Look at this article here.
Specifically it looks like you missing a declaration that looks something like this.
[Guid("9E5E5FB2-219D-4ee7-AB27-E4DBED8E123E"),
ClassInterface(ClassInterfaceType.None),
ComSourceInterfaces(typeof(DBCOM_Events))]
public class DBCOM_Class : DBCOM_Interface
{
You have this part
// // Events interface Database_COMObjectEvents
[Guid("47C976E0-C208-4740-AC42-41212D3C34F0"),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface DBCOM_Events
{
}
But without the second the vtable and typelib of the COM object doesn't have the Event Maps needed to work with VB6 (or other COM Consumers).
You can use the Google search terms "com event" c# and get a bunch of other good results.
I was defining the interface using the delegate instead of the event:
/* Externally Accesssible Event API */
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ISerialEvent
{
[DispId(5)]
void DataEvent();
}
should be
/* Externally Accesssible Event API */
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ISerialEvent
{
[DispId(5)]
void dEvent();
}