用C#做开发已经好几年了一直用得挺顺手的最近有一个项目需要用到DirectShow的相关COM组件也就是想在C#的项目里面实现一个基于内存流的Filter这个却让我着实头痛了好久
原因就是在C#里面没有现成的DirectShow的COM组件的定义虽说在C#中可以使用一些特定的方式来操作COM组件(C#中COM操作(一)——实例化)可是对于DirectShow这样需要声明一大批的COM接口类型枚举却是一件漫长又枯燥的事件而且搞不好一个不小心中间出个错误导致最后调试总不通过而抓狂即便是现在有了DirectShowNET提供的对绝大部分的DirectShow的C#翻译可是面对网上那么多的C++开源代码还有少得可怜的C#操作DirectShow的代码你会怎么办?难道又是一行一行的翻译成C#吗反正我是不会这样干的于是这种方式首先被毙掉了
俗话说最优秀的程序员也是最懒的程序员类和方法能公用的就尽量公用已存在的实现就不需要自己再去写一次了既然有现成的C++的实现好的代码为什么不可以再次拿来用一用呢可是在C#里面想要使用C++的实现(注意这里我用的是实现并没有指定是类或方法)比较常见的是通过 DllImportAttribute特性导入相应的程序集可是这种方式只可以调用方法而且是限定死了的只能是C语言形式的导出方法对于其它的方法是不可以的以前用的最多的就是对Windows API的调用了这样的使用形式极其不方便C++和C#本都是都面向对象的语言却要通过面向过程式的方法调用来相互通信感觉很别扭也很不爽对于追求完美的程序员来说这也不是一个上上的选择于是这种方式也不是最好的暂时作为候选吧再找找看还有没有其它的方式
后来我想到了以前看过一篇文章说的是在C++里面通过托管C++作为媒价调用C#里的代码把这个反过来就是用C#通过托管C++调用NativeC++ 这样是否也是可行的呢?不过这都是好几年前看到的了只怪那时候对于这种跨平台调用的方式研究不深也不怎么感兴趣所以当时就没有太在意对于文中所说的托管C++的印象也一直停留在中看不中用的程序上如今也只得找来看看虽说信心不大也报着死马当活马医一回的心情试试先
其实前面已经提到过了如果仅仅是对C++的导出方法的调用的话基本不是难事可是C++是面向对象的语言很多情况下都是以类的形式存在的导出的也是 C++的类就拿我找到的C++写的内存流的Filter来说吧网上的代码都是基于类的并且我想在C#里面控制FilterGraph的构建解决怎么在C#里面直接或间接使用到这个类才是问题的本质和关键所在网上找了几份资料也查了MSDN里面都说可以使用托管C++作Wrapper包装 NativeC++然后编译成CLI可识别的托管程序集就可以供C#调用了
class MemFilter : public IBaseFilter
{
public:
private:
}
public ref class MemFilterWrapper : IBaseFilter
{
private:
MemFilter* m_pFilter;
public:
// 此处只只需要把MemFilter类的公有方法用托管方工声明一下在方法的实现中直接调用m_pFilter中相应的成员方法就可以了
}
把这两个类都编译到同一个CLI/C++项目中去这样在C#里面就可以直接使用MemFilterWrapper这个类间接操作C++类了与其它普通的CLS类没什么区别不过需要注意的是对于C++里面的一些基元类型比如intchar*bool等包括数组需要用CLS里面相应的类型去替换然后再对外公布成方法也就是说把所有的与C++特性相关的都封装起来对外只显示符合公共语言规范的格式不然的话在使用的时候比较麻烦(有可能需要使用C#的不安全代码)
就像之前提到的那样我们也可以通过使用托管C++让C#的类披上一层C++的皮再声明成导出类这样就可以供普通C++调用了(前是你必须先安装Framework)
public class CSharpClass // C# 类
{
public void SayHello(){ ConsoleWriteLine(Hello); }
}
class NativeCPP // C++原生类代理注意这里的参数返回值什么的都不能带有托管类型的痕迹
{
public:
void* m_pCSharpClass;
NativeCPP(void);
SayHello(void);
}
NativeCPP::NativeCPP(void)
{
CSharpClass^ obj = new CSharpClass();
GCHandle^ handle = GCHandleAlloc(obj)
m_pCSharpClass = GCHandle::ToIntPtr(handle)ToPointer();
}
NativeCPP::SayHello(void)
{
GCHandle handle = GCHandle::FromIntPtr(m_pCSharpClass);
CSharpClass^ obj = dynamic_cast<CSharpClass^>(handleTarget);
objSayHello();
}
不过这个时候有个问题普通的C++类用new方式构造的实例可以用C++指针持有在C++代码中没有调用delete的时候会一直存在于当前进程的地址空间中这样在后面的使用中不会出现内存访问方面的问题可是从C#到C++的过程就不是这的了在C#中对象是由CLI自己管理的当CLI发现一个对象不有直接或间接的引用存在的时候(也就是所谓的无根对象)就会被回收这里的引用是指的CLI中的引用另外C#的对象如果以引用的形式存在的话那么在原始的C++里面中两个类之间传送这个对象将会变得不可能因为除了托管C++外其它的代码根本不能识别C#引用只认识指针因此我们必须把C# 中的引用转成指针可是如果通过简单的方式一旦转成指针后C#中的对象很可能会在没有引用的时候被GC回收了造成C++代码再去访问的时候出内存导常 MS提供了一个名叫GCHandle的结构来解决这样的问题通过这个结构可以构造一块内存区域代表这个引用对象GC发现有这么一块内存区域存在的时候不管什么情况下都不会对该对象进行回收用用类似的方法也可以从给定的内存指针上返回所指代的托管对象的引用而这块内存本身不受托管代码控制反回的也只是一个指针因此在整个C++的代码生命周期里都不用再担心会引用到无效的C#对象了
这样的处理访问方式保留了C#代码与C++代码各自的书写特别真正做到了互不干挠假如我再从C#换到VBNET也是很easy的事情这正是我想要的结果到这里才发现原来C#与C++互相访问是如些的简单其实现实就是这样你知道的就很简单不知道的就会觉得很难