有了这一个想法后,又做了一个简单的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, ¶ms, &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[] { 0x90, 0x5d, 0x1f, 0x16, 0xcf, 0x83, 0xb8, 0x41, 0xa3, 0, 0xba, 0x5f, 0x5c, 3, 0x93, 0xa3 };
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#的交互也比较少.所以我想把它分享出来,以让大家可以一起讨论.其实好多地方我也没有找到好的方法,只是基本可以解决,所以希望能得到指点.同时也算是一篇日记吧.