【问题标题】:How to Consume COM Server (ATL, DLL Surrogate) in .NET C# WinService?如何在 .NET C# WinService 中使用 COM 服务器(ATL、DLL 代理)?
【发布时间】:2013-03-28 05:02:55
【问题描述】:

我有一个 DLL Com 服务器,实际上只被一个旧的 Delphi exe 应用程序使用。

COM Server 是多年前(不是我)用 C++ ATL 编写的。它实现回调(事件 - 是否相同?) - 使用传出接口IConnectionPointImpl。类工厂是单例的(标有DECLARE_CLASSFACTORY_SINGLETON

现在要求这个 COM 服务器必须在多个客户端之间共享:Delphi 和 C#(.NET 2.0,VS2008)。我把它写成DllSurrogate,现在我可以从多个Delphi客户端使用它,使用从TOleServer继承的类,覆盖GetServer方法总是使用CoCreateInstance(因为GetActiveObject通常会失败)并且它正在工作。

现在我需要从 C# WinService 中使用它,但我不知道从哪里开始。 我写了一个使用 WinApi CoCreateInstanceDllImport("ole32.dll") 的小 C# Hello-world - 我能够从 COM 服务器连接到现有实例,但无法订阅事件。

这是VS导入的DLL META-DATA:

我不知道这是否正确。 这是近似代码:

using System;
using System.Collections.Generic;
using System.Text;
using SWLMLib;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;


namespace TestSWLM
{
    [Flags]
    enum CLSCTX : uint
    {
        //... defines here CLSCTX
    }

    class Program
    {
        [DllImport("ole32.dll", EntryPoint = "CoCreateInstance", CallingConvention = CallingConvention.StdCall)]
        static extern UInt32 CoCreateInstance([In, MarshalAs(UnmanagedType.LPStruct)] Guid rclsid,
           IntPtr pUnkOuter, UInt32 dwClsContext, [In, MarshalAs(UnmanagedType.LPStruct)] Guid riid,
           [MarshalAs(UnmanagedType.IUnknown)] out object rReturnedComObject);

        public static void AboutExpireHandler(IFeature pFeature, int HoursRemained)
        {
            Console.WriteLine("AboutExpireHandler, pFeature = {0}", pFeature.Code);        
        }

        static void Main(string[] args)
        {
            try
            {
                SWLMLib.ISWMgr lintfSWLMgr = null;

                object instance = null;
                UInt32 dwRes = CoCreateInstance(new Guid("8EAAFAD7-73F8-403B-A53B-4400E16D8EDF"), IntPtr.Zero, (uint)CLSCTX.CLSCTX_LOCAL_SERVER,
                    new Guid("00000000-0000-0000-C000-000000000046"), out instance);
                SWLMLib.SWMgrClass lSWLMgr = null;
                unsafe
                {
                    lintfSWLMgr = (instance as SWLMLib.ISWMgr);
                    Type liType = instance.GetType();
                }

                if (lintfSWLMgr != null)
                {
                    IntPtr iuknw = Marshal.GetIUnknownForObject(lintfSWLMgr);

                    IntPtr ipointer = IntPtr.Zero;
                    Guid lICPCGuid = typeof(IConnectionPointContainer).GUID;
                    Guid lICPGuid = typeof(IConnectionPoint).GUID;
                    Guid lIEv = new Guid("{C13A9D38-4BB0-465B-BF4A-487F371A5538}");
                    IConnectionPoint lCP = null;
                    IConnectionPointContainer lCPC = null;
                    Int32 r = Marshal.QueryInterface(iuknw, ref lICPCGuid, out ipointer);
                    lCPC = (IConnectionPointContainer)Marshal.GetObjectForIUnknown(ipointer);
                    lCPC.FindConnectionPoint(ref lIEv, out lCP);
                    Int32 outID;
                    lCP.Advise(???, out outID); // HERE I don't know what to do further
                    lIEvEv.FeatureAboutToExpire += AboutExpireHandler;
                }
            }
            catch (Exception E)
            {
                Console.WriteLine(E.Message);
                throw;
            }

            Console.ReadLine();
        }
    }
}

欢迎提供任何建议、链接和专有技术。

【问题讨论】:

  • 您正在寻找“COM Interop” 对该词的快速 Google 搜索将为您提供所需的一切。
  • @RobertHarvey,我整天都在寻找它们——方法太多,或者至少有 2 种:原始(使用 WinAPI)和导入 TLB 包装 COM 类——我不知道哪个好对于我的范围,尤其是 DLL Surrogate
  • 如果您有 TLB,这就是要走的路,只需添加添加引用,然后选择 TLB(使用 Visual Studio 添加引用对话框中的 COM 选项卡)。
  • @SimonMourier,你是什么意思?如果 .TLB 文件 - 我没有,我只有 DLL 和 COM-Server 的源...可以生成吗?
  • 是的,应该是COM对象编译生成的。通常它是从 .IDL 文件生成的。

标签: c# delphi com interop com-interop


【解决方案1】:

似乎我成功连接到并处理 DLL (in-proc) Com Server 的事件。

  1. 我将 COM 服务器作为 DLL 代理 (HowTo here)。
  2. Delphi 客户端 - 形成我的 COM 包装类,继承自 TOleServer 类,我重写了 GetServer 方法:

function TSWMgr.GetServer: IUnknown; begin OleCheck(CoCreateInstance(ServerData^.ClassId, nil, CLSCTX_LOCAL_SERVER, IUnknown, Result)); end;

  1. C#(Hello-World 客户端)端(在咨询了一些 HowTos,如 this):

//使用 SWLMLib; //使用 System.Runtime.InteropServices; //使用 System.Runtime.InteropServices.ComTypes;

[Flags]
enum ReturnCode : uint
{
    S_OK = 0, S_FALSE = 1, REGDB_E_CLASSNOTREG = 0x80040154, CLASS_E_NOAGGREGATION = 0x80040110, E_NOINTERFACE = 0x80004002, E_POINTER = 0x80004003
}

[Flags]
enum CLSCTX : uint
{
    CLSCTX_INPROC_SERVER = 0x1, CLSCTX_INPROC_HANDLER = 0x2, CLSCTX_LOCAL_SERVER = 0x4,
    //... //others
    CLSCTX_ALL = CLSCTX_SERVER | CLSCTX_INPROC_HANDLER
}

/// <summary>
/// Sink Class implementig COM Server outgoing interface SWLMLib.ISWMgrEvents 
/// </summary>
[ClassInterface(ClassInterfaceType.None)]
class MySink : SWLMLib.ISWMgrEvents
{
    public void FeatureAboutToExpire(SWLMLib.IFeature pFeature, int HoursRemained)
    {
        Console.WriteLine("{0} FeatureAboutToExpire: Feature {1} Hours={2}", DateTime.Now, pFeature.Code, HoursRemained);
        Marshal.ReleaseComObject(pFeature); //WTF??? Without this line COM Server object is not released!
    }

    public void FeatureExpired(SWLMLib.IFeature pFeature)
    {
        Console.WriteLine("FeatureExpired: Feature {0}", pFeature.Code);
        Marshal.ReleaseComObject(pFeature); //Without this line COM Server object is not released!
    }
}

class Program
{
    //Import "CoCreateInstance" to play with run context of created COM-object (3rd parameter)
    [DllImport("ole32.dll", EntryPoint = "CoCreateInstance", CallingConvention = CallingConvention.StdCall)]
    static extern UInt32 CoCreateInstance([In, MarshalAs(UnmanagedType.LPStruct)] Guid rclsid,
       IntPtr pUnkOuter, UInt32 dwClsContext, [In, MarshalAs(UnmanagedType.LPStruct)] Guid riid,
       [MarshalAs(UnmanagedType.IUnknown)] out object rReturnedComObject);

    static void Main(string[] args)
    {
        try
        {
            Guid SWMgrClassObjectGuid = typeof(SWLMLib.SWMgrClass).GUID;    //{8EAAFAD7-73F8-403B-A53B-4400E16D8EDF}
            Guid IUnknownGuid = new Guid("00000000-0000-0000-C000-000000000046"); //Can it be written in more pretty style?

            SWLMLib.ISWMgr lintfSWLMgr = null;

            /* This will create IN-PROC Server because, it seems CoCreateInstance will be invoked with CLSCTX_INPROC_SERVER flag settled
            Type type = Type.GetTypeFromCLSID(SWMgrClassObjectGuid), true);
            object instance0 = Activator.CreateInstance(type);
            lintfSWLMgr = (instance0 as SWLMLib.ISWMgr); */

            Guid Ev1 = typeof(ISWMgrEvents).GUID;
            object instance = null;

            unsafe
            {
                UInt32 dwRes = CoCreateInstance(SWMgrClassObjectGuid,
                                                IntPtr.Zero,
                                                (uint)(CLSCTX.CLSCTX_LOCAL_SERVER), //if OR with CLSCTX_INPROC_SERVER then INPROC Server will be created, because of DLL COM Server
                                                IUnknownGuid,
                                                out instance);
                if (dwRes != 0)
                {
                    int iError = Marshal.GetLastWin32Error();
                    Console.WriteLine("CoCreateInstance Error = {0}, LastWin32Error = {1}", dwRes, iError);
                    return;
                }
                lintfSWLMgr = (instance as SWLMLib.ISWMgr);
            }

            if (lintfSWLMgr != null)
            {
                //lintfSWLMgr.InitializeMethod(...); //Initialize object

                //Find Connection Point for Events
                Guid ISWMgrEventsGuid = typeof(SWLMLib.ISWMgrEvents).GUID;      //{C13A9D38-4BB0-465B-BF4A-487F371A5538} Interface for Evenets Handling
                IConnectionPoint lCP = null;
                IConnectionPointContainer lCPC = (instance as IConnectionPointContainer);
                lCPC.FindConnectionPoint(ref ISWMgrEventsGuid, out lCP);

                MySink lSink = new MySink();
                Int32 dwEventsCookie;
                lCP.Advise(lSink, out dwEventsCookie);
                Console.WriteLine("Waiting for Events Handling...");
                Console.WriteLine("Press ENTER to Exit...");

                Console.ReadLine(); // Until Eneter is not hit, the events arrive properly
                //Here starting to Unsubscribe for Events and Com Objects CleanUP
                lCP.Unadvise(dwEventsCookie);
                Marshal.ReleaseComObject(lCP); 
                Marshal.ReleaseComObject(lintfSWLMgr); 
            }

        }
        catch (Exception E)
        {
            Console.WriteLine(E.Message);
            throw;
        }
    }
}

也许这不是最好的方式(如 TblImp.exe 和/或 COM Wrappers),但这种原始方式有效。

【讨论】:

  • 这对我来说似乎还不错。我不得不承认,很难弄清楚你的实际问题。如果我没有阅读您的示例代码并(偶然)找到带有“// HERE 我不知道该做什么进一步”评论的行,我会完全跳过您的帖子。也许你应该把它放在帖子的第一行,或者更好地放在标题行。
  • @StuartRedmann,说实话我不明白你的评论。 1.是我的帖子太假还是..别的什么? 2. 至少……我的回答对你有用吗?
  • 我不知道这是怎么发生的,但我绝对没有回复您的评论,而是回复了 OP 的问题。也许我没有足够的权限来评论问题而只能评论答案?
  • 再次抱歉,没有认出您是 OP。
    您的帖子绝对不是太假,但恕我直言,有太多不相关的信息。对我来说,阅读这么长的文本需要很长时间。我更喜欢这样的行:“我想在我的 C# 应用程序中使用带有传出接口的 COM 组件,但我不知道我必须在 IConnectionPoint.Advice (...) 中提供什么”
【解决方案2】:

我想你会想要编写如下代码:

Type type = Type.GetTypeFromCLSID(coClassGuid, true);
object instance = Activator.CreateInstance(type);

您可以将其视为类似于CoCreateInstance

QueryInterface 的 C# COM 互操作方式是进行强制转换或使用 as 关键字:

IComInterface comInterface = instance as IComInterface;

这将避免您必须直接使用 COM API。

我绝不是 COM 互操作专家,我只是需要做一些工作,所以我希望这对你有所帮助。

【讨论】:

  • 谢谢,但似乎没有帮助。我之前尝试过它,它创建了单独的新实例,从系统资源管理器中我看到它直接使用 Dll,但不应该。 Dll 必须通过 DLLHOST.exe 在进程之间共享
  • 是的,我忘记了 dll 代理。您可以将此代码与 64 位 exe 和 32 位 com dll 一起使用(其中 com 组件仅在 32 位端注册),但我无法使用此方法加载代理主机具有相同的位数。
  • 你可以使用 c++/cli,而不是使用 p/invoke。
猜你喜欢
  • 2013-06-21
  • 2011-09-26
  • 2012-03-03
  • 2011-06-23
  • 2010-12-17
  • 2012-05-31
  • 2011-11-10
  • 2010-12-15
  • 1970-01-01
相关资源
最近更新 更多