I am trying to get COM to start my out-of-process .NET COM server. It works if the server process is compiled with x64, but if I use AnyCPU (which is what I want) then it hangs for a while and eventually fails with 0x80080005 (CO_E_SERVER_EXEC_FAILURE). How can I get this to work?
I am running on a 64-bit machine: Windows 7 with Visual Studio 2008 SP1.
I can see in Task Manager that it does start my server. So I guess the problem is in the communications between COM and the server (class registration).
My test client application is written in C#, but it doesn't matter whether it is compiled for x86 or x64. The problem also occurs with something written in 32-bit C++.
If I rebuild the server using x64 and run it, and then rebuild back as AnyCPU, then COM can start it. A reboot will take me back to the original situation. Perhaps COM doesn't know in advance what bitness is going to be used, and a previous execution helps.
I found Andy McMullen's blog post and tried passing CLSCTX_ACTIVATE_64_BIT_SERVER to CoCreateInstance(), but that triggers a failure earlier: 0x80040154 (REGDB_E_CLASSNOTREG). Am I doing something wrong in my COM registration? You can see below that it is very simple. Registration occurs when running in 64 bits, and the problem occurs when the client is 64 bits, so Wow6432Node should not be involved.
Another chap has had a similar problem, but the MSFT answer is confusing. He seems to be suggesting it can only work via DCOM (see link) or COM+. I suspect either will be an awful lot of work, and substantially worse than distributing my .exe built as x64 and x86.
You may be wondering why I am implementing IPersistFile. It is because my real problem is to get BindMoniker() working from a 32-bit C++ program to my AnyCPU .Net program. I have reduced my problem to the simpler example presented here.
Here is the client code:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
[DllImport("ole32.dll", ExactSpelling = true, PreserveSig = false)]
[return: MarshalAs(UnmanagedType.Interface)]
static extern object CoCreateInstance(
[In, MarshalAs(UnmanagedType.LPStruct)] Guid rclsid,
[MarshalAs(UnmanagedType.IUnknown)] object pUnkOuter,
CLSCTX dwClsContext,
[In, MarshalAs(UnmanagedType.LPStruct)] Guid riid);
[Flags]
enum CLSCTX : uint
{
CLSCTX_LOCAL_SERVER = 0x4,
CLSCTX_ACTIVATE_64_BIT_SERVER = 0x80000,
}
private void Form1_Load(object sender, EventArgs e)
{
IPersistFile pf = (IPersistFile)CoCreateInstance(
new Guid("1984D314-FC8D-44bc-9146-8A13500666A6"),
null,
CLSCTX.CLSCTX_LOCAL_SERVER,
new Guid("0000010b-0000-0000-C000-000000000046")); // IPersistFile
pf.Load("c:\\bozo", 0);
}
}
and here is the server:
static class Program
{
[STAThread]
static void Main()
{
if (Environment.CommandLine.Contains("/reg")) {
RegistryKey cls = Registry.LocalMachine.CreateSubKey(String.Format(
"SOFTWARE\\Classes\\CLSID\\{0}", PersistFile.ClassID.ToString("B")));
cls.SetValue("InprocHandler32", "Ole32.dll");
RegistryKey ls32 = cls.CreateSubKey("LocalServer32");
ls32.SetValue(null, '"' + Application.ExecutablePath + '"');
ls32.SetValue("ServerExecutable", Application.ExecutablePath);
}
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
RegistrationServices reg = new RegistrationServices();
reg.RegisterTypeForComClients(
typeof(PersistFile),
RegistrationClassContext.LocalServer,
RegistrationConnectionType.MultipleUse);
Application.Run(new Form1());
}
}
[ComVisible(true),
Guid("1984D314-FC8D-44bc-9146-8A13500666A6"),
ClassInterface(ClassInterfaceType.None)]
public class PersistFile : IPersistFile
{
public static Guid ClassID
{
get
{
GuidAttribute a = (GuidAttribute)typeof(PersistFile).GetCustomAttributes(typeof(GuidAttribute), false)[0];
return new Guid(a.Value);
}
}
#region IPersistFile
public void GetClassID(out Guid pClassID)
{
MessageBox.Show("GetClassID");
pClassID = ClassID;
}
public int IsDirty()
{
MessageBox.Show("IsDirty");
return 1;
}
public void Load(string pszFileName, int dwMode)
{
MessageBox.Show(String.Format("Load {0}", pszFileName));
}
public void Save(string pszFileName, bool fRemember)
{
MessageBox.Show("Save");
throw new NotImplementedException();
}
public void SaveCompleted(string pszFileName)
{
MessageBox.Show("SaveCompleted");
throw new NotImplementedException();
}
public void GetCurFile(out string ppszFileName)
{
MessageBox.Show("GetCurFile");
throw new NotImplementedException();
}
#endregion
}
I guess the problem is at run-time. I have created a COM Server which registers using a C++ library (registration is performed flawlessly). I have run into problems when switching to AnyCPU from .NET (CS).
The architecture:
C++ library interfacing COM (built on both x64 and x86 platforms)
.NET library wrapper (CS) (correctly instantiates the required x64/x86 C++ library)
.NET application (CS) - COM client or COM server
Ugly things happen when registering the .NET application built as "AnyCPU". Once the COM Client invokes the COM Server through DCOM, the server application starts but the client receives error that the COM Server could not be started.
I went some steps further, analyzed registration data with procmon and other tools and I reached the same conclusion:
x86 registers the classes in CLASSES\Wow6432Node
x64 and AnyCPU register the classes in CLASSES (on a x64 windows machine, exactly the same keys; I bet that x86 and AnyCPU would register the same on an x86 machine)
Now, I did some more experiments: the x86/x64/AnyCPU COM Client can connect with no problems to any x86/x64 COM Server but it cannot connect anyhow to an AnyCPU COM Server...
I then performed the following test cases:
Have the x86 COM Server register, replace the executable with the AnyCPU COM Server: COM Client was starting the x86 COM Server, but no communication... it was starting the server over and over again..
Have the x64 COM Server register, replace the executable with the AnyCPU COM Server: COM Client was starting the x64 COM Server, but no communication... it was starting the server over and over again..
Have the AnyCPU COM Server register, replace the executable with the x86 COM Server: COM Client was able to successfully start the and connect to the x86 COM Server.
Have the AnyCPU COM Server register, replace the executable with the x64 COM Server: COM Client was able to successfully start the and connect to the x64 COM Server.
Have the x86 COM Server register, replace the executable with the x64 COM Server: COM Client was able to successfully start the and connect to the x64 COM Server.
Have the x64 COM Server register, replace the executable with the x86 COM Server: COM Client was able to successfully start the and connect to the x86 COM Server.
Where the hell is the communication problem? This is very odd... None of the presented solutions (CLSCTX_ACTIVATE_64_BIT_SERVER, PreferredServerBitness or corflags) helped.
Anyone else did some progress on this matter? Should we contact Microsoft?
Try to use the RegistrationServices class to register your com assembly. It will also choose the correct registry pathes and do some other things.
Example:
Assembly currentAssembly = Assembly.GetExecutingAssembly();
System.Runtime.InteropServices.RegistrationServices regAsm = new System.Runtime.InteropServices.RegistrationServices();
bool isRegistered = regAsm.RegisterAssembly(currentAssembly, System.Runtime.InteropServices.AssemblyRegistrationFlags.SetCodeBase);
Also I think, that .NET client assemblies have some trouble according to .NET com servers, but I can't find any resource for it...
Hope, it will help...
Related
We have a software product that is currently released that is a .NET Framework 4.7.2 application (the "legacy" app). The legacy client-server implementation is built on System.Runtime.Remoting, which is not supported in .NET 5 and later, so the .NET 5 implementation is gRPC.
It is necessary to instantiate each of the two COM servers in turn because the legacy and the .NET 5 COM servers can only connect to the comm (not COM) server application that implements the same communications framework, which are System.Runtime.Remoting and gRPC, respectively.
The COM servers are used by third party applications to interface with the comm server application, so I am currently working on creating a static class that returns the interface from the COM server that can connect to the currently running instance of the comm server.
I have a .NET 5 WPF implementation of the product almost complete, but I've hit a roadblock in that, I am unable to register the .NET COM server.
I found these two articles:
Exposing .NET Core Components to COM
GitHub Issue
I have now been able to:
Create a Type Library
I found a comment from #SimonMourier suggesting copying the .NET 5 COM server code into a .NET Framework project and use RegAsm to export the type library to be used in the .NET 5 project. The type library was added to the .NET 5 COM server project folder and "" was added to an ItemGroup in the .csproj file per the first referenced article.
Register the .NET 5 COM server
This required using the "dotnet publish -r win-x64 -c Debug" command in the project folder from the Visual Studio Developer Command Line. I was then able to use regsvr32 to register the WinCalRemoting.comhost.dll in the "bin\Debug\net5.0\win-x64\publish" project directory.
Create an Instance of the COM Class
After registering the COM server, I am now able to create an instance of the COM class, but haven't been successful at getting the interface from it:
public static IWinCalClient LoadCompatibleRemotingClient(bool useClientEventWindow, string serverName, int serverPort, bool connectToServer = true)
{
UseClientEventWindow = useClientEventWindow;
WinCalServerName = serverName;
WinCalServerPort = serverPort;
ClassIdList = new Guid[]
{
LegacyWinCalClientClsId, // CAN'T GET INTERFACE FROM THIS COM SERVER
//WinCal5ClientClsId // THE .NET 5 COM SERVER WORKS
};
if (RemotingClassObject != null)
{
UnloadClient();
}
foreach (Guid clsId in ClassIdList)
{
try
{
RemotingClassObject = Activator.CreateInstance(Type.GetTypeFromCLSID(clsId, true));
}
catch (Exception e)
{
continue;
}
if (RemotingClassObject != null)
{
RemotingInterface = (IWinCalClient)RemotingClassObject;
if (RemotingInterface == null)
{
UnloadClient();
continue;
}
if (CanClientConnect(_RemotingInterface, connectToServer))
{
break;
}
}
if (Marshal.IsComObject(RemotingClassObject))
{
Marshal.FinalReleaseComObject(RemotingClassObject);
}
RemotingClassObject = null;
}
return RemotingInterface;
}
Update on the exception
After correcting the "bitness" of the test COM Client application that #SimonMourier clued me to, I am able to get the interface from the .NET 5 COM server. I have updated the code from the method.
HOWEVER, I'm now struggling with getting the interface from the .NET Framework COM server in the same way I get it from the .NET 5 COM server. I successfully register it using RegAsm.exe, but I get the following exception:
System.InvalidCastException: 'Unable to cast object of type 'CMI.WinCalRemoting.cWinCalClient' to type 'CMI.WinCalRemoting.IWinCalClient'.'.
I've done an exhaustive search to try to find out how to fix the .NET Framework COM project so that it can be used in the same way that the .NET 5 COM server is used so that it doesn't matter whether the COM client is a .NET Framework or a .NET Core assembly.
I added a .NET Framework COM server project to the shared directory below to replicate what I'm seeing. With the .NET Framework COM server.
I also switched the test application to be 32-bit to replicate how our sister application will be using the COM servers.
All of the projects are located here:
.NET 5 COM Interop
Unable to Add .NET Framework COM Type Library Reference
For a .NET Framework client assembly, I've attempted to add a reference to the .NET Framework COM server that was registered with regasm.exe, but that fails with the following message:
Creating an Excel addin using an SDK from a 3rd party. The SDK contains a native code DLL (in both 32 and 64 bit versions). My addin code is in C# and it appears that addins only run if compiled under "AnyCPU" option.
When I attempt to run the app I get "An attempt was made to load a program with an incorrect format. (Exception from HRESULT: 0x8007000B)" which usually means a 64/32 mismatch, and the error is from attempting to load the 3rd party native code DLL.
My question is: are my assumptions correct about AnyCPU and is there a way to run native code DLL's from an addin compiled under AnyCPU? Thanks!
before .net 4.5 the AnyCPU depends on your current compile machine.
if your machine is 32bit CPU, AnyCPU will compile your program as 32bit program. if it's 64bit CPU, i t will compile as 64bits.
after .net 4.5, the AnyCPU changed:
If the process runs on a 32-bit Windows system, it runs as a 32-bit
process. IL is compiled to x86 machine code.
If the process runs on
a 64-bit Windows system, it runs as a 32-bit process. IL is compiled
to x86 machine code.
If the process runs on an ARM Windows system,
it runs as a 32-bit process. IL is compiled to ARM machine code.
If you want your program can run both 32bit and 64bit cpu, you should link your native code DLL with 32 bit version, otherwise you need to compile them in separate CPUs
Yes it is possible, you can create some wrappers functions that redirect the call to the native dll depending on the current architecture , example :
[DllImport("bin32\\Native86Dll", EntryPoint = "MyFunc", CharSet = CharSet.Ansi, ExactSpelling = true)]
public static extern int MyFunc_32(string sCommand);
[DllImport("bin64\\Native64Dll", EntryPoint = "MyFunc", CharSet = CharSet.Ansi, ExactSpelling = true)]
public static extern int MyFunc_64(string sCommand);
public static int MyFunc(string sCommand )
{
return System.Environment.Is64BitProcess ? MyFunc_64(sCommand) : MyFunc_32(sCommand);
}
Is there a way I can compile an application which calls the DriverPackagePreinstall() (using Pinvoke) in such a way that it can work on 64-bit devices (Windows 7) even though it's targeted to 32bit?
The reason being it will be run as part of an installer of a much bigger application (using Windows Installer Project), which will target 32 bit but must run on a 64 bit platform also.
Here is my code:
using System;
using System.Linq;
using System.Runtime.InteropServices;
namespace MyDriver
{
class Program
{
static void Main(string[] args)
{
if (args.Count() == 0)
{
Console.WriteLine("Please specify filename!");
return;
}
int result= DriverPackagePreinstall(args[0], 0);
if (result == 0)
{
Console.WriteLine("Success");
} else {
Console.WriteLine("Error: 0x{0}", result.ToString("X8"));
}
}
[DllImport("DIFxAPI.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern Int32 DriverPackagePreinstall(
[MarshalAs(UnmanagedType.LPTStr)] string DriverPackageInfPath,
Int32 Flags);
}
}
}
If I build this targeting x86, and try to run on a 64-bit machine, I get error E0000235 (ERROR_IN_WOW64). This error goes away if I build targeting x64.
Now I don't mind compiling it twice and letting the installer decide which to install based on the platform it's being installed to. However, if I try to build the installer while including the 64-bit version I get the error,
263 File 'MyDriver.x64.exe' targeting 'AMD64' is not compatible with the project's target platform 'x86'
Alternatively a way of getting the Installer to overlook this error at build time (and run it anyway when the project is installing) would be good.
I worked around this by adding both the 64-bit and 32-bit versions of the application to a self-extracting zip-file together with a batch file which decides which one to execute.
The self-extractor is a 32-bit exe so as long no one tells Visual Studio that it contains a 64-bit one, it works.
I want to use x86 dll in in my x64 C# application!
On forum I read that com object will help me! This is my ComServerSample. It is compile like x86.
[ComVisible(true)]
public class MyComServer : IMyComSample
{
private dahuaIp.fDisConnect dissconn;
public void con(int lLoginID, StringBuilder pchDVRIP, int nDVRPort, int dwUser)
{
}
public string GetString()
{
dissconn = new dahuaIp.fDisConnect(con);
var zdsc = dahuaIp.CLIENT_Init(dissconn, 0);
return zdsc.ToString();
}
}
[ComVisible(true), Guid("DBE0E8C4-2222-41f3-B6A4-4E2F353D3D05")]
public interface IMyComSample
{
string GetString();
}
And this is test application for using this com server
Type CSI = Type.GetTypeFromProgID("ComServerSample.MyComServer");
var COMobj = Activator.CreateInstance(CSI);
MethodInfo method = CSI.GetMethod("GetString", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
var sdfc = method.Invoke(COMobj, null);
When test app is x86!This two applications work fine!
But when test app is x64 there is an eroor :
Failed to get the COM class factory for component with CLSID
{7B9F9A71-8E1B-3470-8A79-EEB4DA9B25A4} due to the following error:
80040154 Class not registered (Exception from HRESULT: 0x80040154
(REGDB_E_CLASSNOTREG)).
How solve problem? I need to use my x86 dll in x64 application!
You are getting the "Class not registered" error because you used the wrong version of Regasm.exe to get the class library registered. Or because you let VS register it. You must use the 64-bit version of Regasm, it is present in the c:\windows\microsoft.net\framework64 subdirectory.
This however doesn't solve your real problem, COM can only bridge the bitness gap for an out-of-process COM server. .NET does not directly support creating them, only in-process servers are easy. Which is what you got, it will still fail because an in-process server must match the bitness of the EXE.
Getting an out-of-process COM server in .NET requires using COM+ and deriving from the ServicedComponent class. A how-to article that shows step-by-step instructions is here.
Frankly, you are not ahead by doing this. You are much better off by using .NET Remoting or WCF to allow your 64-bit process to talk to a 32-bit host process that loads the 32-bit component.
i have a problem running a simple interop example on my system.
I built a simple 32-bit shared library called libtest.so (c++)
g++ -c -fpic test.cpp -m32
g++ -shared -fpic -o libtest.so test.o -m32
My System:
Ubuntu Linux 10.04 x86_64
Mono C# compiler version 2.4.4.0
In addition i have a sample c# program using my shared library:
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
public class Test
{
[DllImport("libdl.so")]
static extern IntPtr dlopen(string filename, int flags);
[DllImport("libdl.so")]
static extern IntPtr dlclose(IntPtr handle);
[DllImport ("./libtest.so")]
private static extern void HelloWorld();
[DllImport ("./libtest.so",EntryPoint="Test")]
private static extern int Testl(int a,int b);
public static int Main(string[] args)
{
IntPtr handle = dlopen("./libtest.so",2);
if(handle == IntPtr.Zero)
{
Console.WriteLine("Error loading shared library");
return -1;
}
HelloWorld();
int ret = Testl(116,1);
Console.WriteLine("Result from shared-Librarry Call: " + ret);
dlclose(handle);
return 0;
}
}
The Problem: Loading the library does not work.
exporting MONO_LOG_LEVEL=debug gives me the following hint:
Mono-INFO: DllImport error loading library './libtest.so: Wrong ELF-Class: ELFCLASS32'.
Well i guess mono runs my program in 64-bit mode and therefore it cannot call a 32-bit shared library? If i build the shared library in 64 bit mode (without -m32) everything works fine!!
My Mono-Compiler 2.4.4. does not have the option to specify the platform with /platform:x86 and therefore i installed version 2.10, but using it does not work either.
/opt/mono-2.10/bin/gmcs /platform:x86 sharpCall.cs
Is there a possibility to load 32-bit shared libraries on a 64-bit system?
The problem is that you have a 64bit version of Mono installed on your system which can only P/Invoke into 64bit native libraries, it cannot P/Invoke into 32bit native libraries.
The -platform:x86 flag is meant for the C# compiler, not the runtime, and does not hint to the runtime to use a 32bit memory space.
You need to install the 32bit version of Mono on your Ubuntu system if you want to P/Invoke into 32bit native libraries.
You cannot load a 32 bit module into a 64 bit process. Either run a 32 bit process, or compile your native module as a 64 bit module.
Is there a possibility to load 32-bit shared libraries on a 64-bit
system?
Yes, but only if you compile the program that uses said shared libraries into a 32-bit process.
Well i guess mono runs my program in 64-bit mode and therefore it
cannot call a 32-bit shared library? If i build the shared library in
64 bit mode (without -m32) everything works fine!!
Of course this happen. Just compile the program with the m32 flag and you should have no problems.