C#如何与com交互(一篇老外的文章简单的翻译摘要)

Interoperation com and .net
1.在.NET程序中使用经典的com组件
  先写一个atl的com组件,这是idl
  interface IAirlineInfo : IDispatch
{

    [id(1), helpstring("method GetAirlineTiming")]
    HRESULT GetAirlineTiming([in] BSTR bstrAirline,
                             [out,retval] BSTR* pBstrDetails);
   
    [propget, id(2), helpstring("property LocalTimeAtOrlando")]
    HRESULT LocalTimeAtOrlando([out, retval] BSTR *pVal);
   
};
  下面是实现部分
CAirlineInfo::GetAirlineTiming(BSTR bstrAirline, BSTR *pBstrDetails)
{
  _bstr_t bstrQueryAirline(bstrAirline);
  if(NULL == pBstrDetails) return E_POINTER;

  if(_bstr_t("Air Scooby IC 5678") == bstrQueryAirline)
  {
    // Return the timing for this Airline
    *pBstrDetails = _bstr_t(_T("16:45:00 - Will arrive at Terminal 3")).copy();

  }
  else
  {
    // Return an error message if the Airline was not found
    return Error(LPCTSTR(_T("Airline Timings not available for this Airline" )), 
                 __uuidof(AirlineInfo), AIRLINE_NOT_FOUND);
  }
  return S_OK;
 
}


使用一个叫做TLBIMP.exe的工具来通过上面组件的type library 生成相应的metadata wrapper
TLBIMP AirlineInformation.tlb /out:AirlineMetadata.dll


可以使用ildasm来查看元数据信息,我们可以看到数据类型(参数或是返回值)都已经被转换成了
对等的.net部分。

在上面的例子中,bstr已经被换成了string ,用[out ,retval]标记的参数已经被直接转换成了实际的
函数返回值,而原来的返回值HRESULT已经被作为.net exception抛出

下面就来想办法用.net来调用这个com组件
直接使用AirlineMetadata.dll 这个元数据程序集直接操作com
String strAirline = "Air Scooby IC 5678";
String strFoodJunkieAirline = "Air Jughead TX  1234";
try
{
   AirlineInfo objAirlineInfo;
   objAirlineInfo = new AirlineInfo();
  
   // Call the GetAirlineTiming() method
   System.Console.WriteLine("Details for Airline {0} --> {1}",
                            strAirline,
                            objAirlineInfo.GetAirlineTiming(strAirline));
                           
    // This should make the COM object throw us the
    // AIRLINE_NOT_FOUND error as a COMException
    System.Console.WriteLine("Details for Airline {0} --> {1}",
                             strFoodJunkieAirline,
                             objAirlineInfo.GetAirlineTiming(
                                        strFoodJunkieAirline));
}
catch(COMException e)
{
   System.Console.WriteLine("Oops an error occured !. Error Code is : {0}.
        Error message is : {1}",e.ErrorCode,e.Message);
}
直接new就可以了,真的是很神奇的。
不过这些工作都是RCW偷偷的为我们做的,它将.net调用转换成com调用,同时负责其中
数据类型的转换,最后将返回值转换和.net的形式,将HRESULT转换成为Exception抛出

那么经典的QueryInterface到那里去了,我们应该怎么来用它,
在.net中QueryInterface的调用直接有简单的cast来代替了,你只需要用相应的接口来
强制转换就可以了,如果不成功,就会返回InvalidCastException,相当的简单。
其中复杂的工作已经由rcw做了,我们其实只需要考虑程序的逻辑就可以了。

如果绝对这样直接转换太暴力了(确实,直接通过异常来判断总是不好的),那么可以通过
“is”操作符来判断一下类型,确认了在安全的转换。还可以通过“as”操作符来做转换,
转换不成功是不会抛出异常的,只会返回一个null.


那么如何来实现COM的后期绑定呢?
刚才我们在上面看到的例子都是使用metadata代理来前期绑定.net客户端到COM对象上面。

但是如果我们一定要做后期绑定怎么办?
  使用reflection就可以了。这样不仅可以应用到com对象中,同时.net对象也可以这样来做
后期的绑定和加载。
  如果你的com只实现了一个纯的dispinterface,那么你就只能使用reflection来生成你的对象
和调用你的函数了。
  做后期绑定,你首先要知道对象的ProgID或是CLSID,
  由于reflection 和核心函数CreateInstance 需要一个 Type信息来做为参数,而我们有的只是
简单的GUID信息,那么需要通过GetTypeFromProgID 或是 Type.GetTypeFromCLSID来做相应的转换
而调用方法,我们要使用InvokeMember。
.......
try
{

   object objAirlineLateBound;
   Type objTypeAirline;
  
   //  Create an object array containing
   // the input parameters for the method
   object[] arrayInputParams= { "Air Scooby IC 5678" };
  
   //Get the type information from the progid
   objTypeAirline =
     Type.GetTypeFromProgID("AirlineInformation.AirlineInfo");
  
   // Here's how you use the COM CLSID
   // to get the associated .NET System.Type
   // objTypeAirline = Type.GetTypeFromCLSID(new Guid(
                       "{F29EAEEE-D445-403B-B89E-C8C502B115D8}"));

  
   // Create an instance of the object
   objAirlineLateBound = Activator.CreateInstance(objTypeAirline);

   // Invoke the 'GetAirlineTiming' method
   String str =  (String)objTypeAirline.InvokeMember("GetAirlineTiming",
                                    BindingFlags.Default |
                                    BindingFlags.InvokeMethod,
                                    null,
                                    objAirlineLateBound,
                                    arrayInputParams);

    System.Console.WriteLine("Late Bound Call" +
              " - Air Scooby Arrives at : {0}",str);

    // Get the value of the 'LocalTimeAtOrlando' property
    String strTime = (String)objTypeAirline.InvokeMember("LocalTimeAtOrlando",
                                    BindingFlags.Default |
                                    BindingFlags.GetProperty,
                                    null,
                                    objAirlineLateBound,
                                    new object[]{});

    Console.WriteLine ("Late Bound Call - Local" +
                       " Time at Orlando,Florida is: {0}",
                       strTime);

}/* end try */
catch(COMException e)
{
    System.Console.WriteLine("Error code : {0}, Error message : {1}",
                              e.ErrorCode, e.Message);
}/* end catch */
.......

上面基本是reflection的东西,真正和com有关的,恐怕就是那两个转换的函数。


接着是比较复杂的部分,com 的事件回调机制
   一个com 对象如果要是要支持outgoing接口,那么它一定要实现IConnectionPointContainer
接口,客户端通过QI这个IConnectionPointContainer来检查它是否支持相应的outgoing接口
如果QI失败了,那么这个对象就根本不支持事件。如果成功了,那么可以通过FindConnectionPoint
来检查是否支持指定的接口。如果支持,那么就会返回对应的连接点。然后将sink对象的IUnknown
接口传给IConnectionPoint::advise,那么com就会将它加入一个map中,并在需要的时候触发这个
事件。同时它会返回一个cookie给客户端,这个cookie可以在你不像响应事件的时候通过IConnectionPoint::UnAdvise
来取消。

   下面建立一个带连接点的atl com
   interface IAirlineArrivalPager : IDispatch
{
    [id(1), helpstring("method AddArrivalDetails")]
       HRESULT AddArrivalDetails([in] BSTR bstrAirlineName,
       [in] BSTR bstrArrivalTerminal);
};

....

dispinterface _IAirlineArrivalPagerEvents
{
    properties:
    methods:
       [id(1), helpstring("method OnAirlineArrivedEvent")]
            HRESULT OnAirlineArrivedEvent([in] BSTR bstrAirlineName,
                                          [in] BSTR bstrArrivalTerminal);
};

....

coclass AirlineArrivalPager
{
    [default] interface IAirlineArrivalPager;
    [default, source] dispinterface _IAirlineArrivalPagerEvents;
};

注意第2个接口 声明的是 source的

.......
STDMETHODIMP CAirlineArrivalPager::AddArrivalDetails(
             BSTR bstrAirlineName,BSTR bstrArrivalTerminal)
{
    // Notify all subscribers that an Airline has hit the tarmac
    Fire_OnAirlineArrivedEvent(bstrAirlineName,bstrArrivalTerminal);

    // Return the status to the caller
    return S_OK;
}
.......
这个是第一个接口的实现。
其中Fire_OnAirlineArrivedEvent就是那个触发函数,它遍历连接点的map,并调用其中
注册的sink的OnAirlineArrivedEvent函数,而它是帮助代理类通过继承IConnectionPointImpl
由atl自动生成的

如果是一个c++程序员,要做的就是通过一个AirlineArrivalPager对象获取连接点,然后通过advise
来将自己的sink的IUnknown接口传入连接点中。

过程对应.net程序员的确是复杂了。

下面就说说简单的,^_^

delegates
事件handle是基于Delegate
// Here's the SayGoodMorning delegate
delegate string SayGoodMorning();

public class HelloWorld
{
   public string SpeakEnglish() {
    return "Good Morning";
   }
  
   public string SpeakFrench() {
        return "Bonjour";
   }
  
   public static void Main(String[] args) {

    HelloWorld obj = new HelloWorld();

    // Associate the delegate with a method reference
    SayGoodMorning english = new SayGoodMorning(obj.SpeakEnglish);
    SayGoodMorning french = new SayGoodMorning(obj.SpeakFrench);

    // Invoke the delegate
    System.Console.WriteLine(english());
    System.Console.WriteLine(french());

  }

}/* end class */

上面是典型的delegate的使用,典型的命令模式

如果一个com支持连接点,那么用tlbimp生成的metadata代理将包含相应的信息

 void subscribePaging() {
  
     // Create an AirlineArrivalPager object
     m_pager = new AirlineArrivalPager();

     // Add the delegate instance that references
     // the OnMyPagerNotify method
     // to the OnAirlineArrivedEvent event list (ICP::Advise)
     m_pager.OnAirlineArrivedEvent +=
       new _IAirlineArrivalPagerEvents_OnAirlineArrivedEventEventHandler(
       OnMyPagerNotify);
        
  }/* end subscribePaging */

订阅连接点的工作在这里只需要简单的把_IAirlineArrivalPagerEvents_OnAirlineArrivedEventEventHandler的代理
实例加入OnAirlineArrivedEvent event 表单。

OnAirlineArrivedEvent和com中的outgoing接口中的方法是一样的名字,代理名称_IAirlineArrivalPagerEvents_OnAirlineArrivedEventEventHandler
通常它会以这样的模式存在InterfaceName_EventNameEventHandler,

这就是要注册一个sink所要做的全部事情,你需要做的只是生成一个组件对象,并加一个delegate 引用到要调用的函数。

如果想注销这个事件,那么简单的remove引用就可以了。

在.NET中使用com collections


经典COM的重用机制
标准的做法是包含和聚合

a.混合模式的继承
  在这种重用模式,你可以让你的.net 类扩展或是继承你的unmanaged COM
  另外,这个managed类有overriding这个COM 接口或是接受COM实现的权利。
  这是一个非常强大的模型,你可以在一个class中混合使用托管和非托管实现

  下面是一个ATL COM
  [
 ....
]
interface IFlyer : IDispatch
{
    [id(1), helpstring("method TakeOff")]
            HRESULT TakeOff([out,retval] BSTR* bstrTakeOffStatus);
    [id(2), helpstring("method Fly")]
            HRESULT Fly([out,retval] BSTR* bstrFlightStatus);
};

[
  .....
]
coclass Flyer
{
   [default] interface IFlyer;
};

下面是实现
STDMETHODIMP CFlyer::TakeOff(BSTR *bstrTakeOffStatus)
{
   *bstrTakeOffStatus =
       _bstr_t(_T("CFlyer::TakeOff - This is COM taking off")).copy();
   return S_OK;
}

STDMETHODIMP CFlyer::Fly(BSTR *bstrFlyStatus)
{
    *bstrFlyStatus =
        _bstr_t(_T("CFlyer::Fly - This is COM in the skies")).copy();
    return S_OK;
}
你还是可以通过tlbimp的方式来生成代理,供托管代码使用

我们可以通过常规的继承机制来通过继承的方式实现托管类,
using System;
using MyFlyerMetadata;

// Inherit from the metadata type representing
// the unmanaged COM component
// Use the COM Component's implementation
// of TakeOff & Fly (Use the base class' implementation)
public class Bird : Flyer
{

}/* end class Bird */

// Inherit from the metadata type representing
// the unmanaged COM component
// Override the COM object's method implementations in our
// derived managed-class.
// (Also call base class implementation when
// necessary using base.MethodName())
public class Airplane : Flyer
{

    // Override the COM Component's Flyer::TakeOff implementation
    // with our own implementation
    public override String TakeOff() {
   
       return "Airplane::TakeOff - This is .NET taking off";
      
    }/* end TakeOff */

    // Override the COM Component's Flyer::Fly implementation
    // with our own implementation
    public override String Fly() {
   
       // Can call the base class' implementation too if you
       // wish to.
       System.Console.WriteLine(base.Fly());
       return "Airplane::Fly - This is .NET in the skies";
      
    }/* end Fly */

}/* end class Airplane */

public class FlightController
{
    public static void Main(String[] args)
    {
        Bird falcon = new Bird();
        System.Console.WriteLine("BIRD: CLEARED TO TAKE OFF");
        System.Console.WriteLine(falcon.TakeOff());
        System.Console.WriteLine(falcon.Fly());

        Airplane skyliner = new Airplane();
        System.Console.WriteLine("AIRPLANE: CLEARED TO TAKE OFF");
        System.Console.WriteLine(skyliner.TakeOff());
        System.Console.WriteLine(skyliner.Fly());

    }/* end Main */

}/* end FlightController */

有一点必须声明,在托管代码中使用非托管com中的类,要重载它的方法,必须
同时重载它所有的方法,不能只重载一个。

这个就是混合模式的继承


混合模式的包含
在这个托管类中使用了和经典COM的包含机制。
它就是把一个metadata 代理类作为一个成员。
如果它需要这个com组件的服务的时候,它就直接把请求发送给这个组件的方法

这个托管类需要做的就是在调用这个包含的com组件方法的前后加入自己的代码。
using System;
using MyFlyerMetadata;

 .....
 
// Contains an instance of the metadata type representing
// the unmanaged COM Component
public class HangGlider
{
    private Flyer flyer = new Flyer();

    // Forward the call to the contained class' implementation
    public String TakeOff()
    {
      return flyer.TakeOff();
    }

    // Forward the call to the contained class' implementation
    public String Fly()
    {
       // Do what you need to do before or after fowarding the
       // call to flyer
       System.Console.WriteLine("In HangGlider::Fly - " +
                                "Before delegating to flyer.Fly");
       return flyer.Fly();
    }

}/* end class HangGlider */

public class FlightController
{
    public static void Main(String[] args)
    {
        ....

        HangGlider glider = new HangGlider();
        System.Console.WriteLine("HANGGLIDER: CLEARED TO TAKEOFF");
        System.Console.WriteLine(glider.TakeOff());
        System.Console.WriteLine(glider.Fly());
       
    }/* end Main */

}/* end FlightController */

 

在.NET 应用程序中理解com的线程模型和套间
// Set the client thread's ApartmentState to enter an STA
Thread.CurrentThread.ApartmentState = ApartmentSTate.STA;

// Create our COM object through the Interop
MySTA objSTA = new MySTA();
objSTA.MyMethod()

作为另外一种做法,你可以在你的入口点函数中直接声明套间类型。
public class HelloThreadingModelApp {

   .....

   [STAThread]
   static public void Main(String[] args) {
 
        System.Console.WriteLine("The apartment state is: {0}",
                   Thread.CurrentThread.ApartmentState.ToString());
  
   }/* end Main */

}/* end class */
如果不设置,默认的套间类型为MTA

2.在COM中调用.NET 组件
通过com interop和通过framework的工具,可以让com和.net组件交互,就像com和com交互一样

首先只有public 的类才能被com的客户度调用。
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;

public enum WeatherIndications
{
   Sunny = 0,
   Cloudy,
   Rainy,
   Snowy
}

[ClassInterface(ClassInterfaceType.AutoDual)]
public class TemperatureComponent
{
   private float m_fTemperature = 0;
  
   // Public Constructor
   public TemperatureComponent()
   {
      m_fTemperature = 30.0f;
   }

   //Public Property Accessors (Defines get/set methods)
   public float Temperature
   {
      get { return m_fTemperature; }
      
      set { m_fTemperature = value;}
     
   }/* end Temperature get/set property */
  

   // Public Method that displays the Current Temperature
   public void DisplayCurrentTemperature()
   {
      String strTemp = String.Format("The current " +
                       "temperature at Marlinspike is : " +
                       "{0:####} degrees fahrenheit",
                       m_fTemperature);
               
      MessageBox.Show(strTemp,"Today's Temperature");
     
   }/* end DisplayCurrentTemperature */

   // Another public method that returns an enumerated type
   public WeatherIndications GetWeatherIndications()
   {
      if(m_fTemperature > 70) {
     
        return WeatherIndications.Sunny;
      }
      else {
     
        // Let's keep this simple and just return Cloudy
        return WeatherIndications.Cloudy;
      }
     
   }/* end GetWeatherIndications */
  
  
}/* end class Temperature */

注意,类有个标签 ClassInterface并且被设置为ClassInterfaceType.AutoDual。
这个可以看作一个将.net组件的public 成员导出成为一个默认的类接口

使用interface来导出类接口不是推荐的做法

下面就生成类型库和注册这个程序集
我们生成的.net assembly 对于COM客户端是不认识的,我们需要把它转换成更加友好的类型
这里我们使用tlbexp或是regasm.exe工具,对于regasm工具,它不仅可以做转换工作,同时它还可以用
来完成注册工作,

转换
regasm Temperature.dll /tlb:Temperature.tlb

这个命令同时完成了注册的工作

下面是在vb 6.0中 使用这个客户端
这里创建组件的工作和一般的com的创建过程是一样的
你可以早期绑定这个控件,也可以通过createobject来实现晚期绑定
Private Sub MyButton_Click()

  On Error GoTo ErrHandler

  ' Create an instance of the temperature component
  Dim objTemperature As New TemperatureComponent

  ' Display the current temperature
  objTemperature.DisplayCurrentTemperature

  ' Set the temperature property
  objTemperature.Temperature = 52.7

  ' Display the current temperature after property mutation
  objTemperature.DisplayCurrentTemperature
 
  ' Check the weather indications
  If (objTemperature.GetWeatherIndications() = _
                     WeatherIndications_Sunny) Then
     MsgBox "Off to the beach"
  Else
     MsgBox "Stay at home and watch Godzilla on TV"
  End If

  Exit Sub
 
ErrHandler:

  MsgBox "Error Message : " & Err.Description, _
           vbOKOnly, "Error Code " & CStr(Err.Number)
End Sub

在注册表中 HKCR/CLSID/{...Component's CLSID...}中InprocServer32中标明的
dll路径指向的是mscoree.dll,这个是.NET runtime,在这个目录下还有class,assembly和
RuntimeVersion, com runtime在调用的时候,它先会调用mscoree.dll的DllGetClassObject入口点
函数,这个runtime使用传入给dllgetclassobject的classID来在assemble和class keys(在Inprocserver32)中加载
这个程序集,让这个程序集来为这个请求服务。runtimes为每个.net 组件创建一个CCW。这个对象将会是
调用透明化。

如果没有标记 ClassInterface 属性,一个默认的类接口同样会生成,而在这种情况下,基于idispatch 的接口
是不会包含任何关于方法类型信息和DISPIDs,这种类型的类接口不能使用晚期绑定的客户端。


 

 

 

 

 

 

  


 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C#可以通过调用WebService来实现与WebService的交互。首先,在C#中需要引入System.Web.Services命名空间,并创建一个Web引用来连接到WebService。可以使用Visual Studio的“添加服务引用”功能来实现这一步骤。引用提供了一个示例代码,展示了如何在C#中创建一个WebService,并调用其中的方法。 在C#中,可以使用WebClient类或HttpWebRequest类来发送HTTP请求并接收WebService的响应。可以通过调用WebService的方法名和参数来进行交互。WebService的方法返回的是字符串或其他数据类型,可以根据需要进行处理。引用提供了一个示例代码,展示了如何在C#中调用WebService的方法。 具体来说,在C#中与WebService交互的步骤如下: 1. 创建一个Web引用,连接到WebService。 2. 实例化WebService的代理类,以便可以调用WebService的方法。 3. 调用WebService的方法,并传递所需的参数。 4. 处理WebService的响应,根据需要进行相应的操作。 需要注意的是,调用WebService的方法需要遵循WebService的接口和参数要求,以确保数据的正确传输和处理。同时,也需要注意Web引用的安全性设置,以确保与WebService的通信是安全可靠的。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [C#调用webservice接口的最新方法教程](https://download.csdn.net/download/weixin_38680811/12769756)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [C# 创建WebService](https://blog.csdn.net/u012563853/article/details/124785218)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [【C# & WebService】【1】认识WebService的交互方式](https://blog.csdn.net/Simpson_/article/details/126371471)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值