none
C# COM Client to Existing C++ out-of-process COM Server RRS feed

  • Question

  • I'm reposting this question since I've been having some problems with my MSDN subscription, hoping I might have some better assistance now that aforementioned problems seem to have been resolved.

    I'm trying to connect a COM client written in C# to an existing (legacy) out-of-process COM server written in C++.

    I have imported the tlb using tlbimp.exe and have referenced the RWC in my project.

    I can connect to the COM server using

    object scComm32ComObj = Marshal.GetActiveObject("ScComm32.Document");
    

    But that is as far as I am able to get.

    I need to be able to call some methods on the ScComm32CommObj, but trying to cast it to an interface or a class usign the interface results in an exception.

    Thanks,

    Bill

    Wednesday, September 15, 2010 3:15 PM

Answers

  • I used one of my incidents to solve this.

    The COM server was not written using IUnknown (which is required for early binding) rather it used IDispatch so it depends on late binding. C# has poor support for late binding but VB has that built in.

    So I made a VB.NET wrapper that I call to get this done.

     


    -- Bill McCormick ACE-CO
    Monday, October 25, 2010 4:22 PM

All replies

  • I have succesfully imported the tlb for a c++ app (exe) that exposes a COM interface.

    tlbimp ScComm32.tlb /tlbreference:ScComm32.tlb /out:NET35ScComm32.dll /verbose
    Microsoft (R) .NET Framework Type Library to Assembly Converter 3.5.30729.1 
    Copyright (C) Microsoft Corporation. All rights reserved.
    
    Resolved referenced file 'ScComm32.tlb' to file 'C:\ace-co-devel\ScComm32\ScComm32\Release\ScComm32.tlb'.
    Type 'IScComm32' imported.
    Type 'Document' imported.
    Type library imported to C:\ace-co-devel\ScComm32\ScComm32\Release\NET35ScComm32.dll

    I added a reference to NET35ScComm32.dll to a class assembly project and can make a new COM object by doing

    private static DocumentClass scComm = new DocumentClass();

    Creating a new DocumentClass will run the application, so I assume that I'm on the right track. I have 2 problems however.

    First, I don't want to run a new appliaction. I want to talk to an existing one. How can I do that?

    Secondly, I get an InvalidCastException ...

    Unable to cast COM object of type 'NET35ScComm32.DocumentClass' to interface type 'NET35ScComm32.IScComm32'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{EC08DE87-0CC5-11D4-98B9-00403399AC6C}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).

     .. when I try to call a method from the COM object:

    public virtual short ComQPut(ref string ComStr);
    

    Here's the DocumentClass metadata:

    #region Assembly NET35ScComm32.dll, v2.0.50727
    // C:\ace-co-devel\Aceco.NET\Common\NET35ScComm32.dll
    #endregion
    
    using System;
    using System.Runtime.InteropServices;
    
    namespace NET35ScComm32
    {
      [Guid("EC08DE85-0CC5-11D4-98B9-00403399AC6C")]
      [ClassInterface(0)]
      [TypeLibType(2)]
      public class DocumentClass : IScComm32, Document
      {
        public DocumentClass();
    
        [DispId(7)]
        public virtual void ComQPurge();
        [DispId(8)]
        public virtual short ComQPut(ref string ComStr);
        [DispId(9)]
        public virtual short ComQUnused();
        [DispId(19)]
        public virtual object GetGrp0();
        [DispId(20)]
        public virtual object GetGrp1();
        [DispId(21)]
        public virtual object GetGrp2();
        [DispId(3)]
        public virtual short PrtQPut(ref string PrtStr, short NbrChars);
        [DispId(18)]
        public virtual short PrtQUnused();
        [DispId(12)]
        public virtual short ScorpMsgQNCapacity(short QueueIdx);
        [DispId(13)]
        public virtual short ScorpMsgQNCount(short QueueIdx);
        [DispId(14)]
        public virtual short ScorpMsgQNGetChar(short QueueIdx);
        [DispId(15)]
        public virtual short ScorpMsgQNGetMsg(short QueueIdx, ref string Buffer, short BfrSize);
        [DispId(16)]
        public virtual void ScorpMsgQNPurge(short QueueIdx);
        [DispId(25)]
        public virtual short ScorpScreenCursorIndex();
        [DispId(26)]
        public virtual short ScorpScreenSendKey(short uKeyCode);
        [DispId(23)]
        public virtual void ScorpScreenText(short nStartIndex, short nCount, ref string bsText);
        [DispId(24)]
        public virtual short ScorpScreenTextColor(short nIndex);
        [DispId(27)]
        public virtual short ScorpShowWindow(short nCmdShow);
        [DispId(17)]
        public virtual short TGetScrpField(ref string FlData, ref string PrtLine, ref string Col, ref string DtaBase, ref string DtaSrc, ref string DtaFld, ref string DtaFldDsc, ref string DtaFormat, ref string DtaLen, ref string SrcIdx, ref string EscStr, ref string ChPerIn, short BatchNbr, short MtlNbr);
        [DispId(2)]
        public virtual short TktDataComplete();
        [DispId(4)]
        public virtual short TktDataDataAvailable();
        [DispId(10)]
        public virtual short TktDataDataAvailableForClient(short ClientMask);
        [DispId(1)]
        public virtual short TktDataGetField(short FieldId, short BatchNbr, short MtlNbr, ref string FlData, short FlDataLen);
        [DispId(5)]
        public virtual short TktDataHighestMtlNbr(short BatchNbr);
        [DispId(6)]
        public virtual void TktDataRelease();
        [DispId(11)]
        public virtual void TktDataReleaseForClient(short ClientMask);
        [DispId(22)]
        public virtual void VersionString(ref string ReturnString);
      }
    }
    
    

     Please Help.

    Thanks,

    Bill

     

    • Merged by SamAgain Friday, September 17, 2010 8:32 AM duplicated
    Friday, September 10, 2010 11:36 PM
  • Hi,

    Thanks for your post. This is mainly about out-of-process COM server. Please take a look the All-In-One code samples for COM. It contains good demo code for this topic.

    The names of the demos are:

    ATLExeCOMServer -  An out-of-process ATL COM Server 

    ATLCOMService - An out-of-process ATL COM Service 

    CSCOMService  - An out-of-process COM Service in C# 

    CSExeCOMServer -  An out-of-process COM Server in C# 


    Please mark the right answer at right time.
    Thanks,
    Sam
    Monday, September 13, 2010 9:14 AM
  • Really? CSExeCOMServer?

    It all looks pretty complicated. In VB6 all it took was a 2 lines of code to get this done. First you would call GetObject and then you could call a method on the object.

    I'll continue to look for a better answer.

    Thanks,

    Bill

     

    Monday, September 13, 2010 2:08 PM
  • I think it should look something like this:

    private static DocumentClass scComm = (DocumentClass)Marshal.GetActiveObject("ScComm.Document");

    But I get this exception:

    Invalid class string (Exception from HRESULT: 0x800401F3 (CO_E_CLASSSTRING))

    Monday, September 13, 2010 5:02 PM
  • Sorry, it should look like this:

    private static DocumentClass scComm = (DocumentClass)Marshal.GetActiveObject("ScComm32.Document");

    But I get this exception:

    Unable to cast COM object of type 'System.__ComObject' to class type 'NET35ScComm32.DocumentClass'. COM components that enter the CLR and do not support IProvideClassInfo or that do not have any interop assembly registered will be wrapped in the __ComObject type. Instances of this type cannot be cast to any other class; however they can be cast to interfaces as long as the underlying COM component supports QueryInterface calls for the IID of the interface.

    And so then I thought that this should work:

    private static IScComm32 scComm = (IScComm32)Marshal.GetActiveObject("ScComm32.Document");

    But that generates the following exception:

    Unable to cast COM object of type 'System.__ComObject' to interface type 'NET35ScComm32.IScComm32'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{EC08DE87-0CC5-11D4-98B9-00403399AC6C}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).

    So that does not make much sense because when I imported the TLB, the IScComm32  interface was clearly there so one would think it is supported.

    Monday, September 13, 2010 5:16 PM
  • I took another look at this and it is not applicable since I won't to connect to an out-of-proc service that is already running. This example creates a new one, which my tlbimp generated lib will do, but I need to connect to an already running service or fail.

    I'ce wasted 2 days on this now and I really need a solution MS people. Please.

    Thanks,

    Bill

    Tuesday, September 14, 2010 1:24 AM
  • Hi,

    Just a quick reflection. Please take a look at this article How to use Visual C# to automate a running instance of an Office program. It shows how to create a Microsoft Visual C# 2005 or Microsoft Visual C# .NET client that gets an Automation Reference to a running instance of an Office program. Thought its about Office Word, it could be helpful to your scenario, too.


    Please mark the right answer at the right time.
    Thanks,
    Sam
    • Edited by SamAgain Thursday, September 16, 2010 3:16 AM refine
    Thursday, September 16, 2010 3:15 AM
  • Yes, I have already done that, it works as advertised, and it is exactly what I need to be able to do.

    However, the issue is that I cannot cast my object into the RWC interface. Doing so causes an exception indicating that the COM server's QueryInterface does not support casting into the requested interface.  Trying to cast into the RWC class causes an excpetion telling me that I must cast into an interface.

    It may be that the legacy out-of-process COM server is coded in a way that makes the casting not possible. However, having run the tlbimp on the tlb file not only produces a useable wrapper, it produces a wrapper that can Create the COM object; indeed, doing so launches the application. So, although some things do seem to work correctly, it's more than a little confusing why I cannot cast the result of GetActiveObject into something useful. I can recode the COM server if required, but that would be my last resort. I think there must be some way to twist .NET into doing this since we've been running VB6 clients against it now for about 10 years.

    I've been banging my head on the wall trying to get this to work for a few days now and I really need some MS support on this beyond suggestions of "please look at this" and "please look at that". I can send you whatever you need.

    Thanks,

    Bill

    Thursday, September 16, 2010 4:06 AM
  • Hi,

    As the exception info shows, "This operation failed because the QueryInterface call on the COM component for the interface with IID '{EC08DE87-0CC5-11D4-98B9-00403399AC6C}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).". So if the COM component does support the interface as we saw it when importing the TLB, it is really werid. Anyway, I found the following thread addressing a very similar problem to yours. Based on that thread, I think the root cause lies less in out-proc COM server but more in its regesteration. So I would suggest un-registering and re-registering your COM component.

    (I understand that it may be annoying to look at so many references, but we have to solve the problem step by step. Sorry for the inconvenience.)

    http://social.msdn.microsoft.com/forums/en-US/sqltools/thread/d5d3e5fc-d8ce-4f42-b7ea-9bbbb7756a20/

     


    Please mark the right answer at the right time.
    Thanks,
    Sam
    • Edited by SamAgain Friday, September 17, 2010 8:36 AM refine
    Friday, September 17, 2010 8:30 AM
  • I can't tell you how many times I've read that same thread and unreg'd and re-reg'd the COM.

    Just so we're on the same page, I do the registration like so:

    C:\ace-co-devel\ScComm32\ScComm32\Debug>sccomm32 /unregserver

    C:\ace-co-devel\ScComm32\ScComm32\Debug>sccomm32 /regserver

    Which results in the following:

    [HKEY_CLASSES_ROOT\AppID\ScComm32.exe]
    "AppId"="{EC08DE85-0CC5-11D4-98B9-00403399AC6C}"

    [HKEY_CLASSES_ROOT\CLSID\{EC08DE85-0CC5-11D4-98B9-00403399AC6C}]
    @="ScComm Document"

    [HKEY_CLASSES_ROOT\CLSID\{EC08DE85-0CC5-11D4-98B9-00403399AC6C}\InprocHandler32]
    @="ole32.dll"

    [HKEY_CLASSES_ROOT\CLSID\{EC08DE85-0CC5-11D4-98B9-00403399AC6C}\LocalServer32]
    @="C:\\ACE-CO~1\\ScComm32\\ScComm32\\Debug\\ScComm32.exe"

    [HKEY_CLASSES_ROOT\CLSID\{EC08DE85-0CC5-11D4-98B9-00403399AC6C}\ProgID]
    @="ScComm32.Document"

    And now, to top off my MS frustration, this is the 2nd time I've had to type this today because who knows what y'all did with my 1st one.

    Thanks,

    Bill

    Friday, September 17, 2010 8:30 PM
  • I used one of my incidents to solve this.

    The COM server was not written using IUnknown (which is required for early binding) rather it used IDispatch so it depends on late binding. C# has poor support for late binding but VB has that built in.

    So I made a VB.NET wrapper that I call to get this done.

     


    -- Bill McCormick ACE-CO
    Monday, October 25, 2010 4:22 PM