2008年(909)
分类:
2008-05-06 22:37:32
下载源代码
一、COM 服务器 COM 客户端
这是传统的 COM 知识,如果对这部分内容不清楚,可以去看
杨老师 的个人专栏,那里有非常棒的教程,我就不在这里废话了^_^
我不细说可并不代表这部分不重要,恰恰相反,如果读者对这部分很熟悉,就会发现后面所有的内容在形式上几乎都是模仿传统的COM调用。
二、COM 服务器 .net 客户端
嗯,这才是重点。下图是这部分的原理。每个COM对象都会有且只有一个运行库可调用包装(RCW)代理,而不管它有多少个引用。
在没有公开接口(或者根本就没有)的情况下
这种情况用到的操作就是P/Invoke。我们至少要知道如下二个内容:
//1.先写好要用到的命名空间 using namespace System::Runtime::InteropServices; typedef IntPtr HWND; //就是非托管类型 void * 啦,win32平台上是4个字节,所以也可以写成int //2.利用DllImport属性(即DllImportAttribute属性类)"#import"导入DLL文件,并标识调用函数 [DllImport("user32", EntryPoint="MessageBoxA")] //3.创建原型,请读者注意数据类型的变化 extern "C" int MsgBox(HWND hWnd,String* pText,String* pCaption,unsigned int uType); //4.调用 MsgBox(this->Handle,"hello","hi",0); //5.包装类这样写,很简单,就不写进提供下载的示例代码了^_^ public __gc class SDKMsgBox: { public: [DllImport("user32", EntryPoint="MessageBoxA")] extern "C" int MsgBox(HWND hWnd,String* pText,String* pCaption,unsigned int uType); ....... }如果传递的值是数组、结构或者类,就没这么简单了,需要自定义封装(即Marshal,进行自定义类型转换)
//对于数组,只需定义一下封送方法即可,就不写入供下载的示例代码了 extern "C" void SendArray( [MarshalAs(UnmanagedType::LPArray)]Array类的传递方法没什么好说的,自然是和结构的传递方法相同。但有一点要注意,传递类时通常要有至少1级间接寻址,即指针(和上例中的Rect一样)。list, int length ); /* 对于结构,比如,User32.dll中的这个函数 BOOL PtInRect(const RECT *lprc, POINT pt); RECT和Point是两个结构 注意,下面Point声明为__value而非__gc,因为.net v1.1的封送处理会出现问题(可能是笔者失误, 也可能是.net v1.1的Bug),不能自动将托管指针所指向的内容复制到非托管堆中(不是指__box打包 功能哦),所以实际使用中没有像上例的参数(String *)那样使用托管指针。 */ //StructLayout即StructLayoutAttribute属性类,是用来定义对象的内存布局的 //Sequential表示对象的成员按照定义的顺序进行内存布局 [StructLayout(LayoutKind::Sequential)] public __value struct Point { public: int x; int y; }; //Explicit表示对象的成员按照FieldOffset(即FieldOffsetAttribute属性类)指定的位置进行内存布局 [StructLayout(LayoutKind::Explicit)] public __gc struct Rect { public: [FieldOffset(0)] int left; //FieldOffset()中的数字是内存布局, [FieldOffset(4)] int top; //一定不能写了,这里都是int,所以每次都 4 [FieldOffset(8)] int right; [FieldOffset(12)] int bottom; }; [DllImport("User32.dll")] extern "C" bool PtInRect(const Rect& r, Point p);//第1个参数定义成托管则规定1级间接寻址要使用引用 /* 如果COM服务器方的参数里有char *text,最好如下定义属性 [StructLayout(LayoutKind::Sequential,CharSet=CharSet::Ansi)] ... ( ... , String *text); 其它的类型依此类推 */ }
/* 比如,Kernel32.dll中的这个函数 void GetSystemTime(SYSTEMTIME* SystemTime); 把SYSTEMTIME看成类,结构和类本来就是“同根生”嘛^_^ */ [StructLayout(LayoutKind::Sequential)] public __gc class MySystemTime { public: unsigned short wYear; unsigned short wMonth; unsigned short wDayOfWeek; unsigned short wDay; unsigned short wHour; unsigned short wMinute; unsigned short wSecond; unsigned short wMilliseconds; }; [DllImport("Kernel32.dll")] extern "C" void GetSystemTime(MySystemTime& st);如果想使用回调函数,那就更麻烦了,需要用到委托/事件机制来接收消息。
using namespace System::Runtime::InteropServices; //定义一个委托 __delegate bool CallBack(int hwnd, int lParam); [DllImport("user32")] extern "C" int EnumWindows(CallBack* x, int y); //参数CallBack从函数指针变成了委托,其实它们大同小异 //回调函数,在调试窗口显式窗口句柄 bool Report(int hwnd, int lParam) { System::Diagnostics::Trace::WriteLine(hwnd.ToString(),"Window handle is:"); return true; }; //使用 //实例化一个委托myCallBack CallBack* myCallBack = new CallBack(this, &EnumReport::Report); EnumWindows(myCallBack, 0); //将函数指针(实例化的委托)传给COM服务器,COM服务器会自动调用它返回结果如果想使用回调接口或连接点,看清本节的标题啦,根本就没有接口,怎么做啊?呵呵。
可用的属性,通常使用 DllImportAttribute( [DllImport(...)] ) 来设置值
本节大多内容可以在MSDN2003以上版本的“使用非托管 DLL 函数”中找到。“个性”化封送处理(仅COM服务器.net客户端)请参见“用平台调用封送数据”,平台调用即P/Invoke。
下载本文示例代码
连接 COM 与.NET 的桥梁(二)连接 COM 与.NET 的桥梁(二)连接 COM 与.NET 的桥梁(二)连接 COM 与.NET 的桥梁(二)连接 COM 与.NET 的桥梁(二)连接 COM 与.NET 的桥梁(二)连接 COM 与.NET 的桥梁(二)连接 COM 与.NET 的桥梁(二)连接 COM 与.NET 的桥梁(二)连接 COM 与.NET 的桥梁(二)连接 COM 与.NET 的桥梁(二)连接 COM 与.NET 的桥梁(二)