在进行这一个研究前,首先得有一个思想:语言只是一个工具,不同的语言在不同的平台与环境下发挥不同的作用,而你的权利是在选择.有一次我要开发一个关于视频点播系统(事实上这一个系统没什么用处)的时候,发现C#不是很合适进行对Directshow的开发.一个方面是directx在C#环境下对Directshow的支持比较少,另一个方面是在存在Directshow.net(一个开源项目,是把directshow封装在.Net环境下调用)情况下,如果你想用它来写SourceFilter的话还是显得有点困难,因为你不是很容易调用系统较底层一点的API,都得用C#封装一下很麻烦.然后后面写这一个文章的动机可能是我自己个人的问题,我总是认为用C++做出来的界面难度大且不好看,但对C#来说就会很不一样,想开发成像vs2005界面那样左右都有可隐藏的窗口都要比用C++开发一个简单界面容易些.所以最后面这一个系统的开发方案是用C++实现directshow的source filter与网络通信,然后通过COM技术封装成一个Player.dll让C#调用.而在此过程当中会遇到很多C++与C#的通过COM实现相互调用的问题,我便在此总结了一下我的研究结果,有的也许也没有最终最好的答案,有的研究也许在这里只到一半便放弃了,总之这里是我在寻找思路与解决方案的过程.
有了这一个方案后先开始写一个小小的test:
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
Code
项目名:InteropWithCSharpAndCPP
file1.h:
#pragma once
#ifdef __WinTest_C_DLL
#else
#define __WinTest_C_DLL extern "C" _declspec(dllimport)
#endif
__WinTest_C_DLL int add(int a,int b);
file1.cpp:
#include "file1.h"
int add(int a,int b)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
return a+b;
}
然后再写了一个C++ test 项目:cpptest1
cpptest1.cpp:
#include "stdafx.h"
#include "file1.h"
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
cout<<add(4,2)<<endl;
getchar();
return 0;
}
引用InteropWithCSarpAndCPP.lib与文件file1.h总后输出6测试成功.
然后又再写了一个C#的测试项目:charptest1
Program.cs:
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
namespace charptest1
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
class Program
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
[DllImport("InteropWithCSharpAndCPP.dll")]
public static extern int add(int a, int b);
static void Main(string[] args)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
Console.WriteLine(add(2, 4));
Console.Read();
}
}
}
同时把InteropWithCSharpAndCPP.dll放到charptest1 bin 目录下,也测试成功.
第一个测试成功,心想这事应该还比较好办.但接着一个问题,就是类的问题,最终Player.dll肯定不可能都用这一种方法的方式给C#调用吧?便开始了第二个简单测试,也就是上面的一个升级版本.突然一想在C#当中调用C++里面的类,还真不太好办,因为DllImport还真不能直接把一个类extern出来,不过后面想到这一种最简单的方法:
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
Code
Project:WinTest3
Files:
Math.h
#pragma once
extern "C" _declspec(dllimport) class Math
{
public:
static Math* instance();
int sub(int a,int b);
static void deleteMath(void *);
void output(int a,int b);
private:
Math(void);
virtual ~Math(void);
};
Math.cpp
#include "Math.h"
#include <Windows.h>
#include <iostream>
#include <strstream>
#include <sstream>
using namespace std;
Math::Math(void)
{
}
Math* Math::instance()
{
return new Math();
}
void Math::deleteMath(void * m)
{
Math * mIn=(Math*) m;
delete mIn;
}
int Math::sub(int a,int b)
{
return a-b;
}
void Math::output(int a,int b)//当时随兴突然想在GUI下测试一下
{
HWND hwnd=GetForegroundWindow();
HDC hdc=GetDC(hwnd);
wostringstream myString;
myString<<"x="<<a<<","<<"y="<<b<<endl;myString.str();
TextOut(hdc,0,0,myString.str().c_str(),wcslen(myString.str().c_str()));
ReleaseDC(hwnd,hdc);
}
Math::~Math(void)
{
}
WinTest_C.def
LIBRARY "WinTest3"
EXPORTS
add @1
?sub@Math@@QAEHHH@Z @2
?instance@Math@@SAPAV1@XZ @3
?deleteMath@Math@@SAXPAX@Z @4
?output@Math@@QAEXHH@Z @5
千万别把上面的文件名,类名与方法名以某种联系,其实都是我当时突然想出来做一个测试用的.而文件WinTest_C.def中变形后的方法名是在文件WinTest3.map中找到的,你可以在项目中生成此文件的选项.
然后在C#项目中做一个测试:
Project:WinInterop
Files:
NMath.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
namespace WinInterop
{
class NMath
{
[DllImport("WinTest3.dll", EntryPoint = "?sub@Math@@QAEHHH@Z", CallingConvention = CallingConvention.ThisCall)]
public static extern int sub(IntPtr the, int a, int b);
[DllImport("WinTest3.dll", EntryPoint = "?instance@Math@@SAPAV1@XZ", CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr CreateMath();
[DllImport("WinTest3.dll", EntryPoint = "?deleteMath@Math@@SAXPAX@Z", CallingConvention = CallingConvention.StdCall)]
public static extern void DeleteMath(IntPtr instance);
[DllImport("WinTest3.dll", EntryPoint = "?output@Math@@QAEXHH@Z", CallingConvention = CallingConvention.ThisCall)]
public static extern void output(IntPtr instance, int a, int b);
}
}
Form1部分:
private void button1_Click(object sender, EventArgs e)
{
IntPtr instance =NMath.CreateMath();
NMath.output(instance, 10, 10);
NMath.DeleteMath(instance);
}
这一个测试成功,界面上能看到输出的结果.
看到上面这一段代码后,相信很多的朋友都会觉得很别扭,觉得真正实现当中怎么可能用这一种方法.首先说为什么我会用Math* Math::instance()这一种方法来生成类吧.因为在C#项目中,我不知道如何extern出Math的构造方法去生成类.对应的void Math::deleteMath(void * m) 也是一样的原因啦.然后说到WinTest_C.def的方法名,里面很多变异后的方法名是在.map文件当中找到的,并且你每添加一个方法都得重新生成并找出来,添加到.def文件当中,如果你想修改方法名的话更加烦麻.然后还有NMath里面那么多的static,entrypoint的.这不是一个开发人员应该用的方法,代码写得多,没有结构,并且都还是做一些低级的开发工作,如查找无聊的方法名,当时就对这一种方法脑火了.
上一个方法失败后开始想到IDispatch,IUnKnown等COM技术.但又不想自己全部去实现所有的这一些接口,然后想到用ATL项目开发.这样便开始走入正轨.关于COM的细节与ATL的一些知识我这里不能多说,水平也没达到,但我可以说一下我是如何使用ATL一步步实现我预期的方案的.
Interop(交互) Between C# and C++ 研究二