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
Related
I need to pass a delegate function between .NET 6 32.bit COM server and .NET 6 64.bit client to communicate.
I wrote a sample with a 32-bit server, implemented as a C# class in a class library project, hosted by Windows system surrogate and a 64-bit client that calls the server.
Class code:
namespace OutOfProcCOM;
[ComVisible(true)]
[Guid(Contract.Constants.ServerInterface)] //F586D6F4-AF37-441E-80A6-3D33D977882D
public interface IServer
{
double Test();
void TestEvent([MarshalAs(UnmanagedType.FunctionPtr)] ChangeDelegate d);
}
[ComVisible(true)]
[Guid(Contract.Constants.ServerInterface)] //F586D6F4-AF37-441E-80A6-3D33D977882D
public delegate bool ChangeDelegate();
[ComVisible(true)]
[Guid(Contract.Constants.ServerClass)] //"AF080472-F173-4D9D-8BE7-435776617347"
public sealed class DllServer : IServer
{
public event OnMyEventDelegate OnMyEvent;
public double Test()
{
return 10;
}
public void TestEvent([MarshalAs(UnmanagedType.FunctionPtr)] ChangeDelegate d)
{
d?.Invoke();
}
}
This is how I register it:
regsvr32.exe
"DllServer.comhost.dll"
Registry entry
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\Wow6432Node\AppID\{AF080472-F173-4D9D-8BE7-435776617347}]
"DllSurrogate"=""
[HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{AF080472-F173-4D9D-8BE7-435776617347}]
"AppID"="{AF080472-F173-4D9D-8BE7-435776617347}"
And here is the client:
var type = Type.GetTypeFromProgID("OutOfProcCOM.DllServer");
var obj = (IServer)Activator.CreateInstance(type);
var x = obj.Test();
obj.TestEvent(() => {
Console.WriteLine("Hello!");
});
Execution of obj.Test() is working.
Execution of obj.TestEvent(...) throws a System.AccessViolationException:
System.AccessViolationException: 'Attempted to read or write protected memory. This is often an indication that other memory is corrupt.'
Is there someone who can help me? I am going crazy migrating a COM application to .NET Core!
Thank you!
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.
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
OK, I will try to do my best, but I think that my English is still too bad when it comes to complex subjects/phrases.
I have to build a class (as for now it's not static) that retrieves the information about the external application (software available on customers PC).
The Linux class was really easy to code, but now I have to implement it on Windows.
Basically I've encountered some difficulties with x64 registry reading from x86 version of my application: you can do it only by using [DllImport("advapi32.dll")].
From the beginning I knew that software retirement is strongly depending on target OS, so I've created 4 classes:
public abstract ExternalApplications that defines some basic methods and it is derived by ExternalApplicationsWin, ExternalApplicationsMac and ExternalApplicationsUnix.
Inmy application I create an instance of ExternalApplications and assign it by switching the operating system type:
ExternalApplications ex = null;
switch (Environment.OSVersion.Platform)
{
case PlatformID.Win32NT:
goto default;
case PlatformID.MacOSX:
_ex = new ExternalApplicationsMac(); break;
case PlatformID.Unix:
_ex = new ExternalApplicationsUnix(); break;
default:
_ex = new ExternalApplicationsWin(); break;
}
I this way the method call will be dispatched automatically to the right ExternalApplicationsXXX type.
Now by introducing [DllImport("advapi32.dll")] I'm obligated to:
change the class structure (to static extern)
it will probably brake Mono compilation on Unix/Mac OS X compilation (with
DllNotFoundException)
There are several options:
(1) I can create 3 parallel projects (.cproj) with the same target assembly name for Linux, Mac and Windows. The projects will contain the same classes and methods so I can reference it inside my application without any problem.
This option sucks, it lacks of automation: everything is done manually.
(2) I can accomplish a preventive "DllImport" code exclusion by using C# directives like
#if !MONO
...
#endif
I'm sure that there are many other methods like DLLMAP and others.
I think that more experienced programmers will tell me what is the most elegant/reliable solution.
EDIT 1:
I've just discovered that x64 registry hive reading from an x86 application is possible, however only in .NET 4.0 and higher (according to my source, still need to test).
string registryBranchPath = "SOFTWARE\MySoft";
RegistryKey regKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
regKey = regKey.OpenSubKey(registryBranchPath);
This solves only one part of my question, the other part (regarding the possibility to use DllImport in Windows specific non-static classes) is still to be confirmed / tested.
Your class doesn't need to be static, only the imported external method is marked as such. Also, the imported method should be marked private, as nothing except the importing class should call it directly.
You don't need any fancy compilation tricks / code splitting, the DllImport is not checked until it's actually invoked.
An example from MSDN documentation for DllImport:
using System;
using System.Runtime.InteropServices;
class Example
{
// Use DllImport to import the Win32 MessageBox function.
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
private static extern int MessageBox(IntPtr hWnd, String text,
String caption, uint type);
public void MessageBox()
{
// Call the MessageBox function using platform invoke.
MessageBox(new IntPtr(0), "Hello World!", "Hello Dialog", 0);
}
}
So the final solution for me will look like this simplified snippet:
public static class UsageClass
{
private static ExternalApplications _extApps;
public static void Initialize()
{
_extApps = null;
switch (Environment.OSVersion.Platform)
{
case PlatformID.Win32NT:
goto default;
case PlatformID.MacOSX:
_extApps = new ExternalApplicationsMac(); break;
case PlatformID.Unix:
_extApps = new ExternalApplicationsUnix(); break;
default:
_extApps = new ExternalApplicationsWin(); break;
}
}
internal abstract class ExternalApplications
{
public abstract string[] DoSomething();
}
internal class ExternalApplicationsWin : ExternalApplications
{
public abstract void DoSomething()
{
// Windows code
RegistryKey regKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine,
(Environment.Is64BitOperatingSystem) ? RegistryView.Registry64 : RegistryView.Registry32).OpenSubKey("SOFTWARE\MySoft");
...
}
}
internal class ExternalApplicationsUnix : ExternalApplications
{
public abstract void DoSomething()
{
// Unix code
}
}
internal class ExternalApplicationsMac : ExternalApplications
{
public abstract void DoSomething()
{
// Mac code
}
}
}