分类:
2008-10-14 14:54:15
连接 COM 与.NET 的桥梁(二)
COM 服务器的 P/Invoke 方式
作者:
一、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。
--------------------next---------------------