C# com interopt failure on american machines - c#

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.

Related

C# Constructors Ambiguity with explicit call - Error CS0012

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...

C# IConnectionPointContainer.FindConnectionPoint throws exception

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.

Getting C# ActiveX/COM Library to Work through JScript

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

Expose dll for COM Interop

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

Using C++ COM interface in C# for both client and server

I need to make a piece of C# code interact through COM with all kinds of implementations.
To make it easeier for users of that integration, I included the interacted interfaces in IDL (as part of a relevant existing DLL, but without coclass or implementation), then got that into my C# code by running Tlbimp to create the types definition.
I implemented my C#, creating COM objects based on Windows registry info and casting the object into the interface I need.
I then created a C# implementation of the interface in a seperate project and registered it.
The main program creates the testing COM object correctly but fails to cast it into the interface (gets a null object when using C# 'as', gets an InvalidCastException of explicit cast).
Can someone suggest why the interface is not identified as implemented by the testing object?
This is the interface defition in IDL (compiled in C++ in VS 2005):
[
object,
uuid(B60C546F-EE91-48a2-A352-CFC36E613CB7),
dual,
nonextensible,
helpstring("IScriptGenerator Interface"),
pointer_default(unique)
]
interface IScriptGenerator : IDispatch{
[helpstring("Init the Script generator")]
HRESULT Init();
[helpstring("General purpose error reporting")]
HRESULT GetLastError([out] BSTR *Error);
};
This is the stub created for C# by Tlbimp:
[TypeLibType(4288)]
[Guid("B60C546F-EE91-48A2-A352-CFC36E613CB7")]
public interface IScriptGenerator
{
[DispId(1610743813)]
void GetLastError(out string Error);
[DispId(1610743808)]
void Init();
}
This is part of the main C# code, creating a COM object by its ProgID and casting it to the IScriptGenerator interface:
public ScriptGenerator(string GUID)
{
Type comType = Type.GetTypeFromProgID(GUID);
object comObj = null;
if (comType != null)
{
try
{
comObj = Activator.CreateInstance(comType);
}
catch (Exception ex)
{
Debug.Fail("Cannot create the script generator COM object due to the following exception: " + ex, ex.Message + "\n" + ex.StackTrace);
throw ex;
}
}
else
throw new ArgumentException("The GUID does not match a registetred COM object", "GUID");
m_internalGenerator = comObj as IScriptGenerator;
if (m_internalGenerator == null)
{
Debug.Fail("The script generator doesn't support the required interface - IScriptGenerator");
throw new InvalidCastException("The script generator with the GUID " + GUID + " doesn't support the required interface - IScriptGenerator");
}
}
And this is the implementing C# code, to test it's working (and it's not):
[Guid("EB46E31F-0961-4179-8A56-3895DDF2884E"),
ProgId("ScriptGeneratorExample.ScriptGenerator"),
ClassInterface(ClassInterfaceType.None),
ComSourceInterfaces(typeof(SOAAPIOLELib.IScriptGeneratorCallback))]
public class ScriptGenerator : IScriptGenerator
{
public void GetLastError(out string Error)
{
throw new NotImplementedException();
}
public void Init()
{
// nothing to do
}
}
Again - thanks for the suggestions.
I was able to finally resolve the issue on my own. I tried the above suggestions and didn't made any progress. Then I changed the namespace of the interop in the 'testing' code - it varied from the one in the main code because of different argument use when using Tlbimp. This solved the problem.
Here's my guess to why: .Net creates the COM object, but when it detects this is actually a .Net object, it bypass the COM layer and communicates directly. In which case, queryInterface (with the interface GUID) is not used and the interface do differ because of different C# namespaces.
This means that in order to supprot integration with .Net code, I will need to publish my original interop assembly aside the IDL.
Thanks,
Inbar
I think you need this on the interface
[InterfaceType(ComInterfaceType.InterfaceIsDual)]

Categories

Resources