Interop(交互) Between C# and C++ 研究三

54 篇文章 0 订阅

有了这一个想法后,又做了一个简单的Demo:

(1)在原来的Atltest项目里面添加新类TestSecond,并且为之添加连接点.

(2)为类TestSecond添加接口RaiseEvent,AtlTest.idl文件中,代码如下:

[id(1), helpstring("方法RaiseEvent")] HRESULT RaiseEvent([in] LONG a);

 在TestSecond.cpp文件中添加如下代码:

 

STDMETHODIMP CTestSecond::RaiseEvent(LONG a)

{

// TODO: 在此添加实现代码

 if(3==a)

 {

Program_List *pList=NULL;

pList=(Program_List*)CoTaskMemAlloc(sizeof(Program_List));

pList->aBstr=SysAllocString(TEXT("abcdef"));

pList->check_offset2=34;

pList->check_size=23;

pList->file_size=12;

pList->file_type=1;

pList->pid=1;

this->Fire_OnProgramList(pList);

 }

 return S_OK;

}

 

此方法用于引发事件,当输入参数a==3时引发OnProgramList事件,然后把Program_List的指针做为事件的参数传出去.

 

(3)_ITestSecondEvents(自动生成的)添加接口,AtlTest.idl文件中,代码如下:

dispinterface _ITestSecondEvents

{

properties:

methods:

[id(1), helpstring("方法OnProgramList")] HRESULT OnProgramList([in]struct Program_List* pList);

};

 

(4)为类TestSecond添加连接点,然后自动生成一个引发事情时,用于回调方法的代理类:CProxy_ITestSecondEvents

 

上面这一个项目完成后,我还先写一个C++的测试程序,用于测试上面的程序的成功性:

Project:cpptest1

Files:

--------新添类:TestSecondEvent---------

TestSecondEvent.h中:

 

#pragma once

#include "stdafx.h"

 

extern _ATL_FUNC_INFO FuncOnProgramList;

 

class TestSecondEvent:public IDispEventSimpleImpl<1,TestSecondEvent,&__uuidof(_ITestSecondEvents)>

{

public:

BEGIN_SINK_MAP(TestSecondEvent)

SINK_ENTRY_INFO(1,__uuidof(_ITestSecondEvents),0x1,OnProgramList,&FuncOnProgramList)

END_SINK_MAP()

STDMETHOD(OnProgramList)(Program_List* pList);

public:

TestSecondEvent(void);

public:

virtual ~TestSecondEvent(void);

};

TestSecondEvent.cpp中,

#include "StdAfx.h"

#include "TestSecondEvent.h"

 

_ATL_FUNC_INFO FuncOnProgramList={CC_STDCALL, VT_EMPTY,1,VT_I4};

 

TestSecondEvent::TestSecondEvent(void)

{

 

}

 

TestSecondEvent::~TestSecondEvent(void)

{

}

 

HRESULT TestSecondEvent::OnProgramList(Program_List *pList)//事件响应时回调的方法

{

_bstr_t t(pList->aBstr,false);//第一次运行的错误的地方

std::cout<<t<<std::endl;

CoTaskMemFree(pList);

return 0;

}//TestSecondEvent.cpp文件结束

-------cpptest1.cpp的main函数中----------------

CoInitialize(NULL);

{

CComPtr<ITestSecond> pTestSecond;

HRESULT hr=pTestSecond.CoCreateInstance(__uuidof(TestSecond),NULL,CLSCTX_SERVER);

if(FAILED(hr))

{

cout<<"create com instance error"<<endl;

getchar();

return 0;

}

TestSecondEvent secondEvent;

secondEvent.DispEventAdvise(pTestSecond);//建立与事件源的连接

 

pTestSecond->RaiseEvent(3);

}

CoUninitialize();

getchar();

return 0;

TestSecondEvent是用于连接源的类,更具体的麻烦兄弟自己看书,会讲得更具体一些.

最后运行测试的程序,会发现错误发生在_bstr_t t(pList->aBstr,false);然后调试运行可以看到在函数HRESULT TestSecondEvent::OnProgramList(Program_List *pList)中,

pList=(Program_List*)CoTaskMemAlloc(sizeof(Program_List));时,pList的指针是0xbaadf00d,而当响应事件的回调函数HRESULT TestSecondEvent::OnProgramList(Program_List *pList)是,传入的参数pList的值却变成了0xccccffff.而这一个值的Program_List的指针里面没有任何数据,所以在生成_bstr_t时显然出错.(上面函数的详细代码可参考上面)查看原来是在自动生成的代理类_ITestSecondEvents_CP中.原来自动生成的代码如下:

#pragma once

 

template<class T>

class CProxy_ITestSecondEvents :

public IConnectionPointImpl<T, &__uuidof(_ITestSecondEvents)>

{

public:

HRESULT Fire_OnProgramList( Program_List * pList)

{

HRESULT hr = S_OK;

T * pThis = static_cast<T *>(this);

int cConnections = m_vec.GetSize();

 

for (int iConnection = 0; iConnection < cConnections; iConnection++)

{

pThis->Lock();

CComPtr<IUnknown> punkConnection = m_vec.GetAt(iConnection);

pThis->Unlock();

 

IDispatch * pConnection = static_cast<IDispatch *>(punkConnection.p);

 

if (pConnection)

{

CComVariant avarParams[1];

avarParams[0] = pList;//注意这里,他将被默认以VT_BOOL传递

CComVariant varResult;

 

DISPPARAMS params = { avarParams, NULL, 1, 0 };

hr = pConnection->Invoke(1, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &params, &varResult, NULL, NULL);

}

}

return hr;

}

};

 

看到上面的注释,经过调试运行我发现在行avarParams[0]=pList;这一行的时候,被以VT_BOOL参数传递.经过几次尝试,我把这一句改成:

//avarParams[0] = pList;

avarParams[0].byref = pList;

avarParams[0].vt = VT_I4;

生成后再次运行,得到正确的指针值.运行成功,但却留下一个猜想:自动生成的代码对自己定义结构类型(Program_List*)的支持很不好.这也是下文生成.Net调用的封装DLL作一下铺垫吧.

 

在上面运行成功后,我又用iblimp生成.Net平台下调用的封装的DLL,发现这一工具还真强大,AtlTest.dll自动生成了OnProgramList事件.测试的代码如下:

    static void Main(string[] args)

    {

      AtlTestLib.TestSecond testSecondClass = new AtlTestLib.TestSecond();

      testSecondClass.OnProgramList += new AtlTestLib._ITestSecondEvents_OnProgramListEventHandler(testSecondClass_OnProgramList);

 

      try

      {

        testSecondClass.RaiseEvent(3);

      }

      catch (Exception exc)

      {

        Console.WriteLine(exc.Message);//没有出错,也没有异常

      }

      Console.Read();

    }

 

    static void testSecondClass_OnProgramList(ref AtlTestLib.Program_List pList)

    {//在这里设断点,发现跟本没有运行到这里

      Console.WriteLine(pList.aBstr);

    }

运行结果发现没有错误,也没有异常,就是没有响应事件.这里正好呼应了我前面的猜想自动生成的代码对自己定义结构类型(Program_List*)的支持很不好,所以tblimp自动生成的DLL无法建立与事件源的连接.

 

Reflector.exe查看AtlTestLib的源码,很容易发现这一段代码:

[ComVisible(false), TypeLibType((short) 0x10)]

public delegate void _ITestSecondEvents_OnProgramListEventHandler([In] ref Program_List pList);

其实这一段代码也不算错,但一写AtlTest项目中的代理类CProxy_ITestSecondEvents比较就会发现问题,

//avarParams[0] = pList;

avarParams[0].byref = pList;

avarParams[0].vt = VT_I4;

 这三行代码是关健的地方,我把接口的第一个参数以VT_I4方式传递,即最后应该是int32类型的.C++的测试项目中用的是Program_List*指针做为参数,所以VT_I4还是可以直接转成Program_List*指针的.但在C#测试项目中,ref Program_List结构做为连接点的参数,其不能直接把VT_I4直接转成Program_List结构,所以最后事件没有响应.解决方法有两个,一个是在CProxy_ITestSecondEvents的给avarParams[0]设置正解的参数值,让其能转化成Program_List.但可惜我试了很多种VT_***,但最后都没成功,也许是我自己写错了.最后选择第二种方法,VT_I4,而写自己的封装项目,int32作为连接参数,然后再转换成Program_List的结构.

以下是我自已写的一个用于封装AtlTest项目,tlbimp工具自动生成的代码有好多个地方有区别,大家可以区别一下.其实原本也想直接复制自动成生AtlTestLib的装封方法的,不过后来出现几个错误不好解决,所以放弃了.大家可以用Refrector.exe来查看其源码,看看其中的封装方法,然后自己实现一下.我也想看一看.闲话少说,下面是源代码:

 


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

namespace AtlTestCS
{
  
#region extern internface//在导出类型定义的时候,注意GUID要与COM项目中的GUID一致
  [StructLayout(LayoutKind.Sequential, Pack 
= 4), Guid("581B648D-1A8F-4A2E-81F3-1684799966E8")]
  
public struct Program_List//结构
  {
    [MarshalAs(UnmanagedType.BStr)]
    
public string aBstr;
    
public int pid;
    
public int file_type;
    
public int file_size;
    
public int check_size;
    
public int check_offset2;
  }

  [ComVisible(
false)]
  
public delegate void _ITestSecondEvents_OnProgramListEventHandler([In] int pList);//注意这里是最终实现的事件的委托,用的是int

  [ComImport, Guid(
"E8A7D51A-3B86-4F13-9C45-261CA94A9A64")]//TestSecond类
  public class TestSecond
  {
//它里面不用写东西
  }

  [ComImport, Guid(
"FCF8E1D1-AC4E-4A7A-8858-AE13E40536A6"), TypeLibType((short)0x10c0)]
  
public interface ITestSecond//TestSecond的接口
  {
    [DispId(
1)]
    
void RaiseEvent([In] int a);
  }

  [ComImport, InterfaceType((
short)2), TypeLibType((short)0x1000), Guid("161F5D90-83CF-41B8-A300-BA5F5C0393A3")]
  
public interface _ITestSecondEvents//事件
  {
    [DispId(
1)]
    
void OnProgramList([In] int pList);
  }
  
#endregion

  
#region SinkObject
  [TypeLibType(TypeLibTypeFlags.FHidden), ClassInterface(ClassInterfaceType.None)]
  
public sealed class _ITestSecondEvents_SinkHelper : _ITestSecondEvents
  {
//搜索sink的帮助类
    
// Fields
    public int m_dwCookie = 0;
    
public _ITestSecondEvents_OnProgramListEventHandler m_OnProgramListDelegate = null;

    
// Methods
    internal _ITestSecondEvents_SinkHelper()
    {
    }

    
public void OnProgramList(int listRef1)
    {
      
if (this.m_OnProgramListDelegate != null)
      {
        
this.m_OnProgramListDelegate(listRef1);
      }
    }
  }
  
#endregion

  
#region EventProvider
  
internal sealed class _ITestSecondEvents_EventProvider : IDisposable
  {
//提供事件的连接
    
// Fields
    private ArrayList m_aEventSinkHelpers;
    
private IConnectionPoint m_ConnectionPoint;
    
private IConnectionPointContainer m_ConnectionPointContainer;

    
// Methods
    public _ITestSecondEvents_EventProvider(object obj1)
    {
      
this.m_ConnectionPointContainer = (IConnectionPointContainer)obj1;
    }

    
public event _ITestSecondEvents_OnProgramListEventHandler OnProgramList
    {
      add
      {
        
lock (this)
        {
          
if (this.m_ConnectionPoint == null)
          {
            
this.Init();
          }
          _ITestSecondEvents_SinkHelper helper 
= new _ITestSecondEvents_SinkHelper();
          
int pdwCookie = 0;
          
this.m_ConnectionPoint.Advise((object)helper, out pdwCookie);
          helper.m_dwCookie 
= pdwCookie;
          helper.m_OnProgramListDelegate 
= value;
          
this.m_aEventSinkHelpers.Add((object)helper);
        }
      }
      remove
      {
        
lock (this)
        {
          
if (this.m_aEventSinkHelpers != null)
          {
            
int count = this.m_aEventSinkHelpers.Count;
            
int index = 0;
            
if (0 < count)
            {
              
do
              {
                _ITestSecondEvents_SinkHelper helper 
= (_ITestSecondEvents_SinkHelper)this.m_aEventSinkHelpers[index];
                
if ((helper.m_OnProgramListDelegate != null&& helper.m_OnProgramListDelegate.Equals(value))
                {
                  
this.m_aEventSinkHelpers.RemoveAt(index);
                  
this.m_ConnectionPoint.Unadvise(helper.m_dwCookie);
                  
if (count <= 1)
                  {
                    Marshal.ReleaseComObject(
this.m_ConnectionPoint);
                    
this.m_ConnectionPoint = null;
                    
this.m_aEventSinkHelpers = null;
                  }
                  
break;
                }
                index
++;
              }
              
while (index < count);
            }
          }
        }
      }
    }

    
public  void Dispose()
    {
      
this.Finalize();
      GC.SuppressFinalize(
this);
    }

    
public  void Finalize()
    {
      Monitor.Enter(
this);
      
try
      {
        
if (this.m_ConnectionPoint != null)
        {
          
int count = this.m_aEventSinkHelpers.Count;
          
int num2 = 0;
          
if (0 < count)
          {
            
do
            {
              _ITestSecondEvents_SinkHelper helper 
= (_ITestSecondEvents_SinkHelper)this.m_aEventSinkHelpers[num2];
              
this.m_ConnectionPoint.Unadvise(helper.m_dwCookie);
              num2
++;
            }
            
while (num2 < count);
          }
          Marshal.ReleaseComObject(
this.m_ConnectionPoint);
        }
      }
      
catch (Exception)
      {
      }
      
finally
      {
        Monitor.Exit(
this);
      }
    }

    
private void Init()
    {
      IConnectionPoint ppCP 
= null;
      
byte[] b = new byte[] { 0x900x5d0x1f0x160xcf0x830xb80x410xa300xba0x5f0x5c30x930xa3 };
      Guid riid 
= new Guid(b);
      
this.m_ConnectionPointContainer.FindConnectionPoint(ref riid, out ppCP);
      
this.m_ConnectionPoint = ppCP;
      
this.m_aEventSinkHelpers = new ArrayList();
    }
  }
  
#endregion

  
#region TestSecondClass
  
public class TestSecondClass//生成TestSecond类的代理,以提供统一的接口调用
  {
    
private ITestSecond mTestSecond;
    
private _ITestSecondEvents_EventProvider mTestSecondEventProvider;

    
public TestSecondClass()
    {
      mTestSecond 
= new TestSecond() as ITestSecond;
      mTestSecondEventProvider 
= new _ITestSecondEvents_EventProvider(mTestSecond);
    }

    
public event _ITestSecondEvents_OnProgramListEventHandler OnProgramList
    {
      add
      {
        mTestSecondEventProvider.OnProgramList 
+= value;
      }
      remove
      {
        mTestSecondEventProvider.OnProgramList 
-= value;
      }
    }

    
public void RaiseEvent(int a)
    {
      mTestSecond.RaiseEvent(a); 
    }
  }
  
#endregion

}

------------Main()函数中---------------

   AtlTestCS.TestSecondClass testSecondClass 
= new AtlTestCS.TestSecondClass();
      testSecondClass.OnProgramList 
+= new AtlTestCS._ITestSecondEvents_OnProgramListEventHandler(testSecondClass_OnProgramList);
      testSecondClass.RaiseEvent(
3);
      Console.Read();

事件对应的函数
    
static void testSecondClass_OnProgramList(int iList)
    {
      IntPtr pList 
= (IntPtr)iList;
      AtlTestCS.Program_List programList 
=(AtlTestCS.Program_List)Marshal.PtrToStructure(pList, typeof(AtlTestCS.Program_List));
      Console.WriteLine(programList.aBstr); 
      Marshal.FreeCoTaskMem(pList);

    }

 

最后运行成功.不过到这里,已基本实现了我最初的设想.C++实现COM,然后通过事件通知UI层更新界面,但我心理还是很不爽.因为为了实现这么一个小小的功能,得自己写这么多的代码.如果的在项目当中使用的话,那不得写多少无用功的代码呀?为什么弃tblimp.exe不用呢?所以最后想到别一个解决方法,也许不会很让大家满意,但我是觉得可以实现心中想要的结果便可.下面是简单的说明这一个实现:

 

STDMETHODIMP CTestSecond::RaiseEvent(LONG a)

{

// TODO: 在此添加实现代码

 if(3==a)

 {

Program_List *pList=NULL;

pList=(Program_List*)CoTaskMemAlloc(sizeof(Program_List));

pList->aBstr=SysAllocString(TEXT("abcdef"));

pList->check_offset2=34;

pList->check_size=23;

pList->file_size=12;

pList->file_type=1;

pList->pid=1;

LONG lptrList=(LONG)pList;//也许只写这么一点大家便明白了,直接把指针转化为int32,激发事件

this->Fire_OnProgramList(lptrList);

 }

 return S_OK;

}

而对应的事件接口也改成:

dispinterface _ITestSecondEvents

{

properties:

methods:

[id(1), helpstring("方法OnProgramList")] HRESULT OnProgramList([in]LONG lptrList);

};

最后通过tlbimp.exe生成的事件委托也对应为:

[ComVisible(false), TypeLibType((short) 0x10)]

public delegate void _ITestSecondEvents_OnProgramListEventHandler([In] int lptrList);

然后再通过以下代码:

            IntPtr pList = (IntPtr)lptrList;

      AtlTestCS.Program_List programList =(AtlTestCS.Program_List)Marshal.PtrToStructure(pList, typeof(AtlTestCS.Program_List));

      Console.WriteLine(programList.aBstr);

      Marshal.FreeCoTaskMem(pList);

分析:最后这一解决方案其实也差很多,因为不管如何如果让别人用的话总觉得很别扭.因为别人还得理解为什么还得把int lptrList转化成IntPtr,再通过Marshal转化得到Program_List.对于面对对象来说,这不免也让别人知道得太多了?所以还可以作一点相应的封装,让用户看到的只是Program_List的参数.而如果直接能在CProxy_ITestSecondEvents中设置好转换的参数的话,就免去很多烦麻,等别人帮我解答了...

总结:这一篇文章主要是讨论如何让传统的C++.Net平台的交互问题.包括简单的交互.直接写C++组件,而不是通过COM相应的接口.这种情况下全部得自己写封装组件,不能用tlbimp.exe生成,因为它不是标准的接口.而其对面对对象的支持不够好.如以前两篇文章所说,最后都得在C#中转化成static函数.然后还有不知道在自己写的COM组件中实现事件.而用C++ATL/COM项目的话,前面的几个问题都好解决.虽然不能在其中定义灵活的方法,比如可以有返回值,但其很容易实现类的生成,事件的生成,且还有方便的工具tlbimp.exe.只是有一个问题是对自定义的结构的支持不够好.

最后简单说一说这一篇文章的起因.是因为其中遇到好几个问题都不能在百度,google,codeproject中找到很好的解答.比如如何自己写对COM事件的封装程序,ATL开发中把结构做为函数的参数等.而原本在网上讨论C++C#的交互也比较少.所以我想把它分享出来,以让大家可以一起讨论.其实好多地方我也没有找到好的方法,只是基本可以解决,所以希望能得到指点.同时也算是一篇日记吧.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值