分类: C/C++
2008-04-23 22:02:59
理解 COM 套间
; 汇编程序示例 data_seg segment ;定义数据段 n_i dw ? data_seg ends stack_seg segment ;定义堆栈 dw 128 dup(0) tos label word statck_seg ends code1 segment ;定义代码段 main proc far assume cs:ccode,ds;data,seg,ss:stack_seg start: move ax,stack_seg ;将定义的堆栈段的地址保存到ss mov ss,ax mov sp,offset tos ;将堆栈的最后地址保存到sp,堆栈是从下到上访问的 push ds ;保存旧的数据段 sub ax,ax push ax mov ax,data_seg ;将定义的数据段保存到ds mov ds,ax call fact ;调用子函数 ……. ;其它操作省略 ret ;返回到系统 main endp fact proc near ;子函数定义 …… ;具体操作省略 ret ;返回到调用处 fact endp code1 ends end start 示例1:汇编程序结构从以上程序我们看到,一个程序可以分为代码段,数据段,堆栈段等几部分。汇编编译器在编译的时候会将这些文件转化为成一个标准格式(在windows下被称为PE文件格式)的文件(很多时候可执行文件被命名为二进制文件,我不喜欢这个名字,我觉得它容易给人误解;事实上计算机上所有的文件都是0和1组成的,都是二进制文件;真正不同的就是处理这些文件的方式;EXE文件需要操作系统来调用,TXT文件需要写字本来打开;但其本质上并没有什么不同,只是在不同的组合上,二进制数有不同的意义)。该文件格式会把我们的代码按格式安放在不同的部分。程序必须在内存中,才可以执行。在程序运行前,操作系统会按照标准格式将这些内容加载到内存中。这些数据加载到内存中也需要按照一定的格式,CPU提供了DS,CS,SS等段寄存器,这样代码段的开始位置需要被CS指定,数据段的开始位置需要用DS来指定,SS需要指向堆栈的开始位置等。在DOS下,每次只能运行一个程序,这些内容基本构成了进程。但在Windows下,丰富了进程的内容,还包括一些数据结构用来维护我们程序中用到的图标,对话框等内容,以及线程。其实进程就是程序在内存中的组织形式,有了这样的组织形式,程序才可能运行。也就是说,当程序加载到内存中去后,就形成了一个进程。
…… mov eax,4 mov ebx,5 ……假如我们的程序运行到mov eax,4,那么EIP就会指向该句代码所在的内存的地址。当这行代码执行完毕之后,那么EIP会自动加一,那么它就会指向mov ebx,4。而程序的执行就是靠EIP的不断增加来完成的(跳转的话,EIP就变成了跳转到的地址)。在Windows系统下,进程并不拥有EIP,EAX,那么只有进程,一个程序就无法运行。而拥有这些寄存器的是线程,所以说进程是静态的。
// // Context Frame // // This frame has a several purposes: 1) it is used as an argument to // NtContinue, 2) is is used to constuct a call frame for APC delivery, // and 3) it is used in the user level thread creation routines. // // The layout of the record conforms to a standard call frame. // typedef struct _CONTEXT { // // The flags values within this flag control the contents of // a CONTEXT record. // // If the context record is used as an input parameter, then // for each portion of the context record controlled by a flag // whose value is set, it is assumed that that portion of the // context record contains valid context. If the context record // is being used to modify a threads context, then only that // portion of the threads context will be modified. // // If the context record is used as an IN OUT parameter to capture // the context of a thread, then only those portions of the thread''s // context corresponding to set flags will be returned. // // The context record is never used as an OUT only parameter. // DWORD ContextFlags; // // This section is specified/returned if CONTEXT_DEBUG_REGISTERS is // set in ContextFlags. Note that CONTEXT_DEBUG_REGISTERS is NOT // included in CONTEXT_FULL. // DWORD Dr0; DWORD Dr1; DWORD Dr2; DWORD Dr3; DWORD Dr6; DWORD Dr7; // // This section is specified/returned if the // ContextFlags word contians the flag CONTEXT_FLOATING_POINT. // FLOATING_SAVE_AREA FloatSave; // // This section is specified/returned if the // ContextFlags word contians the flag CONTEXT_SEGMENTS. // DWORD SegGs; DWORD SegFs; DWORD SegEs; DWORD SegDs; // // This section is specified/returned if the // ContextFlags word contians the flag CONTEXT_INTEGER. // DWORD Edi; DWORD Esi; DWORD Ebx; DWORD Edx; DWORD Ecx; DWORD Eax; // // This section is specified/returned if the // ContextFlags word contians the flag CONTEXT_CONTROL. // DWORD Ebp; DWORD Eip; DWORD SegCs; // MUST BE SANITIZED DWORD EFlags; // MUST BE SANITIZED DWORD Esp; DWORD SegSs; // // This section is specified/returned if the ContextFlags word // contains the flag CONTEXT_EXTENDED_REGISTERS. // The format and contexts are processor specific // BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION]; } CONTEXT;好了,线程就先讲这么多了。如果对进程和线程的内容感兴趣,可以到Intel的网站下载PDF格式的电子书《IA-32 Intel Architecture Software Developer’s Manual》,纸版的书也可以在这儿预定(他们会免费邮寄给你)。通过这套书,你可以对CPU的结构有一个清晰的认识。另外可以找几本讲解Windows系统的书看看,不过这类的好书不多,最著名的是《Advance Windows》,不过也是偏向于实用,对系统结构的讲解不多。也是,要完全去了解这部分的细节,太困难了,毕竟微软没有给我们提供这部分的源码。幸好,其实我们理解它大致的原理就足够用了。
#include这段程序的本意是让全局变量累次加10,并打印出操作后的数值。但我们运行程序后的结果如下,可以看到程序的运行结果非我们所愿。打印出的结果是一串乱序的文字。#include int g_i = 10; //一个全局变量 DWORD WINAPI ThreadProc(LPVOID lpv) { g_i = 10; std::cout <<"In the Thread " << ::GetCurrentThreadId() << ",the first g_i is " << g_i << "!" << std::endl; Sleep(5000); //睡眠 g_i = 10; std::cout <<"In the Thread " << ::GetCurrentThreadId() << ",the secend g_i is " << g_i << "!" << std::endl; return 0; } int main(int argc, char* argv[]) { DWORD threadID[2]; HANDLE hThreads[2]; for(int i = 0; i <= 1; i ) //创建两个线程 hThreads[i] = ::CreateThread(NULL, 0, ThreadProc, NULL, 0, &threadID[i]); WaitForMultipleObjects(2,hThreads,TRUE,INFINITE); //等待线程结束 for(i = 0; i <= 1; i ) ::CloseHandle(hThreads[i]); //关闭线程句柄 system("pause"); return 0; } 示例程序2-多线程程序
#include再次运行,结果就是我们所需要的了。#include int g_i = 10; //一个全局变量 CRITICAL_SECTION cs; //一个临界区变量 DWORD WINAPI ThreadProc(LPVOID lpv) { EnterCriticalSection(&cs); //进入临界区 g_i = 10; std::cout <<"In the Thread " << ::GetCurrentThreadId() << ",the first g_i is " << g_i << "!" << std::endl; ::LeaveCriticalSection(&cs); Sleep(5000); //睡眠 EnterCriticalSection(&cs); g_i = 10; std::cout <<"In the Thread " << ::GetCurrentThreadId() << ",the secend g_i is " << g_i << "!" << std::endl; ::LeaveCriticalSection(&cs); return 0; } int main(int argc, char* argv[]) { DWORD threadID[2]; HANDLE hThreads[2]; InitializeCriticalSection(&cs); for(int i = 0; i <= 1; i ) //创建两个线程 hThreads[i] = ::CreateThread(NULL, 0, ThreadProc, NULL, 0, &threadID[i]); WaitForMultipleObjects(2,hThreads,TRUE,INFINITE); //等待线程结束 for(i = 0; i <= 1; i ) ::CloseHandle(hThreads[i]); //关闭线程句柄 system("pause"); return 0; }
#define _WIN32_WINNT 0x0400 #include该段程序将main中定义的ITestInterface1对象,通过指针传到了新建的线程中。运行该段程序,结果如下,又是一串乱序的文字串。也就是说我们需要在TestComObject1中对TestFunc1进行线程同步控制。但大多数人并不想这样做,因为我们开发的组件大多数情况下并不会在多线程执行。但为了避免低概率事件发生后的不良后果,套间出场了。#include #include "..\TestComObject1\TestComObject1_i.c" #include "..\TestComObject1\TestComObject1.h" DWORD WINAPI ThreadProc(LPVOID lpv) { HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); if ( FAILED(hr) ) { std::cout << "CoinitializeEx failed!" << std::endl; return 0; } ITestInterface1 *pTest = NULL; hr = ::CoCreateInstance(CLSID_TestInterface1, 0, CLSCTX_INPROC, IID_ITestInterface1, (void**)&pTest); if ( FAILED(hr) ) { std::cout << "CoCreateInstance failed!" << std::endl; return 0; } hr = pTest->TestFunc1(); if ( FAILED(hr) ) { std::cout << "TestFunc1 failed!" << std::endl; return 0; } pTest->Release(); ::CoUninitialize(); return 0; } int main(int argc, char* argv[]) { HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); if ( FAILED(hr) ) { std::cout << "CoinitializeEx failed!" << std::endl; return 0; } ITestInterface1 *pTest = NULL; hr = ::CoCreateInstance(CLSID_TestInterface1, 0, CLSCTX_INPROC, IID_ITestInterface1, (void**)&pTest); if ( FAILED(hr) ) { std::cout << "CoCreateInstance failed!" << std::endl; return 0; } DWORD threadID; HANDLE hThreads = ::CreateThread(NULL, //创建一个进程 0, ThreadProc, NULL, //将pTest作为一个参数传入新线程 0, &threadID); hr = pTest->TestFunc1(); if ( FAILED(hr) ) { std::cout << "TestFunc1 failed!" << std::endl; return 0; } ::WaitForSingleObject(hThreads,INFINITE); //等待线程结束 ::CloseHandle(hThreads); //关闭线程句柄 pTest->Release(); ::CoUninitialize(); system("pause"); return 0; }
STDMETHODIMP CTestInterface1::TestFunc1() { // TODO: Add your implementation code here std::cout << "In the itestinferface1''s object, the thread''s id is " << ::GetCurrentThreadId() << std::endl; return S_OK; }该方法非常简单,就是打印出该方法运行时,所在的线程的ID号。如果在不同的线程中调用同一个对象的时候,通过套间,发送消息,最终该对象只应该在一个线程中运行,所以它的线程ID号应该是相同的。我们将通过该ID值来验证套间的存在。
#define _WIN32_WINNT 0x0400 #include#include #include "..\TestComObject1\TestComObject1_i.c" #include "..\TestComObject1\TestComObject1.h" DWORD WINAPI ThreadProc(LPVOID lpv) { HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); if ( FAILED(hr) ) { std::cout << "CoinitializeEx failed!" << std::endl; return 0; } ITestInterface1 *pTest = NULL; hr = ::CoCreateInstance(CLSID_TestInterface1, 0, CLSCTX_INPROC, IID_ITestInterface1, (void**)&pTest); if ( FAILED(hr) ) { std::cout << "CoCreateInstance failed!" << std::endl; return 0; } hr = pTest->TestFunc1(); if ( FAILED(hr) ) { std::cout << "TestFunc1 failed!" << std::endl; return 0; } pTest->Release(); ::CoUninitialize(); return 0; } int main(int argc, char* argv[]) { HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); if ( FAILED(hr) ) { std::cout << "CoinitializeEx failed!" << std::endl; return 0; } ITestInterface1 *pTest = NULL; hr = ::CoCreateInstance(CLSID_TestInterface1, 0, CLSCTX_INPROC, IID_ITestInterface1, (void**)&pTest); if ( FAILED(hr) ) { std::cout << "CoCreateInstance failed!" << std::endl; return 0; } hr = pTest->TestFunc1(); if ( FAILED(hr) ) { std::cout << "TestFunc1 failed!" << std::endl; return 0; } DWORD threadID; HANDLE hThreads[1]; hThreads[0] = ::CreateThread(NULL, //创建一个进程 0, ThreadProc, (LPVOID)pTest, //将pTest作为一个参数传入新线程 0, &threadID); ::WaitForSingleObject(hThreads,INFINITE); //等待线程结束 ::CloseHandle(hThreads); //关闭线程句柄 pTest->Release(); ::CoUninitialize(); system("pause"); return 0; }
DWORD ApartMentMsgWaitForMultipleObject(HANDLE *hHandle,DWORD dwWaitCout, DWORD dwMilliseconds) { BOOL bQuit = FALSE; DWORD dwRet; while(!bQuit) { int rc; rc = ::MsgWaitForMultipleObjects ( dwWaitCout, // 需要等待的对象数量 hHandle, // 对象树组 FALSE, //等待所有的对象 (DWORD)dwMilliseconds, // 等待的时间 (DWORD)(QS_ALLINPUT | QS_ALLPOSTMESSAGE) // 事件类型 ); //等待的事件激发 if( rc == WAIT_OBJECT_0 ) { dwRet = rc; bQuit = TRUE; } //其他windows消息 else if( rc == WAIT_OBJECT_0 dwWaitCout ) { MSG msg; while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage (&msg); DispatchMessage(&msg); } } } return dwRet; }该函数用来处理消息的同步,也够麻烦的,还需要自己写这段程序。这段程序的意思是如果等待的事件被激发,那么设置bQuit为TURE,那么退出消息循环。如果接收到其它的消息的话,再分发出去。好了,把我们的程序再改一下:
// ::WaitForSingleObject(hThreads,INFINITE); //等待线程结束 ApartMentMsgWaitForMultipleObject(hThreads,1,INFINITE);
#define _WIN32_WINNT 0x0400 #include我们通过CoGetInterfaceAndReleaseStream将main中的pTest变为pStream,然后将pStream作为参数传入到线程中,然后再通过CoGetInterfaceAndReleaseStream将pSteam变为接口指针。再来看看运行的结果:#include #include "..\TestComObject1\TestComObject1_i.c" #include "..\TestComObject1\TestComObject1.h" DWORD WINAPI ThreadProc(LPVOID lpv) { //HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); if ( FAILED(hr) ) { std::cout << "CoinitializeEx failed!" << std::endl; return 0; } IStream *pStream = (IStream*)lpv; ITestInterface1 *pTest = NULL; hr = ::CoGetInterfaceAndReleaseStream(pStream, IID_ITestInterface1, (void**)&pTest); if ( FAILED(hr) ) { std::cout << "CoGetInterfaceAndReleaseStream failed!" << std::endl; return 0; } hr = pTest->TestFunc1(); if ( FAILED(hr) ) { std::cout << "TestFunc1 failed!" << std::endl; return 0; } pTest->Release(); ::CoUninitialize(); return 0; } DWORD ApartMentMsgWaitForMultipleObject(HANDLE *hHandle,DWORD dwWaitCout, DWORD dwMilliseconds) { BOOL bQuit = FALSE; DWORD dwRet; while(!bQuit) { int rc; rc = ::MsgWaitForMultipleObjects ( dwWaitCout, // 需要等待的对象数量 hHandle, // 对象树组 FALSE, //等待所有的对象 (DWORD)dwMilliseconds, // 等待的时间 (DWORD)(QS_ALLINPUT | QS_ALLPOSTMESSAGE) // 事件类型 ); if( rc == WAIT_OBJECT_0 ) { dwRet = rc; bQuit = TRUE; } else if( rc == WAIT_OBJECT_0 dwWaitCout ) { MSG msg; while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage (&msg); DispatchMessage(&msg); } } } return dwRet; } int main(int argc, char* argv[]) { //HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); if ( FAILED(hr) ) { std::cout << "CoinitializeEx failed!" << std::endl; return 0; } ITestInterface1 *pTest = NULL; hr = ::CoCreateInstance(CLSID_TestInterface1, 0, CLSCTX_INPROC, IID_ITestInterface1, (void**)&pTest); if ( FAILED(hr) ) { std::cout << "CoCreateInstance failed!" << std::endl; return 0; } hr = pTest->TestFunc1(); if ( FAILED(hr) ) { std::cout << "TestFunc1 failed!" << std::endl; return 0; } IStream *pStream = NULL; hr = ::CoMarshalInterThreadInterfaceInStream(IID_ITestInterface1, pTest, &pStream); if ( FAILED(hr) ) { std::cout << "CoMarshalInterThreadInterfaceInStream failed!" << std::endl; return 0; } DWORD threadID; HANDLE hThreads[1]; hThreads[0] = ::CreateThread(NULL, //创建一个进程 0, ThreadProc, (LPVOID)pStream, //将pStream作为一个参数传入新线程 0, &threadID); ApartMentMsgWaitForMultipleObject(hThreads,1,INFINITE); ::CloseHandle(hThreads); //关闭线程句柄 pTest->Release(); ::CoUninitialize(); system("pause"); return 0; }
#define _WIN32_WINNT 0x0400 #include此部分代码与我们测试Apartment时的代码基本相同,只是新增了输出main和创建线程的ID的语句。好的,我们来运行程序,可以得到如下的结果:#include #include "..\TestComObject1\TestComObject1_i.c" #include "..\TestComObject1\TestComObject1.h" DWORD WINAPI ThreadProc(LPVOID lpv) { HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); //HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); if ( FAILED(hr) ) { std::cout << "CoinitializeEx failed!" << std::endl; return 0; } IStream *pStream = (IStream*)lpv; ITestInterface1 *pTest = NULL; hr = ::CoGetInterfaceAndReleaseStream(pStream, IID_ITestInterface1, (void**)&pTest); if ( FAILED(hr) ) { std::cout << "CoGetInterfaceAndReleaseStream failed!" << std::endl; return 0; } std::cout << "ThradProc''s threadid is " << ::GetCurrentThreadId() << std::endl; //输出ThradProc的线程ID hr = pTest->TestFunc1(); if ( FAILED(hr) ) { std::cout << "TestFunc1 failed!" << std::endl; return 0; } pTest->Release(); ::CoUninitialize(); return 0; } DWORD ApartMentMsgWaitForMultipleObject(HANDLE *hHandle,DWORD dwWaitCout, DWORD dwMilliseconds) { BOOL bQuit = FALSE; DWORD dwRet; while(!bQuit) { int rc; rc = ::MsgWaitForMultipleObjects ( dwWaitCout, // 需要等待的对象数量 hHandle, // 对象树组 FALSE, //等待所有的对象 (DWORD)dwMilliseconds, // 等待的时间 (DWORD)(QS_ALLINPUT | QS_ALLPOSTMESSAGE) // 事件类型 ); if( rc == WAIT_OBJECT_0 ) { dwRet = rc; bQuit = TRUE; } else if( rc == WAIT_OBJECT_0 dwWaitCout ) { MSG msg; while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage (&msg); DispatchMessage(&msg); } } } return dwRet; } int main(int argc, char* argv[]) { HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); //HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); if ( FAILED(hr) ) { std::cout << "CoinitializeEx failed!" << std::endl; return 0; } ITestInterface1 *pTest = NULL; hr = ::CoCreateInstance(CLSID_TestInterface1, 0, CLSCTX_INPROC, IID_ITestInterface1, (void**)&pTest); if ( FAILED(hr) ) { std::cout << "CoCreateInstance failed!" << std::endl; return 0; } std::cout << "main''s threadid is " << ::GetCurrentThreadId() << std::endl; //打印main的线程ID hr = pTest->TestFunc1(); if ( FAILED(hr) ) { std::cout << "TestFunc1 failed!" << std::endl; return 0; } IStream *pStream = NULL; hr = ::CoMarshalInterThreadInterfaceInStream(IID_ITestInterface1, pTest, &pStream); if ( FAILED(hr) ) { std::cout << "CoMarshalInterThreadInterfaceInStream failed!" << std::endl; return 0; } DWORD threadID; HANDLE hThreads[1]; hThreads[0] = ::CreateThread(NULL, //创建一个进程 0, ThreadProc, (LPVOID)pStream, //将pStream作为一个参数传入新线程 0, &threadID); ApartMentMsgWaitForMultipleObject(hThreads,1,INFINITE); ::CloseHandle(hThreads); //关闭线程句柄 pTest->Release(); ::CoUninitialize(); system("pause"); return 0; }
class ATL_NO_VTABLE CTestInterface1 : public CComObjectRootEx我们先声明一个私有成员变量dwTlsIndex,它用来存放TLS的索引值(一个线程的TLS相当于一个数组,可以存放不同的数据)。再将构造函数中填入保存数据的代码。此处只是简单的分配了一个字节的地址,并将该地址通过TlsSetValue保存到TLS中去。, public CComCoClass , public IDispatchImpl { private: DWORD dwTlsIndex; public: CTestInterface1() { dwTlsIndex = TlsAlloc(); HLOCAL l = LocalAlloc(LMEM_FIXED, 1); TlsSetValue(dwTlsIndex, l); }
STDMETHODIMP CTestInterface1::TestFunc1() { // TODO: Add your implementation code here LPVOID lpvData = TlsGetValue(dwTlsIndex); if ( lpvData == NULL ) return RPC_E_WRONG_THREAD; std::cout << "In the itestinferface1''s object, the thread''s id is " << ::GetCurrentThreadId() << std::endl; return S_OK; }这边也很简单,就是简单的通过TlsGetValue去尝试得到dwTlsIndex所标志的内容是否存在。如果不存在,那么就说明程序运行在了不同的套间中。就会返回RPC_E_WRONG_THREAD,这是COM设计者定义的宏,表示线程的非法使用。(由于我的懒惰,不再写新的COM了,只是简单的修改了TestComObject1,这部分新加的代码被我注释掉了,你如果想看这部分的效果,去掉注释就可以了)