How to use 32-bit COM library in C# from non STA thread and/or 64-bit process

0

I’m trying to consume a legacy COM components (dll library created probably in C++, 32 bit) in my C# application. I need to get it work in the following scenarios:

  1. COM component used in x86 STA thread (e.g. main app thread)
  2. COM component used in x86 worker thread (e.g. task/thread pool)
  3. COM component used in x64 STA thread (e.g. main app thread)
  4. COM component used in x64 worker thread (e.g. task/thread pool)

So far I have it working in scenario 1. I have also created PoC wrapper for scenario 2 – but this is not the way I’d like to follow now. All in all – only scenario 1 works fine. The rest simply does not.

I have done some research and the findings were that scenario 1 should be fairly simple. And it was. I have also learnt that scenarios 2, 3, 4 should also be possible when COM component is hosted in a surrogate (32 bit?) process. In that case there may be a significant performance loss, but I’m not so much concerned about performance now.

Although trying many times I could not make it work that way. I have run out of ideas and need your help. Let me describe my setup first. The COM components mentioned at the beginning are provided as the following set of files:

  • GeoDefs.dll
  • GeoDefs.tlb
  • GeomDefs.dll
  • GeomDefs.tlb
  • GeoFunc.dll
  • GeoFunc.tlb
  • GeomFunc.dll
  • GeomFunc.tlb
  • GeoDatumKrtgrf.dll
  • GeoDatumKrtgrf.tlb

I have registered all (5) of the above dll files with regsvr32 tool. Registration was successful. I have created simple C# WinForms application and added COM references to my project:

  • GeoDatumKrtgrf 1.0 Type Library
  • GeoDefs 1.0 Type Library
  • GeoFunc 1.0 Type Library
  • GeomDefs 1.0 Type Library
  • GeomFunc 1.0 Type Library

Under the hood Visual Studio has created interop assemblies (placed in obj folder):

  • Interop.GEODATUMKRTGRFLib.dll
  • Interop.GEODEFSLib.dll
  • Interop.GEOFUNCLib.dll
  • Interop.GEOMDEFSLib.dll
  • Interop.GEOMFUNCLib.dll

which appeared in references section as:

  • GEODATUMKRTGRFLib
  • GEODEFSLib
  • GEOFUNCLib
  • GEOMDEFSLib
  • GEOMFUNCLib

All above references have Embed Interop Types set to “False” and Copy Local to “True” in properties.

And finally – there are two simple code snippets that I use to test if COM is properly accessible.

void btnComInMainThread_Click(object sender, EventArgs e)
{
    try
    {
        var comObject = new GeoDatumKrtgrfClass();

        comObject.InitDatum(eT_DatumTypes.DT_WGS_1984);

        comObject.LG2G(out var lonDouble, out var latDouble, 17000000, 25000000);

        Debug.Assert(Math.Abs(lonDouble - 17.0) < 1e-6 && Math.Abs(latDouble - 25.0) < 1e-6);

        Debug.Print($"{DateTime.Now:HH:mm:ss.fff}: Ok.");
    }
    catch (Exception ex)
    {
        Debug.Print(ex.ToString());
    }
}

void btnComInWorkerThread_Click(object sender, EventArgs e)
{
    Task.Run(() =>
    {
        try
        {
            var comObject = new GeoDatumKrtgrfClass();

            comObject.InitDatum(eT_DatumTypes.DT_WGS_1984);

            comObject.LG2G(out var lonDouble, out var latDouble, 17000000, 25000000);

            Debug.Assert(Math.Abs(lonDouble - 17.0) < 1e-6 && Math.Abs(latDouble - 25.0) < 1e-6);

            Debug.Print($"{DateTime.Now:HH:mm:ss.fff}: Ok.");
        }
        catch (Exception ex)
        {
            Debug.Print(ex.ToString());
        }
   });
}

As it was said before - only btnComInMainThread_Click works fine if and only if C# application project is targeted at x86 (Scenario 1). In any other case there is a following exception as soon as InitDatum method is called.

System.InvalidCastException: Unable to cast COM object of type 'GEODATUMKRTGRFLib.GeoDatumKrtgrfClass' to interface type 'GEODATUMKRTGRFLib.IGeoDatumKrtgrf'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{C6ACFB21-24DC-43FB-AF7F-07EB526D2DF5}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).
   at System.StubHelpers.StubHelpers.GetCOMIPFromRCW(Object objSrc, IntPtr pCPCMD, IntPtr& ppTarget, Boolean& pfNeedsRelease)
   at GEODATUMKRTGRFLib.GeoDatumKrtgrfClass.InitDatum(eT_DatumTypes e_Datum)
   at ...

Below I include some (shortened) extra info from OLE/COM Object Viewer and ILSpy.

GeoDatumKrtgrf.tlb

// Generated .IDL file (by the OLE/COM Object Viewer)
// 
// typelib filename: GeoDatumKrtgrf.tlb

[
  uuid(D541177E-570B-4F7F-A6B0-931C0BAC85C9),
  version(1.0),
  helpstring("GeoDatumKrtgrf 1.0 Type Library"),
  custom(DE77BA64-517C-11D1-A2DA-0000F8773CE9, 100663662),
  custom(DE77BA63-517C-11D1-A2DA-0000F8773CE9, 1302262921),
  custom(DE77BA65-517C-11D1-A2DA-0000F8773CE9, "Created by MIDL version 6.00.0366 at Fri Apr 08 13:41:58 2011
")

]
library GEODATUMKRTGRFLib
{
    // TLib :     // TLib : GeoDefs 1.0 Type Library : {3EB9DBAA-44B4-4FFE-AAA2-FA7AFC6FC228}
    importlib("GeoDefs.tlb");
    // TLib : GeomDefs 1.0 Type Library : {F28B71EA-132F-4C47-AAB1-1218716945E5}
    importlib("GeomDefs.tlb");

    // Forward declare all types defined in this typelib
    interface IGeoDatumKrtgrf;

    [
      uuid(7948C0A1-0806-406A-B5A1-34A9106B8C37),
      helpstring("GeoDatumKrtgrf Class")
    ]
    coclass GeoDatumKrtgrf {
    [default] interface IGeoDatumKrtgrf;
    };

    [
      odl,
      uuid(C6ACFB21-24DC-43FB-AF7F-07EB526D2DF5),
      helpstring("IGeoDatumKrtgrf Interface")
    ]
    interface IGeoDatumKrtgrf : IGeoDatum {

    ...

    [helpstring("method LG2G")]
    HRESULT _stdcall LG2G(
            [out] double* pd_Lon, 
            [out] double* pd_Lat, 
            [in] long l_Lon, 
            [in] long l_Lat);

    ...

    };
};

GeoDefs.tlb

// Generated .IDL file (by the OLE/COM Object Viewer)
// 
// typelib filename: GeoDefs.tlb

[
  uuid(3EB9DBAA-44B4-4FFE-AAA2-FA7AFC6FC228),
  version(1.0),
  helpstring("GeoDefs 1.0 Type Library"),
  custom(DE77BA64-517C-11D1-A2DA-0000F8773CE9, 100663662),
  custom(DE77BA63-517C-11D1-A2DA-0000F8773CE9, 1302262808),
  custom(DE77BA65-517C-11D1-A2DA-0000F8773CE9, "Created by MIDL version 6.00.0366 at Fri Apr 08 13:40:05 2011
")

]
library GEODEFSLib
{
    // TLib :     // TLib : OLE Automation : {00020430-0000-0000-C000-000000000046}
    importlib("stdole2.tlb");

    ...

    [
      odl,
      uuid(69E77C75-BB8D-46F9-BD93-1D26E09249DE),
      helpstring("IGeoDatum Interface")
    ]
    interface IGeoDatum : IUnknown {
    HRESULT _stdcall InitDatum([in] eT_DatumTypes e_Datum);
    HRESULT _stdcall SetViewByR(
            [in] stT_GeoRect* pst_GeoRect, 
            [in, out] tagRECT* pst_Rect);
    HRESULT _stdcall SetViewByP(
            [in] stT_GeoCoord* pst_GeoCoordLB, 
            [in] stT_GeoCoord* pst_GeoCoordRT, 
            [in, out] tagPOINT* pst_PntLB, 
            [in, out] tagPOINT* pst_PntRT);
    HRESULT _stdcall G2L(
            [in] double d_Lon, 
            [in] double d_Lat, 
            [in, out] long* pl_X, 
            [in, out] long* pl_Y);
    HRESULT _stdcall L2G(
            [in] long l_X, 
            [in] long l_Y, 
            [in, out] double* pd_Lon, 
            [in, out] double* pd_Lat);
    HRESULT _stdcall GP2LP(
            [in] stT_GeoCoord* pst_GeoCoord, 
            [in, out] tagPOINT* pst_Pnt);
    HRESULT _stdcall LP2GP(
            [in] tagPOINT* pst_Pnt, 
            [in, out] stT_GeoCoord* pst_GeoCoord);
    HRESULT _stdcall GR2LR(
            [in] stT_GeoRect* pst_GeoRect, 
            [in, out] tagRECT* pst_Rect);
    HRESULT _stdcall LR2GR(
            [in] tagRECT* pst_Rect, 
            [in, out] stT_GeoRect* pst_GeoRect);
    [helpstring("method NormalizeGR")]
    HRESULT _stdcall NormalizeGR([in, out] stT_GeoRect* pst_GeoRect);
    };

    ...

    [
      uuid(38EE7367-30EC-43A7-96F6-C55BC39B62C0),
      helpstring("Cnv Class")
    ]
    coclass Cnv {
    [default] interface ICnv;
    };

    [
      odl,
      uuid(6C92FACA-266F-4943-B4AE-7E538F6FC672),
      helpstring("ICnv Interface")
    ]
    interface ICnv : IUnknown {
    [helpstring("method dms2dd")]
    HRESULT _stdcall dms2dd(
            [in] stT_GeoCoordDMS* pst_GeoCoordDMS, 
            [out, retval] stT_GeoCoord* pst_GeoCoord);
    [helpstring("method dd2dms")]
    HRESULT _stdcall dd2dms(
            [in] stT_GeoCoord* pst_GeoCoord, 
            [out, retval] stT_GeoCoordDMS* pst_GeoCoordDMS);
    [helpstring("method dm2dd")]
    HRESULT _stdcall dm2dd(
            [in] stT_GeoCoordDM* pst_GeoCoordDM, 
            [out, retval] stT_GeoCoord* pst_GeoCoord);
    [helpstring("method dd2dm")]
    HRESULT _stdcall dd2dm(
            [in] stT_GeoCoord* pst_GeoCoord, 
            [out, retval] stT_GeoCoordDM* pst_GeoCoordDM);
    [helpstring("method DDMMSS2d")]
    HRESULT _stdcall DDMMSS2d(
            [out] stT_GeoCoord* pst_GeoCoord, 
            [in] long l_Lon, 
            [in] long l_Lat);
    [helpstring("method d2DDMMSS")]
    HRESULT _stdcall d2DDMMSS(
            [out] long* pl_Lon, 
            [out] long* pl_Lat, 
            [in] stT_GeoCoord* pst_GeoCoord);
    [helpstring("method dmsTodd")]
    HRESULT _stdcall dmsTodd(
            [in] stT_GeoCoordDMS* pst_GeoCoordDMS, 
            [out] stT_GeoCoord* pst_GeoCoord);
    [helpstring("method ddTodms")]
    HRESULT _stdcall ddTodms(
            [in] stT_GeoCoord* pst_GeoCoord, 
            [out] stT_GeoCoordDMS* pst_GeoCoordDMS);
    [helpstring("method dmshToddh")]
    HRESULT _stdcall dmshToddh(
            [in] stT_GeoCoordDMSH* pst_GeoCoordDMSH, 
            [out] stT_GeoCoordH* pst_GeoCoordH);
    [helpstring("method ddhTodmsh")]
    HRESULT _stdcall ddhTodmsh(
            [in] stT_GeoCoordH* pst_GeoCoordH, 
            [out] stT_GeoCoordDMSH* pst_GeoCoordDMSH);
    [helpstring("method dd2ch")]
    HRESULT _stdcall dd2ch(
            [in] stT_GeoCoord* pst_GeoCoord, 
            [out] BSTR* pac_Lat, 
            [out] BSTR* pac_Lon);
    };
};

Interop.GEODATUMKRTGRFLib.dll 1/3

// GEODATUMKRTGRFLib.GeoDatumKrtgrfClass
using GEODATUMKRTGRFLib;
using GEODEFSLib;
using GEOMDEFSLib;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

[ComImport]
[ClassInterface(0)]
[TypeLibType(2)]
[Guid("7948C0A1-0806-406A-B5A1-34A9106B8C37")]
public class GeoDatumKrtgrfClass : IGeoDatumKrtgrf, GeoDatumKrtgrf
{
    [MethodImpl(MethodImplOptions.InternalCall)]
    public extern GeoDatumKrtgrfClass();

    [MethodImpl(MethodImplOptions.InternalCall)]
    public virtual extern void InitDatum([In] eT_DatumTypes e_Datum);

    void IGeoDatumKrtgrf.InitDatum([In] eT_DatumTypes e_Datum)
    {
        //ILSpy generated this explicit interface implementation from .override directive in InitDatum
        this.InitDatum(e_Datum);
    }

    ...

    [MethodImpl(MethodImplOptions.InternalCall)]
    public virtual extern void LG2G(out double pd_Lon, out double pd_Lat, [In] int l_Lon, [In] int l_Lat);

    void IGeoDatumKrtgrf.LG2G(out double pd_Lon, out double pd_Lat, [In] int l_Lon, [In] int l_Lat)
    {
        //ILSpy generated this explicit interface implementation from .override directive in LG2G
        this.LG2G(out pd_Lon, out pd_Lat, l_Lon, l_Lat);
    }

    ...

}

Interop.GEODATUMKRTGRFLib.dll 2/3

// GEODATUMKRTGRFLib.IGeoDatumKrtgrf
using GEODEFSLib;
using GEOMDEFSLib;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

[ComImport]
[Guid("C6ACFB21-24DC-43FB-AF7F-07EB526D2DF5")]
[InterfaceType(1)]
public interface IGeoDatumKrtgrf : IGeoDatum
{
    [MethodImpl(MethodImplOptions.InternalCall)]
    new void InitDatum([In] eT_DatumTypes e_Datum);

    ...

    [MethodImpl(MethodImplOptions.InternalCall)]
    void LG2G(out double pd_Lon, out double pd_Lat, [In] int l_Lon, [In] int l_Lat);

    ...

}

Interop.GEODATUMKRTGRFLib.dll 3/3

// GEODATUMKRTGRFLib.GeoDatumKrtgrf
using GEODATUMKRTGRFLib;
using System.Runtime.InteropServices;

[ComImport]
[Guid("C6ACFB21-24DC-43FB-AF7F-07EB526D2DF5")]
[CoClass(typeof(GeoDatumKrtgrfClass))]
public interface GeoDatumKrtgrf : IGeoDatumKrtgrf
{
}

Interop.GEODEFSLib.dll

// GEODEFSLib.IGeoDatum
using GEODEFSLib;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

[ComImport]
[Guid("69E77C75-BB8D-46F9-BD93-1D26E09249DE")]
[InterfaceType(1)]
public interface IGeoDatum
{
    [MethodImpl(MethodImplOptions.InternalCall)]
    void InitDatum([In] eT_DatumTypes e_Datum);

    ...
}

There are also Cnv, CnvClass and ICnv (and many struct and enums) which seem to be less relevant and are not included at the moment.

Saying all above I'd like to add that I have also tried to manually tweak system registry. It was rather chaotic and did not bring any improvements.

I hope you will help me getting all four scenarios running.

c#
dll
com
interop
regsvr32
asked on Stack Overflow Sep 22, 2019 by MarKol4

0 Answers

Nobody has answered this question yet.


User contributions licensed under CC BY-SA 3.0