一个技术研究工作,需要使用native代码构建一个dll,并实现其中的函数:
HRESULT WMCreateStreamForURL( LPCWSTR pwszURL,
BOOL* pfCorrectSource,
IStream** ppStream
)
因为未来项目准备使用C#编程,所以很自然想到了C++/CLI来实现这个dll,并且将调用转发到C#的实现里。做起来很容易,我用C#实现了一个StreamManager,负责检查传入的url并返回相应的IStream实现。
这里环境为VS2005。用C#实现的工程叫做protocol,用C++/CLI实现的工程名字为protocol_stub(必须使用/cli参数编译)。
首先遇到的是C++/CLI中的托管代码和非托管代码的相互调用问题,查阅了一下帮助,发现使用#pragma managed和#pragma unmanaged就可以划定函数的编译方式到底是native还是cli。
其次,在C++中调用StreamManager时候报告找不到。经过检查,发现要在C++工程中添加对protocol的引用,同时,要在C++代码的managed部分使用指令
#pragma managed
#import "protocol.tlb" raw_interfaces_only
using namespace protocol;
using namespace System;
using namespace System::Runtime::InteropServices;
这样才可以正常编译代码StreamManager^ mgr = StreamManager::Instance;其中,protocol.tlb是用VS自带工具TlbExp生成的。
第三个问题是导出.net对象的问题。两个工程可以正常编译,但是测试运行时候却报告异常。经过检查,原来是System::Runtime::InteropServices::ComTypes::IStream虽然就是IStream的实现,但是却无法进行强制转换。用如下的代码虽然编译可以过去,但是不能工作
ComTypes::IStream^ s = mgr->getStream(url);
cli::pin_ptr<ComTypes::IStream^> p = &s;
*ppStream = (IStream*)p;
经过查询,发现解决的方法很简单,代码如下:
ComTypes::IStream^ s = mgr->getStream(url);
IntPtr^ handle = Marshal::GetComInterfaceForObject(s, ComTypes::IStream::typeid);
*ppStream = (IStream*)handle->ToPointer();
这样,整个工作顺利完成。
总结:C++/CLI经过vs2003之后变动很大,从丑小鸭终于变成了白天鹅,不仅实现了C++和CLI的无缝整合,而且保留了原先C++语法的优美和简洁。因为可以支持本地代码和CLI代码的混合调用,不仅可以充分利用Windows系统和.net的资源,而且因为本地代码的不可反编译性,使得C++/CLI天生就有了安全保密性。看来要在项目中的一些关键算法部分使用C++了。
另外,几个要注意的地方:
C#中xxx.getType()或者typeof(ClassXXX)在C++/CLI中对应代码是 ClassXXX::typeid
在C++/CLI中#import 比较重要,要注意
在C#中,所有对象天生就是COM对象(好在我一直没有弄错)