2

We have an old 32-bit Visual Studio C# Windows Forms solution, which we want to compile from now on, in 64-bit. Unfortunately, our application uses some external dll-s (for scanners, cameras and so on) which are available only in 32-bit version. Accessing 32-bit DLLs from 64-bit code isn't straightforward, especially, when we want to handle also the events raised by those dll-s. Our knowledge at this area is unsufficient for creating an implementation based on this article, so we are looking for more detailed instructions or examples.

Our first attempt was based on this article. We wrapped the third party dll-s into a late bound 32-bit COM server and we used it from our 64-bit application as described here (mutatis mutandis, because we had to swap the roles of 32-bit and 64-bit). This attempt was successful, but incomplete, because this solution doesn't deliver the events from the COM server to the 64-bit client. So we started focusing on the events. We found a lot of articles and examples dealing with consuming events raised by the COM object, but none of them provides a complete solution for us. One part of the sources deals exclusively with the client, or exclusively with the server, but they are incompatible with each other, or with our environment (WinForm, c#).

For example,

  1. this answer tells us how to make a COM server which exposes .NET events to a VBA client, but I don't know how to use it from a c# client.
  2. In contrast this article gives a good working c# client for an existing COM server, but doesn't tell, how to make such a COM server (this COM server is visibly different from the previous example)
  3. this answer doesn't tell any details of the solution.
  4. This article is for c++ instead of c#.
  5. This answer refers to this article, but the latter uses again a VB client instead of c#.
  6. This article mixes different things in an untraceable manner.

Perhaps some of these can be used by us with some effort, but which and how?

Edit

Now I tend to create a hybrid solution: My newest idea for the backward communication from the 32-bit COM object to the caller 64-bit application is to put a Named Pipe server into the 64-bit application and a Named Pipe client into the COM object and every time when an event is raised in the COM object, it sends a Named Pipe message to the Named Pipe server. The code I found for this is available here (projects CSNamedPipeServer and CSNamedPipeClient).

mma
  • 381
  • 2
  • 15
  • Is this DLL generating visual components, or is it just generating events back to your code? 32 bit => 64 bit is a can of worms (as you have discovered) as it requires you to cross process boundaries (32 bit is a separate virtual environment in 64 bit windows). Have you tried a 32 bit wrapper in C#, talking out of process to your 64 bit program? – Steve Todd Apr 08 '19 at 17:09
  • Didn't you already asked that question a few days ago (and deleted it, with its comments)? – Simon Mourier Apr 08 '19 at 19:10
  • @SimonMourier Almost. The problem Is the same,but the question is now different and is (hopefully) more elaborated. – mma Apr 09 '19 at 09:54
  • I can provide a C# sample with a 64-bit COM server with an event, and a 32-bit COM client that work, if you want. – Simon Mourier Apr 09 '19 at 10:06
  • @SimonMourier Cool! I look forward your solution. – mma Apr 09 '19 at 10:22

2 Answers2

4

Here is a sample with a 64-bit server, implemented as a C# class in a class library project, hosted by Windows' system surrogate: dllhost.

This is the class code (you can compile as 'any cpu', no need to compile as x64):

namespace NetComClassLibrary3
{
    // technically, we don't *have to* define an interface, we could do everything using dynamic stuff
    // but it's more practical so we can reference this .NET dll from our client
    [ComVisible(true)]
    [Guid("31dd1263-0002-4071-aa4a-d226a55116bd")]
    public interface IMyClass
    {
        event OnMyEventDelegate OnMyEvent;
        object MyMethod();
    }

    // same remark than above.
    // This *must* match the OnMyEvent signature below
    [ComVisible(true)]
    [Guid("31dd1263-0003-4071-aa4a-d226a55116bd")]
    public delegate void OnMyEventDelegate(string text);

    // this "event" interface is mandatory
    // note from the .NET perspective, no one seems to implement it
    // but it's referenced with the ComSourceInterfaces attribute on our COM server (below)
    [ComVisible(true)]
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    [Guid("31dd1263-0000-4071-aa4a-d226a55116bd")]
    public interface IMyEvents
    {
        // dispids are mandatory here otherwise you'll get a DISP_E_UNKNOWNNAME error
        [DispId(1)]
        void OnMyEvent(string text);
    }

    [ComVisible(true)]
    [ComSourceInterfaces(typeof(IMyEvents))]
    [Guid("31dd1263-0001-4071-aa4a-d226a55116bd")]
    public class MyClass : IMyClass
    {
        public event OnMyEventDelegate OnMyEvent;

        public object MyMethod()
        {
            // we use the current running process to test out stuff
            // this should be Windows' default surrogate: dllhost.exe
            var process = Process.GetCurrentProcess();
            var text = "MyMethod. Bitness: " + IntPtr.Size + " Pid: " + process.Id + " Name: " + process.ProcessName;
            Console.WriteLine(text); // should not be displayed when running under dllhost
            OnMyEvent?.Invoke("MyEvent. " + text);
            return text;
        }
    }
}

This is how I register it (note I'm targeting the 64bit registry):

%windir%\Microsoft.NET\Framework64\v4.0.30319\regasm.exe NetComClassLibrary3.dll /codebase /tlb

This is a .reg to make sure it will run out-of-process in dllhost.exe (the guid is the COM coclass MyClass' guid):

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\AppID\{31dd1263-0001-4071-aa4a-d226a55116bd}]
"DllSurrogate"=""

[HKEY_CLASSES_ROOT\CLSID\{31dd1263-0001-4071-aa4a-d226a55116bd}]
"AppID"="{31dd1263-0001-4071-aa4a-d226a55116bd}"

And here is the client, compiled as x86:

using System;
using NetComClassLibrary3; // we can reference the .net dll as is

namespace ConsoleApp10
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Bitness: " + IntPtr.Size);
            // note we don't use new MyClass() otherwise we may go inprocess
            var type = Type.GetTypeFromCLSID(typeof(MyClass).GUID);
            var obj = (IMyClass)Activator.CreateInstance(type);

            // note I'm using the beloved dynamic keyword here. for some reason obj.OnMyEvent works but locally raises a cast error I've not investigated further...
            dynamic d = obj;
            d.OnMyEvent += (OnMyEventDelegate)((t) =>
            {
                Console.WriteLine(t);
            });
            Console.WriteLine(obj.MyMethod());
        }
    }
}

When I run it, this is the output:

Bitness: 4 // running as 32-bit
MyEvent. MyMethod. Bitness: 8 Pid: 23780 Name: dllhost // from 64-bit world
MyMethod. Bitness: 8 Pid: 23780 Name: dllhost // from 64-bit world
Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
0

When we swap the role of 32-bit and 64-bit in the solution of Simon Mourier, then - beyond changing the compilation bitness - we should change 4 things.

(1) changing the registration

from

%windir%\Microsoft.NET\Framework64\v4.0.30319\regasm.exe NetComClassLibrary3.dll /codebase /tlb

to

%windir%\Microsoft.NET\Framework\v4.0.30319\regasm.exe NetComClassLibrary3.dll /codebase /tlb

(2) changing registry items

from

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\AppID\{31dd1263-0001-4071-aa4a-d226a55116bd}]
"DllSurrogate"=""

[HKEY_CLASSES_ROOT\CLSID\{31dd1263-0001-4071-aa4a-d226a55116bd}]
"AppID"="{31dd1263-0001-4071-aa4a-d226a55116bd}"

to

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Classes\AppID\{31dd1263-0001-4071-aa4a-d226a55116bd}]
"DllSurrogate"=""

[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Classes\CLSID\{31dd1263-0001-4071-aa4a-d226a55116bd}]
"AppID"="{31dd1263-0001-4071-aa4a-d226a55116bd}"

(3) In the 64-bit client, instead of registering the 32-bit NetComClassLibrary3.dll, copy the definitions of IMyClass and OnMyEventDelegate into the client's source code

(4) Also in the client,

change

var type = Type.GetTypeFromCLSID(typeof(MyClass).GUID);

to

var type = Type.GetTypeFromProgID("NetComClassLibrary3.MyClass");

so the client will look so:

using System;
// removed by mma - using NetComClassLibrary3; // we can reference the .net dll as is

namespace ConsoleApp10
{
    // inserted by mma:
    [System.Runtime.InteropServices.Guid("31dd1263-0002-4071-aa4a-d226a55116bd")]
    public interface IMyClass
    {
        event OnMyEventDelegate OnMyEvent;
        object MyMethod();
    }
    [System.Runtime.InteropServices.Guid("31dd1263-0002-4071-aa4a-d226a55116bd")]
    public delegate void OnMyEventDelegate(string text);
    // end of insertion

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Bitness: " + IntPtr.Size);
            // note we don't use new MyClass() otherwise we may go inprocess
            // removed by mma var type = Type.GetTypeFromCLSID(typeof(MyClass).GUID);
            // inserted by mma:
            var type = Type.GetTypeFromProgID("NetComClassLibrary3.MyClass");
            // end of insertion
            var obj = (IMyClass)Activator.CreateInstance(type);

            // note I'm using the beloved dynamic keyword here. for some reason obj.OnMyEvent works but locally raises a cast error I've not investigated further...
            dynamic d = obj;
            d.OnMyEvent += (OnMyEventDelegate)((t) =>
            {
                Console.WriteLine(t);
            });
            Console.WriteLine(obj.MyMethod());
        }
    }
}

and so the output will change from

Bitness: 4 // running as 32-bit
MyEvent. MyMethod. Bitness: 8 Pid: 23780 Name: dllhost // from 64-bit world
MyMethod. Bitness: 8 Pid: 23780 Name: dllhost // from 64-bit world

to

Bitness: 8 // running as 64-bit
MyEvent. MyMethod. Bitness: 4 Pid: 56140 Name: dllhost // from 32-bit world
MyMethod. Bitness: 4 Pid: 56140 Name: dllhost // from 32-bit world

Remark Adding the definitions of IMyClass and OnMyEventDelegate into the client's source code instead of registering the 32-bit NetComClassLibrary3.dll works also on the 32-bit client + 64-bit COM-server version, but referencing the 32-bit COM dll in the 64-bit client leads to a BadImageFormat exception.

mma
  • 381
  • 2
  • 15