|
Hook(钩子)是一种在消息到达目标窗口前进行截获的技术。使用钩子主要使用以下三个函数SetWindowsHookEx:创建钩子 CallNextHookEx:将消息传给钩子链中的下一个钩子 UnhookWindowsHookEx:释放钩子 对于创建钩子的函数SetWindowsHookEx,MSDN给出其原形如下:
HHOOK SetWindowsHookEx( int idHook, // type of hook to install HOOKPROC lpfn, // address of hook procedure HINSTANCE hMod, // handle to application instance DWORD dwThreadId // identity of thread to install hook for );
这些在windows上面使用没有问题,但是在说明的最后,关于平台限制的地方,可以清楚的看到以下文字:Windows CE: Unsupported. 也就是说,wince并不支持钩子。 但是是不是不支持呢?只能说不直接支持钩子,用别的方法也是可以使用钩子函数的,那就是直接获取钩子函数地址,然后调用的方法。 g_hHookApiDLL = LoadLibrary(_T("coredll.dll")); SetWindowsHookEx = (_SetWindowsHookExW)GetProcAddress(g_hHookApiDLL, _T("SetWindowsHookExW")); 如法炮制,可以获得其他两个函数的地址,有了这三个函数的地址,就可以类似这样使用了: g_hInstalledLLKBDhook = SetWindowsHookEx(WH_KEYBOARD_LL, keyboardProc, hInstance, 0); 关于wince的钩子,有以下总结,不尽不对之处,请给飞狐指正: 1 参看WinCE的winbase.h,wince下可以使用以下三种: #define WH_JOURNALRECORD 0 #define WH_JOURNALPLAYBACK 1 #define WH_KEYBOARD_LL 20 其中最有用的就是键盘钩子了。Wince里面定义其为20,而不是windows里面的14,因此调用时要注意。 2 关于键盘钩子回调函数keyboardProc,它里面的几个参数并不像MSDN里面提到的KeyboardProc那样: LRESULT CALLBACK KeyboardProc( int code, // hook code WPARAM wParam, // virtual-key code LPARAM lParam // keystroke-message information 按照说明,wParam应该存的是虚拟键信息。然而事实上,这三个函数中,第二个是用来指示是键按下还是弹起,第三个参数lParam才是真正存储的按键信息数据。它存储的是一个KBDLLHOOKSTRUCT结构体指针。这个结构体定义如下: typedef struct { DWORD vkCode; DWORD scanCode; DWORD flags; DWORD time; ULONG_PTR dwExtraInfo; } KBDLLHOOKSTRUCT; 结构体里面才是真正的按键信息。 3对于键盘钩子,我只能使用一个,如果创建二个钩子来检测(不管是使用同一个dll,还是两个不同的dll),则第一个可以正常工作,但是第二个会报错,错误id是31 ERROR_GEN_FAILURE即 A device attached to the system is not functioning. 不知道哪位高手有解决方案? 4 钩子有线程级和全局钩子,但是我只试验成功了全局钩子,工作很好,但是线程级钩子还没有成功。 5 钩子用途很多,我们就用它和驱动打交道,具体也不多说了。 钩子函数源代码在采用codeproject网站上面Prathamesh S Kulkarni的源代码基础上,增加了处理按键消息的部分,代码比较长,就不贴了,有需要的可以交流,或者参看Prathamesh S Kulkarni的文章。
Windows 95进程间数据通讯的实现技术 1、引言 在Windows程序中,各个进程之间常常需要交换数据,进行数据通讯。WIN32 API提供了 许多函数使我们能够方便高效的进行进程间的通讯,通过这些函数我们可以控制不同进 程间的数据交换,就如同在WIN16中对本地进程进行读写操作一样。 典型的WIN16两进程可以通过共享内存来进行数据交换:(1)进程A将GlobalAlloc(GM EM_SHARE...)API分配一定长度的内存;(2)进程A将GlobalAlloc函数返回的句柄传递 给进程B(通过一个登录消息);(3)进程B对这个句柄调用GlobalLock函数,并利用G lobalLock函数返回的指针访问数据。这种方法在WIN32中可能失败,这是因为GlobalLo ck函数返回指向的是进程A的内存,由于进程使用的是虚拟地址而非实际物理地址,因此 这一指针仅与A进程有关,而于B进程无关。 本文探讨了几种WIN32下进程之间通讯的几种实现方法,读者可以使用不同的方法以达到 程序运行高效可靠的目的。 2、Windows95中进程的内存空间管理 WIN32进程间通讯与Windows95的内存管理有密切关系,理解Windows95的内存管理对我们 如下的程序设计将会有很大的帮助,下面我们讨论以下Windows95中进程的内存空间管理 。 在WIN16下,所有Windows应用程序共享单一地址,任何进程都能够对这一空间中属于共 享单一的地址空间,任何进程都能够对这一空间中属于其他进程的内存进行读写操作, 甚至可以存取操作系统本身的数据,这样就可能破坏其他程序的数据段代码。 在WIN32下,每个进程都有自己的地址空间,一个WIN32进程不能存取另一个地址的私有 数据,两个进程可以用具有相同值的指针寻址,但所读写的只是它们各自的数据,这样 就减少了进程之间的相互干扰。另一方面,每个WIN32进程拥有4GB的地址空间,但并不 代表它真正拥有4GB的实际物理内存,而只是操作系统利用CPU的内存分配功能提供的虚 拟地址空间。在一般情况下,绝大多数虚拟地址并没有物理内存于它对应,在真正可以 使用这些地址空间之前,还要由操作系统提供实际的物理内存(这个过程叫“提交”co mmit)。在不同的情况下,系统提交的物理内存是不同的,可能是RAM,也可能是硬盘模 拟的虚拟内存。 3、WIN32中进程间的通讯 在Windows 95中,为实现进程间平等的数据交换,用户可以有如下几种选择: * 使用内存映射文件 * 通过共享内存DLL共享内存 * 向另一进程发送WM_COPYDATA消息 * 调用ReadProcessMemory以及WriteProcessMemory函数,用户可以发送由GlobalLock( GMEM_SHARE,...)函数调用提取的句柄、GlobalLock函数返回的指针以及VirtualAlloc函 数返回的指针。 3.1、利用内存映射文件实现WIN32进程间的通讯 Windows95中的内存映射文件的机制为我们高效地操作文件提供了一种途径,它允许我们 在WIN32进程中保留一段内存区域,把目标文件映射到这段虚拟内存中。在程序实现中必 须考虑各进程之间的同步。具体实现步骤如下: 首先我们在发送数据的进程中需要通过调用内存映射API函数CreateFileMapping创建一 个有名的共享内存: HANDLE CreateFileMapping( HANDLE hFile, // 映射文件的句柄, //设为0xFFFFFFFF以创建一个进程间共享的对象 LPSECURITY_ATTRIBUTES lpFileMappingAttributes, // 安全属性 DWORD flProtect, // 保护方式 DWORD dwMaximumSizeHigh, //对象的大小 DWORD dwMaximumSizeLow, LPCTSTR lpName // 必须为映射文件命名 ); 与虚拟内存类似,保护方式可以是PAGE_READONLY或是PAGE_READWRITE。如果多进程都对 同一共享内存进行写访问,则必须保持相互间同步。映射文件还可以指定PAGE_WRITECO PY标志,可以保证其原始数据不会遭到破坏,同时允许其他进程在必要时自由的操作数 据的拷贝。 在创建文件映射对象后使用可以调用MapViewOfFile函数映射到本进程的地址空间内。 下面说明创建一个名为MySharedMem的长度为4096字节的有名映射文件: HANDLE hMySharedMapFile=CreateFileMapping((HANDLE)0xFFFFFFFF), NULL,PAGE_READWRITE,0,0x1000,"MySharedMem"); 并映射缓存区视图: LPSTR pszMySharedMapView=(LPSTR)MapViewOfFile(hMySharedMapFile, FILE_MAP_READ|FILE_MAP_WRITE,0,0,0); 其他进程访问共享对象,需要获得对象名并调用OpenFileMapping函数。 HANDLE hMySharedMapFile=OpenFileMapping(FILE_MAP_WRITE, FALSE,"MySharedMem"); 一旦其他进程获得映射对象的句柄,可以象创建进程那样调用MapViewOfFile函数来映射 对象视图。用户可以使用该对象视图来进行数据读写操作,以达到数据通讯的目的。 当用户进程结束使用共享内存后,调用UnmapViewOfFile函数以取消其地址空间内的视图 : if (!UnmapViewOfFile(pszMySharedMapView)) { AfxMessageBox("could not unmap view of file"); } 3.2、利用共享内存DLL 共享数据DLL允许进程以类似于Windows 3.1 DLL共享数据的方式访问读写数据,多个进 程都可以对该共享数据DLL进行数据操作,达到共享数据的目的。在WIN32中为建立共享 内存,必须执行以下步骤: 首先创建一个有名的数据区。这在Visual C++中是使用data_seg pragma宏。使用data_ seg pragma宏必须注意数据的初始化: #pragma data_seg("MYSEC") char MySharedData[4096]={0}; #pragma data_seg() 然后在用户的DEF文件中为有名的数据区设定共享属性。 LIBRARY TEST DATA READ WRITE SECTIONS .MYSEC READ WRITE SHARED 这样每个附属于DLL的进程都将接受到属于自己的数据拷贝,一个进程的数据变化并不会 反映到其他进程的数据中。 在DEF文件中适当地输出数据。以下的DEF文件项说明了如何以常数变量的形式输出MySh aredData。 EXPORTS MySharedData CONSTANT 最后在应用程序(进程)按外部变量引用共享数据。 extern _export"C"{char * MySharedData[];} 进程中使用该变量应注意间接引用。 m_pStatic=(CEdit*)GetDlgItem(IDC_SHARED); m_pStatic->GetLine(0,*MySharedData,80); 3.3、用于传输只读数据的WM_COPYDATA 传输只读数据可以使用Win32中的WM_COPYDATA消息。该消息的主要目的是允许在进程间 传递只读数据。Windows95在通过WM_COPYDATA消息传递期间,不提供继承同步方式。SD K文档推荐用户使用SendMessage函数,接受方在数据拷贝完成前不返回,这样发送方就 不可能删除和修改数据: SendMessage(hwnd,WM_COPYDATA,wParam,lParam); 其中wParam设置为包含数据的窗口的句柄。lParam指向一个COPYDATASTRUCT的结构: typedef struct tagCOPYDATASTRUCT{ DWORD dwData;//用户定义数据 DWORD cbData;//数据大小 PVOID lpData;//指向数据的指针 }COPYDATASTRUCT; 该结构用来定义用户数据。 3.4、直接调用ReadProcessMemory和WriteProcessMemory函数实现进程间通讯 通过调用ReadProcessMemory以及WriteProcessMemory函数用户可以按类似与Windows3. 1的方法实现进程间通讯,在发送进程中分配一块内存存放数据,可以调用GlobalAlloc 或者VirtualAlloc函数实现: pApp->m_hGlobalHandle=GlobalAlloc(GMEM_SHARE,1024); 可以得到指针地址: pApp->mpszGlobalHandlePtr=(LPSTR)GlobalLock (pApp->m_hGlobalHandle); 在接收进程中要用到用户希望影响的进程的打开句柄。为了读写另一进程,应按如下方 式调用OpenProcess函数: HANDLE hTargetProcess=OpenProcess( STANDARD_RIGHTS_REQUIRED| PROCESS_VM_REDA| PROCESS_VM_WRITE| PROCESS_VM_OPERATION,//访问权限 FALSE,//继承关系 dwProcessID);//进程ID 为保证OpenProcess函数调用成功,用户所影响的进程必须由上述标志创建。 一旦用户获得一个进程的有效句柄,就可以调用ReadProcessMemory函数读取该进程的内 存: BOOL ReadProcessMemory( HANDLE hProcess, // 进程指针 LPCVOID lpBaseAddress, // 数据块的首地址 LPVOID lpBuffer, // 读取数据所需缓冲区 DWORD cbRead, // 要读取的字节数 LPDWORD lpNumberOfBytesRead ); 使用同样的句柄也可以写入该进程的内存: BOOL WriteProcessMemory( HANDLE hProcess, // 进程指针 LPVOID lpBaseAddress, // 要写入的首地址 LPVOID lpBuffer, // 缓冲区地址 DWORD cbWrite, // 要写的字节数 LPDWORD lpNumberOfBytesWritten ); 如下所示是读写另一进程的共享内存中的数据: ReadProcessMemory((HANDLE)hTargetProcess, (LPSTR)lpsz,m_strGlobal.GetBuffer(_MAX_FIELD), _MAX_FIELD,&cb); WriteProcessMemory((HANDLE)hTargetProcess, (LPSTR)lpsz,(LPSTR)STARS, m_strGlobal.GetLength(),&cb); 4、进程之间的消息发送与接收 在实际应用中进程之间需要发送和接收Windows消息来通知进程间相互通讯,发送方发送 通讯的消息以通知接收方,接收方在收到发送方的消息后就可以对内存进行读写操作。
我们在程序设计中采用Windows注册消息进行消息传递,首先在发送进程初始化过程中进 行消息注册: m_nMsgMapped=::RegisterWindowsMessage("Mapped"); m_nMsgHandle=::RegisterWindowsMessage("Handle"); m_nMsgShared=::RegisterWindowsMessage("Shared"); 在程序运行中向接收进程发送消息: CWnd* pWndRecv=FindWindow(lpClassName,"Receive"); pWndRecv->SendMessage(m_MsgMapped,0,0); pWndRecv->SendMessage(m_nMsgHandle, (UINT)GetCurrentProcessID(),(LONG)pApp->m_hGlobalHandle); pWndRecv->SendMessage(m_nMsgShared,0,0); 可以按如下方式发送WM_COPYDATA消息: static COPYDATASTRUCT cds;//用户存放数据 pWnd->SendMessage(WM_COPYDATA,NULL,(LONG)&cds); 接收方进程初始化也必须进行消息注册: UNIT CRecvApp:: m_nMsgMapped=::RegisterWindowsMessage("Mapped"); UNIT CRecvApp::m_nMsgHandle=::RegisterWindowsMessage("Handle"); UNIT CRecvApp::m_nMsgShared=::RegisterWindowsMessage("Shared"); 同时映射消息函数如下: ON_REGISTERED_MASSAGE(CRecvApp::m_nMsgMapped,OnRegMsgMapped) ON_REGISTERED_MASSAGE(CRecvApp::m_nMsgHandle,OnRegMsgHandle) ON_REGISTERED_MASSAGE(CRecvApp::m_nMsgShared,OnRegMsgShared) 在这些消息函数我们就可以采用上述技术实现接收进程中数据的读写操作了。 5、结束语 从以上分析中我们可以看出Windows95的内存管理与Windows 3.x相比有很多的不同,对 进程之间的通讯有较为严格的限制。这就确保了任何故障程序无法意外地写入用户的地 址空间,而用户则可根据实际情况灵活地进行进程间的数据通讯,从这一点上来讲Wind ows95增强应用程序的强壮性。 参考文献: 1、 David J.Kruglinski, Visual C++技术内幕, 北京:清华大学出版社,1995. 2、 Microsoft Co. Visual C++ 5.0 On Line Help.
client: #include "Winsock2.h" #include "stdafx.h" #pragma comment(lib,"Ws2.lib") // TODO: 在 STDAFX.H 中 // 引用任何所需的附加头文件,而不是在此文件中引用 void SOCKETRACE(char *buf,int len) { WSADATA wsadata; WSAStartup(MAKEWORD(2,0),&wsadata); struct sockaddr_in ipaddr; ipaddr.sin_family=AF_INET; ipaddr.sin_port=htons(11000); ipaddr.sin_addr.s_addr=inet_addr("169.254.2.2");
int sk=socket(PF_INET,SOCK_DGRAM,IPPROTO_UDP); int c=connect(sk,(sockaddr*)&ipaddr,sizeof(ipaddr)); send(sk,buf,len,0); closesocket(sk); WSACleanup(); }
void OEMTRACEW(BOOL cond, LPCWSTR fmt, ...) { if(cond) {
int n, size = 100; wchar_t* p ; va_list ap ;
p = (wchar_t*)malloc(size * sizeof(wchar_t)) ;
while( 1 ) { /* Try to print in the allocated space. */ va_start( ap, fmt ) ; n = _vsnwprintf( p, size, fmt, ap ) ; va_end( ap ) ; /* If that worked, return the string. */ if( n > -1 && n < size ) break ;
/* Else try again with more space. */ if( n > -1 ) /* C99 conform vsnprintf() */ size = n+1 ; /* precisely what is needed */ else /* glibc 2.0 */ size *= 2 ; /* twice the old size */
p = (wchar_t*)realloc( p, size * sizeof(wchar_t) ) ; } char nstring[200]={0}; wcstombs( nstring,p,200); free( p ) ; SOCKETRACE(nstring,strlen(nstring));
}
}
void OEMTRACE(BOOL cond, const char * fmt, ...) { if(cond) {
int n, size = 100; char* p ; va_list ap ;
p = (char*)malloc(size) ;
while( 1 ) { /* Try to print in the allocated space. */ va_start( ap, fmt ) ; n = _vsnprintf( p, size, fmt, ap ) ; va_end( ap ) ; /* If that worked, return the string. */ if( n > -1 && n < size ) break ;
/* Else try again with more space. */ if( n > -1 ) /* C99 conform vsnprintf() */ size = n+1 ; /* precisely what is needed */ else /* glibc 2.0 */ size *= 2 ; /* twice the old size */
p = (char*)realloc( p, size ) ; }
SOCKETRACE(p,strlen(p)); free( p ) ;
}
} client c#: udpClient = new UdpClient(); Byte[] bytes = Encoding.Unicode.GetBytes("aa"); udpClient.Send(bytes, bytes.Length,new IPEndPoint(IPAddress.Parse("169.254.2.2"), 11000)); udpserver c#: using System; using System.Collections.Generic; using System.Text; using System.Net.Sockets; using System.Net;
namespace ScocketRec { class Program { static void Main(string[] args) { UdpClient udpClient = new UdpClient(new IPEndPoint(IPAddress.Parse("169.254.2.2"), 11000)); try { //IPEndPoint object will allow us to read datagrams sent from any source. IPEndPoint RemoteIpEndPoint = new IPEndPoint(IPAddress.Parse("169.254.2.1"), 11000); while(true) { // Blocks until a message returns on this socket from a remote host. Byte[] receiveBytes = udpClient.Receive(ref RemoteIpEndPoint); string returnData = Encoding.ASCII.GetString(receiveBytes);
// Uses the IPEndPoint object to determine which of these two hosts responded. Console.WriteLine("This is the message you received " + returnData.ToString()); Console.WriteLine("This message was sent from " + RemoteIpEndPoint.Address.ToString() + " on their port number " + RemoteIpEndPoint.Port.ToString()); }
udpClient.Close();
} catch (Exception e) { Console.WriteLine(e.ToString()); }
} } }
int BCDToInt(byte bcd)
{
return (0xff & (bcd>>4))*10 +(0xf & bcd);
}
#include<time.h>
main(){
char *wday[]={“Sun”,”Mon”,”Tue”,”Wed”,”Thu”,”Fri”,”Sat”};
time_t timep;
struct tm *p;
time(&timep);
p=localtime(&timep); /*取得当地时间*/
printf (“%d%d%d ”, (1900+p->tm_year),( l+p->tm_mon), p->tm_mday);
printf(“%s%d:%d:%d\n”, wday[p->tm_wday],p->tm_hour, p->tm_min,
p->tm_sec);
}
方法1 tmail -service "MMS" -to "" -body "" 方法2 附加到进程 static void LaunchInboxApp(void) { //#include <Toolhelp.h> HANDLE handle=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0); PROCESSENTRY32 info; info.dwSize=sizeof(info); int i=0; if(Process32First(handle,&info)) { if(_tcscmp(info.szExeFile,TEXT("tmail.exe"))==0 ) { HANDLE handle2=OpenProcess(0,FALSE,info.th32ProcessID); TerminateProcess(handle2,0); } else { while (Process32Next(handle,&info)!=FALSE) { if(_tcscmp(info.szExeFile,TEXT("tmail.exe"))==0 ) { HANDLE handle2=OpenProcess(0,FALSE,info.th32ProcessID); TerminateProcess(handle2,0); break; }
} }
} CloseToolhelp32Snapshot(handle);
SHELLEXECUTEINFO si ;
ZeroMemory( &si, sizeof(si) ) ; si.cbSize = sizeof(SHELLEXECUTEINFO) ; si.lpFile = L"\\windows\\tmail.exe" ; si.lpParameters = L"-NoUI";
ShellExecuteEx( &si ) ; }
void enumentryname() { char nstring[100]={0}; LPRASENTRYNAME lprasentryname; int i; DWORD nRet,cb,cEntries; lprasentryname = (LPRASENTRYNAME)LocalAlloc(LPTR, sizeof(RASENTRYNAME)); lprasentryname->dwSize = sizeof(RASENTRYNAME); if ((nRet = RasEnumEntries(NULL, NULL, lprasentryname, &cb, &cEntries)) == ERROR_BUFFER_TOO_SMALL) { lprasentryname = (LPRASENTRYNAME)LocalAlloc(LPTR, cb); lprasentryname->dwSize = sizeof(RASENTRYNAME); } // Calling RasEnumEntries to enumerate the phonebook entries nRet = RasEnumEntries(NULL, NULL, lprasentryname, &cb, &cEntries); if (nRet != ERROR_SUCCESS) { printf("RasEnumEntries failed: Error %d\n", nRet); } else { for(i=0;i < cEntries;i++) { wcstombs( nstring,lprasentryname->szEntryName,100); Write(nstring,strlen(nstring)); memset(nstring,0,100); lprasentryname++; } }
}
void WINAPI RasDialFunc(UINT nmsg,RASCONNSTATE st,DWORD dwError) {
wprintf(nmsg,"MSG"); } HRASCONN hs=NULL; RASDIALPARAMS RasDialParams; RasDialParams.dwSize=sizeof(RasDialParams); lstrcpy(RasDialParams.szEntryName,L"tdwap"); lstrcpy(RasDialParams.szPhoneNumber,L""); lstrcpy(RasDialParams.szUserName,L""); lstrcpy(RasDialParams.szPassword,L""); lstrcpy(RasDialParams.szCallbackNumber,L""); DWORD dwret=RasDial(NULL,NULL,&RasDialParams,0xFFFFFFFF,RasDialFunc,&hs); wprintf(dwret,"RasDial"); while(1) { Sleep(1000); }
unsigned int CloseRasGPRSConnections() { int index; // An integer index DWORD dwError, dwRasConnSize, dwNumConnections; // Number of connections found RASCONN RasConn[20]; // Buffer for connection state data,Assume the maximum number of entries is 20. BOOL RETURN_VALUE=0; WCHAR *MySelectNetName;
// Assume no more than 20 connections. RasConn[0].dwSize = sizeof (RASCONN); dwRasConnSize = 20 * sizeof (RASCONN);
// Find all connections. if (dwError = RasEnumConnections (RasConn, &dwRasConnSize,&dwNumConnections)) { return -1; }
// If there are no connections, return zero. if (!dwNumConnections) { return 0; }
// Terminate all of the remote access connections. GetConnectionStatus(); //here add to get selected network MySelectNetName=GetMMSSelectNet(); GPRSServerName* P_CMWAPtemp=pCMWAP_backup; for (index = 0; index < (int)dwNumConnections; ++index) { while( P_CMWAPtemp ) { if(!wcscmp(RasConn[index].szEntryName,P_CMWAPtemp->ServerName)|| !wcscmp(RasConn[index].szEntryName, MySelectNetName)) { if (dwError = RasHangUp (RasConn[index].hrasconn)) RETURN_VALUE=-1; else //successfully disconnect cmwap; RETURN_VALUE=0; } P_CMWAPtemp = P_CMWAPtemp->pnext ; } } //free mem freelink(pCMWAP_backup); return RETURN_VALUE; }
有个使用指针的方法:
int x = 1; if(*(char *)&x == 1) printf("little-endian\n"); else printf("big-endian\n");
另外一个可能是用联合。
参见问题 10.15 和 20.7。
http://www.uml.org.cn/embeded/200610255.htm
setwindowlong
MapPtrToProcess
PerformCallBack4
分析一下UML类图中关联、聚合、组合三者的定义与关系。 @author:JZhang 06-11-27 E-mail:zhangjunhd@gmail.com Blog: http://blog.csdn.net/zhangjunhd/ 1.关联(Association)类之间的关联大多用来表示变量实例持有着对其他对象的引用。 Phone拥有一个对Button的引用。 2.聚合(Aggregation)聚合是关联的一种特殊形式,它意味着一种整体/部分(whole/part)的关系。 一个整体不能是它自己的一部分。 因此 ,实例不能形成聚合回路,一个单独的对象不能够成为它自己的聚合,两个对象不能互相聚合,三个对象不能形成一个聚合环。下图为实例间的非法聚合循环: 3.组合(Composition)组合是一种特殊的聚合形式。 UML对组合的定义: ①如同聚合,实例不能有循环。 ②一个被所有者实例不能同时有两个所有者。 ③所有者负责被组合的对象的生命周期的管理。如果所有者被销毁,被所有者也必须跟着一起被销毁,如果所有者被复制,被所有者也必须跟着一起被复制。 http://blog.csdn.net/dylgsy/archive/2006/08/16/1076044.aspx UML类图关系全面剖析 http://www.ibm.com/developerworks/cn/rational/r-shenzj/ 利用Rational Rose进行C++代码和数据库结构分析 vs2008 支持 c++ 类图了
|