多線程原理實例應用詳解

從單進程單線程到多進程多線程是操作系統發展的一種必然趨勢,當年的DOS系統屬于單任務操作系統,最優秀的程序員也只能通過駐留內存的方式實現所謂的"多任務",而如今的Win32操作系統卻可以一邊聽音樂,一邊編程,一邊打印文檔。

  理解多線程及其同步、互斥等通信方式是理解現代操作系統的關鍵一環,當我們精通了Win32多線程程序設計后,理解和學習其它操作系統的多任務控制也非常容易。因此,學習Win32多線程不僅對理解Win32本身有重要意義,而且對學習和領會其它操作系統也有觸類旁通的作用。


深入淺出Win32多線程程序設計之基本概念

引言

  從單進程單線程到多進程多線程是操作系統發展的一種必然趨勢,當年的DOS系統屬于單任務操作系統,最優秀的程序員也只能通過駐留內存的方式實現所謂的"多任務",而如今的Win32操作系統卻可以一邊聽音樂,一邊編程,一邊打印文檔。

  理解多線程及其同步、互斥等通信方式是理解現代操作系統的關鍵一環,當我們精通了Win32多線程程序設計后,理解和學習其它操作系統的多任務控制也非常容易。許多程序員從來沒有學習過嵌入式系統領域著名的操作系統VxWorks,但是立馬就能在上面做開發,大概要歸功于平時在Win32多線程上下的功夫。

  因此,學習Win32多線程不僅對理解Win32本身有重要意義,而且對學習和領會其它操作系統也有觸類旁通的作用。

   進程與線程

  先闡述一下進程和線程的概念和區別,這是一個許多大學老師也講不清楚的問題。

  進程(Process)是具有一定獨立功能的程序關于某個數據集合上的一次運行活動,是系統進行資源分配和調度的一個獨立單位。程序只是一組指令的有序集合,它本身沒有任何運行的含義,只是一個靜態實體。而進程則不同,它是程序在某個數據集上的執行,是一個動態實體。它因創建而產生,因調度而運行,因等待資源或事件而被處于等待狀態,因完成任務而被撤消,反映了一個程序在一定的數據集上運行的全部動態過程。

  線程(Thread)是進程的一個實體,是CPU調度和分派的基本單位。線程不能夠獨立執行,必須依存在應用程序中,由應用程序提供多個線程執行控制。

  線程和進程的關系是:線程是屬于進程的,線程運行在進程空間內,同一進程所產生的線程共享同一內存空間,當進程退出時該進程所產生的線程都會被強制退出并清除。線程可與屬于同一進程的其它線程共享進程所擁有的全部資源,但是其本身基本上不擁有系統資源,只擁有一點在運行中必不可少的信息(如程序計數器、一組寄存器和棧)。

  根據進程與線程的設置,操作系統大致分為如下類型:

  (1)單進程、單線程,MS-DOS大致是這種操作系統;

  (2)多進程、單線程,多數UNIX(及類UNIX的LINUX)是這種操作系統;

  (3)多進程、多線程,Win32(Windows NT/2000/XP等)、Solaris 2.x和OS/2都是這種操作系統;

  (4)單進程、多線程,VxWorks是這種操作系統。

  在操作系統中引入線程帶來的主要好處是:

  (1)在進程內創建、終止線程比創建、終止進程要快;

  (2)同一進程內的線程間切換比進程間的切換要快,尤其是用戶級線程間的切換。另外,線程的出現還因為以下幾個原因:

  (1)并發程序的并發執行,在多處理環境下更為有效。一個并發程序可以建立一個進程,而這個并發程序中的若干并發程序段就可以分別建立若干線程,使這些線程在不同的處理機上執行。

  (2)每個進程具有獨立的地址空間,而該進程內的所有線程共享該地址空間。這樣可以解決父子進程模型中,子進程必須復制父進程地址空間的問題。

  (3)線程對解決客戶/服務器模型非常有效。

   Win32進程

  1、進程間通信(IPC)

  Win32進程間通信的方式主要有:

  (1)剪貼板(Clip Board);

  (2)動態數據交換(Dynamic Data Exchange);

  (3)部件對象模型(Component Object Model);

  (4)文件映射(File Mapping);

  (5)郵件槽(Mail Slots);

  (6)管道(Pipes);

  (7)Win32套接字(Socket);

  (8)遠程過程調用(Remote Procedure Call);

  (9)WM_COPYDATA消息(WM_COPYDATA Message)。

  2、獲取進程信息

  在WIN32中,可使用在PSAPI .DLL中提供的Process status Helper函數幫助我們獲取進程信息。

  (1)EnumProcesses()函數可以獲取進程的ID,其原型為:

BOOL EnumProcesses(DWORD * lpidProcess, DWORD cb, DWORD*cbNeeded);

  參數lpidProcess:一個足夠大的DWORD類型的數組,用于存放進程的ID值;

  參數cb:存放進程ID值的數組的最大長度,是一個DWORD類型的數據;

  參數cbNeeded:指向一個DWORD類型數據的指針,用于返回進程的數目;

  函數返回值:如果調用成功,返回TRUE,同時將所有進程的ID值存放在lpidProcess參數所指向的數組中,進程個數存放在cbNeeded參數所指向的變量中;如果調用失敗,返回FALSE。

  (2)GetModuleFileNameExA()函數可以實現通過進程句柄獲取進程文件名,其原型為:

DWORD GetModuleFileNameExA(HANDLE hProcess, HMODULE hModule,LPTSTR lpstrFileName, DWORD nsize);

  參數hProcess:接受進程句柄的參數,是HANDLE類型的變量;

  參數hModule:指針型參數,在本文的程序中取值為NULL;

  參數lpstrFileName:LPTSTR類型的指針,用于接受主調函數傳遞來的用于存放進程名的字符數組指針;

  參數nsize:lpstrFileName所指數組的長度;

  函數返回值:如果調用成功,返回一個大于0的DWORD類型的數據,同時將hProcess所對應的進程名存放在lpstrFileName參數所指向的數組中;加果調用失敗,則返回0。

  通過下列代碼就可以遍歷系統中的進程,獲得進程列表:

//獲取當前進程總數
EnumProcesses(process_ids, sizeof(process_ids), &num_processes);
//遍歷進程
for (int i = 0; i < num_processes; i++)
{
 //根據進程ID獲取句柄
 process[i] = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, 0,
 process_ids[i]);
 //通過句柄獲取進程文件名
 if (GetModuleFileNameExA(process[i], NULL, File_name, sizeof(fileName)))
  cout << fileName << endl;
}




深入淺出Win32多線程程序設計之線程控制

WIN32線程控制主要實現線程的創建、終止、掛起和恢復等操作,這些操作都依賴于WIN32提供的一組API和具體編譯器的C運行時庫函數。

  1.線程函數

  在啟動一個線程之前,必須為線程編寫一個全局的線程函數,這個線程函數接受一個32位的LPVOID作為參數,返回一個UINT,線程函數的結構為:

UINT ThreadFunction(LPVOID pParam)
{
 //線程處理代碼
 return0;
}

  在線程處理代碼部分通常包括一個死循環,該循環中先等待某事情的發生,再處理相關的工作:

while(1)
{
 WaitForSingleObject(…,…);//或WaitForMultipleObjects(…)
 //Do something
}

  一般來說,C++的類成員函數不能作為線程函數。這是因為在類中定義的成員函數,編譯器會給其加上this指針。請看下列程序:

#include "windows.h"
#include <process.h>
class ExampleTask
{
 public:
  void taskmain(LPVOID param);
  void StartTask();
};
void ExampleTask::taskmain(LPVOID param)
{}

void ExampleTask::StartTask()
{
 _beginthread(taskmain,0,NULL);
}

int main(int argc, char* argv[])
{
 ExampleTask realTimeTask;
 realTimeTask.StartTask();
 return 0;
}

  程序編譯時出現如下錯誤:

error C2664: '_beginthread' : cannot convert parameter 1 from 'void (void *)' to 'void (__cdecl *)(void *)'
None of the functions with this name in scope match the target type

  再看下列程序:

#include "windows.h"
#include <process.h>
class ExampleTask
{
 public:
  void taskmain(LPVOID param);
};

void ExampleTask::taskmain(LPVOID param)
{}

int main(int argc, char* argv[])
{
 ExampleTask realTimeTask;
 _beginthread(ExampleTask::taskmain,0,NULL);
 return 0;
}

  程序編譯時會出錯:

error C2664: '_beginthread' : cannot convert parameter 1 from 'void (void *)' to 'void (__cdecl *)(void *)'
None of the functions with this name in scope match the target type

  如果一定要以類成員函數作為線程函數,通常有如下解決方案:

  (1)將該成員函數聲明為static類型,去掉this指針;

  我們將上述二個程序改變為:

#include "windows.h"
#include <process.h>
class ExampleTask
{
 public:
  void static taskmain(LPVOID param);
  void StartTask();
};

void ExampleTask::taskmain(LPVOID param)
{}

void ExampleTask::StartTask()
{
 _beginthread(taskmain,0,NULL);
}

int main(int argc, char* argv[])
{
 ExampleTask realTimeTask;
 realTimeTask.StartTask();
 return 0;
}

#include "windows.h"
#include <process.h>
class ExampleTask
{
 public:
  void static taskmain(LPVOID param);
};

void ExampleTask::taskmain(LPVOID param)
{}

int main(int argc, char* argv[])
{
 _beginthread(ExampleTask::taskmain,0,NULL);
 return 0;
}

  均編譯通過。

  將成員函數聲明為靜態雖然可以解決作為線程函數的問題,但是它帶來了新的問題,那就是static成員函數只能訪問static成員。解決此問題的一種途徑是可以在調用類靜態成員函數(線程函數)時將this指針作為參數傳入,并在改線程函數中用強制類型轉換將this轉換成指向該類的指針,通過該指針訪問非靜態成員。

  (2)不定義類成員函數為線程函數,而將線程函數定義為類的友元函數。這樣,線程函數也可以有類成員函數同等的權限;

  我們將程序修改為:

#include "windows.h"
#include <process.h>
class ExampleTask
{
 public:
  friend void taskmain(LPVOID param);
  void StartTask();
};

void taskmain(LPVOID param)
{
 ExampleTask * pTaskMain = (ExampleTask *) param;
 //通過pTaskMain指針引用
}

void ExampleTask::StartTask()
{
 _beginthread(taskmain,0,this);
}
int main(int argc, char* argv[])
{
 ExampleTask realTimeTask;
 realTimeTask.StartTask();
 return 0;
}

  (3)可以對非靜態成員函數實現回調,并訪問非靜態成員,此法涉及到一些高級技巧,在此不再詳述。



深入淺出Win32多線程程序設計之線程通信

簡介

  線程之間通信的兩個基本問題是互斥和同步。

  線程同步是指線程之間所具有的一種制約關系,一個線程的執行依賴另一個線程的消息,當它沒有得到另一個線程的消息時應等待,直到消息到達時才被喚醒。

  線程互斥是指對于共享的操作系統資源(指的是廣義的"資源",而不是Windows的.res文件,譬如全局變量就是一種共享資源),在各線程訪問時的排它性。當有若干個線程都要使用某一共享資源時,任何時刻最多只允許一個線程去使用,其它要使用該資源的線程必須等待,直到占用資源者釋放該資源。

  線程互斥是一種特殊的線程同步。

  實際上,互斥和同步對應著線程間通信發生的兩種情況:

  (1)當有多個線程訪問共享資源而不使資源被破壞時;

  (2)當一個線程需要將某個任務已經完成的情況通知另外一個或多個線程時。

  在WIN32中,同步機制主要有以下幾種:

  (1)事件(Event);

  (2)信號量(semaphore);

  (3)互斥量(mutex);

  (4)臨界區(Critical section)。

   全局變量

  因為進程中的所有線程均可以訪問所有的全局變量,因而全局變量成為Win32多線程通信的最簡單方式。例如:

int var; //全局變量
UINT ThreadFunction(LPVOIDpParam)
{
 var = 0;
 while (var < MaxValue)
 {
  //線程處理
  ::InterlockedIncrement(long*) &var);
 }
 return 0;
}
請看下列程序:
int globalFlag = false;
DWORD WINAPI ThreadFunc(LPVOID n)
{
 Sleep(2000);
 globalFlag = true;

 return 0;
}

int main()
{
 HANDLE hThrd;
 DWORD threadId;

 hThrd = CreateThread(NULL, 0, ThreadFunc, NULL, 0, &threadId);
 if (hThrd)
 {
  printf("Thread launched\n");
  CloseHandle(hThrd);
 }

 while (!globalFlag)
 ;
 printf("exit\n");
}

  上述程序中使用全局變量和while循環查詢進行線程間同步,實際上,這是一種應該避免的方法,因為:

  (1)當主線程必須使自己與ThreadFunc函數的完成運行實現同步時,它并沒有使自己進入睡眠狀態。由于主線程沒有進入睡眠狀態,因此操作系統繼續為它調度C P U時間,這就要占用其他線程的寶貴時間周期;

  (2)當主線程的優先級高于執行ThreadFunc函數的線程時,就會發生globalFlag永遠不能被賦值為true的情況。因為在這種情況下,系統決不會將任何時間片分配給ThreadFunc線程。

   事件

  事件(Event)是WIN32提供的最靈活的線程間同步方式,事件可以處于激發狀態(signaled or true)或未激發狀態(unsignal or false)。根據狀態變遷方式的不同,事件可分為兩類:

  (1)手動設置:這種對象只可能用程序手動設置,在需要該事件或者事件發生時,采用SetEvent及ResetEvent來進行設置。

  (2)自動恢復:一旦事件發生并被處理后,自動恢復到沒有事件狀態,不需要再次設置。

  創建事件的函數原型為:

HANDLE CreateEvent(
 LPSECURITY_ATTRIBUTES lpEventAttributes,
 // SECURITY_ATTRIBUTES結構指針,可為NULL
 BOOL bManualReset,
 // 手動/自動
 // TRUE:在WaitForSingleObject后必須手動調用ResetEvent清除信號
 // FALSE:在WaitForSingleObject后,系統自動清除事件信號
 BOOL bInitialState, //初始狀態
 LPCTSTR lpName //事件的名稱
);

  使用"事件"機制應注意以下事項:

  (1)如果跨進程訪問事件,必須對事件命名,在對事件命名的時候,要注意不要與系統命名空間中的其它全局命名對象沖突;

  (2)事件是否要自動恢復;

  (3)事件的初始狀態設置。

  由于event對象屬于內核對象,故進程B可以調用OpenEvent函數通過對象的名字獲得進程A中event對象的句柄,然后將這個句柄用于ResetEvent、SetEvent和WaitForMultipleObjects等函數中。此法可以實現一個進程的線程控制另一進程中線程的運行,例如:

HANDLE hEvent=OpenEvent(EVENT_ALL_ACCESS,true,"MyEvent");
ResetEvent(hEvent);



深入淺出Win32多線程設計之MFC的多線程

1、創建和終止線程

  在MFC程序中創建一個線程,宜調用AfxBeginThread函數。該函數因參數不同而具有兩種重載版本,分別對應工作者線程和用戶接口(UI)線程。

  工作者線程

CWinThread *AfxBeginThread(
 AFX_THREADPROC pfnThreadProc, //控制函數
 LPVOID pParam, //傳遞給控制函數的參數
 int nPriority = THREAD_PRIORITY_NORMAL, //線程的優先級
 UINT nStackSize = 0, //線程的堆棧大小
 DWORD dwCreateFlags = 0, //線程的創建標志
 LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL //線程的安全屬性
);

  工作者線程編程較為簡單,只需編寫線程控制函數和啟動線程即可。下面的代碼給出了定義一個控制函數和啟動它的過程:

//線程控制函數
UINT MfcThreadProc(LPVOID lpParam)
{
 CExampleClass *lpObject = (CExampleClass*)lpParam;
 if (lpObject == NULL || !lpObject->IsKindof(RUNTIME_CLASS(CExampleClass)))
  return - 1; //輸入參數非法
 //線程成功啟動
 while (1)
 {
  ...//
 }
 return 0;
}

//在MFC程序中啟動線程
AfxBeginThread(MfcThreadProc, lpObject);

  UI線程

  創建用戶界面線程時,必須首先從CWinThread 派生類,并使用 DECLARE_DYNCREATE 和 IMPLEMENT_DYNCREATE 宏聲明此類。

  下面給出了CWinThread類的原型(添加了關于其重要函數功能和是否需要被繼承類重載的注釋):

class CWinThread : public CCmdTarget
{
 DECLARE_DYNAMIC(CWinThread)

 public:
  // Constructors
  CWinThread();
  BOOL CreateThread(DWORD dwCreateFlags = 0, UINT nStackSize = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL);

  // Attributes
  CWnd* m_pMainWnd; // main window (usually same AfxGetApp()->m_pMainWnd)
  CWnd* m_pActiveWnd; // active main window (may not be m_pMainWnd)
  BOOL m_bAutoDelete; // enables 'delete this' after thread termination

  // only valid while running
  HANDLE m_hThread; // this thread's HANDLE
  operator HANDLE() const;
  DWORD m_nThreadID; // this thread's ID

  int GetThreadPriority();
  BOOL SetThreadPriority(int nPriority);

  // Operations
  DWORD SuspendThread();
  DWORD ResumeThread();
  BOOL PostThreadMessage(UINT message, WPARAM wParam, LPARAM lParam);

  // Overridables
  //執行線程實例初始化,必須重寫
  virtual BOOL InitInstance();

  // running and idle processing
  //控制線程的函數,包含消息泵,一般不重寫
  virtual int Run();

  //消息調度到TranslateMessage和DispatchMessage之前對其進行篩選,
  //通常不重寫
  virtual BOOL PreTranslateMessage(MSG* pMsg);

  virtual BOOL PumpMessage(); // low level message pump

  //執行線程特定的閑置時間處理,通常不重寫
  virtual BOOL OnIdle(LONG lCount); // return TRUE if more idle processing
  virtual BOOL IsIdleMessage(MSG* pMsg); // checks for special messages

  //線程終止時執行清除,通常需要重寫
  virtual int ExitInstance(); // default will 'delete this'

  //截獲由線程的消息和命令處理程序引發的未處理異常,通常不重寫
  virtual LRESULT ProcessWndProcException(CException* e, const MSG* pMsg);

  // Advanced: handling messages sent to message filter hook
  virtual BOOL ProcessMessageFilter(int code, LPMSG lpMsg);

  // Advanced: virtual access to m_pMainWnd
  virtual CWnd* GetMainWnd();

  // Implementation
 public:
  virtual ~CWinThread();
  #ifdef _DEBUG
   virtual void AssertValid() const;
   virtual void Dump(CDumpContext& dc) const;
   int m_nDisablePumpCount; // Diagnostic trap to detect illegal re-entrancy
  #endif
  void CommonConstruct();
  virtual void Delete();
  // 'delete this' only if m_bAutoDelete == TRUE

  // message pump for Run
  MSG m_msgCur; // current message

 public:
  // constructor used by implementation of AfxBeginThread
  CWinThread(AFX_THREADPROC pfnThreadProc, LPVOID pParam);

  // valid after construction
  LPVOID m_pThreadParams; // generic parameters passed to starting function
  AFX_THREADPROC m_pfnThreadProc;

  // set after OLE is initialized
  void (AFXAPI* m_lpfnOleTermOrFreeLib)(BOOL, BOOL);
  COleMessageFilter* m_pMessageFilter;

 protected:
  CPoint m_ptCursorLast; // last mouse position
  UINT m_nMsgLast; // last mouse message
  BOOL DispatchThreadMessageEx(MSG* msg); // helper
  void DispatchThreadMessage(MSG* msg); // obsolete
};

  啟動UI線程的AfxBeginThread函數的原型為:

CWinThread *AfxBeginThread(
 //從CWinThread派生的類的 RUNTIME_CLASS
 CRuntimeClass *pThreadClass,
 int nPriority = THREAD_PRIORITY_NORMAL,
 UINT nStackSize = 0,
 DWORD dwCreateFlags = 0,
 LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL
);

  我們可以方便地使用VC++ 6.0類向導定義一個繼承自CWinThread的用戶線程類。下面給出產生我們自定義的CWinThread子類CMyUIThread的方法。

  打開VC++ 6.0類向導,在如下窗口中選擇Base Class類為CWinThread,輸入子類名為CMyUIThread,點擊"OK"按鈕后就產生了類CMyUIThread。


  其源代碼框架為:

/
// CMyUIThread thread

class CMyUIThread : public CWinThread
{
 DECLARE_DYNCREATE(CMyUIThread)
 protected:
  CMyUIThread(); // protected constructor used by dynamic creation

  // Attributes
 public:

  // Operations
 public:

  // Overrides
  // ClassWizard generated virtual function overrides
  //{{AFX_VIRTUAL(CMyUIThread)
  public:
   virtual BOOL InitInstance();
   virtual int ExitInstance();
  //}}AFX_VIRTUAL

  // Implementation
 protected:
  virtual ~CMyUIThread();

  // Generated message map functions
  //{{AFX_MSG(CMyUIThread)
   // NOTE - the ClassWizard will add and remove member functions here.
  //}}AFX_MSG

 DECLARE_MESSAGE_MAP()
};

/
// CMyUIThread

IMPLEMENT_DYNCREATE(CMyUIThread, CWinThread)

CMyUIThread::CMyUIThread()
{}

CMyUIThread::~CMyUIThread()
{}

BOOL CMyUIThread::InitInstance()
{
 // TODO: perform and per-thread initialization here
 return TRUE;
}

int CMyUIThread::ExitInstance()
{
 // TODO: perform any per-thread cleanup here
 return CWinThread::ExitInstance();
}

BEGIN_MESSAGE_MAP(CMyUIThread, CWinThread)
//{{AFX_MSG_MAP(CMyUIThread)
// NOTE - the ClassWizard will add and remove mapping macros here.
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

  使用下列代碼就可以啟動這個UI線程:

CMyUIThread *pThread;
pThread = (CMyUIThread*)
AfxBeginThread( RUNTIME_CLASS(CMyUIThread) );

  另外,我們也可以不用AfxBeginThread 創建線程,而是分如下兩步完成:

  (1)調用線程類的構造函數創建一個線程對象;

  (2)調用CWinThread::CreateThread函數來啟動該線程。

  在線程自身內調用AfxEndThread函數可以終止該線程:

void AfxEndThread(
 UINT nExitCode //the exit code of the thread
);

  對于UI線程而言,如果消息隊列中放入了WM_QUIT消息,將結束線程。

  關于UI線程和工作者線程的分配,最好的做法是:將所有與UI相關的操作放入主線程,其它的純粹的運算工作交給獨立的數個工作者線程。

  候捷先生早些時間喜歡為MDI程序的每個窗口創建一個線程,他后來澄清了這個錯誤。因為如果為MDI程序的每個窗口都單獨創建一個線程,在窗口進行切換的時候,將進行線程的上下文切換!



深入淺出Win32多線程程序設計之綜合實例

本章我們將以工業控制和嵌入式系統中運用極為廣泛的串口通信為例講述多線程的典型應用。

  而網絡通信也是多線程應用最廣泛的領域之一,所以本章的最后一節也將對多線程網絡通信進行簡短的描述。

   1.串口通信

  在工業控制系統中,工控機(一般都基于PC Windows平臺)經常需要與單片機通過串口進行通信。因此,操作和使用PC的串口成為大多數單片機、嵌入式系統領域工程師必須具備的能力。

  串口的使用需要通過三個步驟來完成的:

  (1) 打開通信端口;

  (2) 初始化串口,設置波特率、數據位、停止位、奇偶校驗等參數。為了給讀者一個直觀的印象,下圖從Windows的"控制面板->系統->設備管理器->通信端口(COM1)"打開COM的設置窗口:



  (3) 讀寫串口。

  在WIN32平臺下,對通信端口進行操作跟基本的文件操作一樣。

  創建/打開COM資源

  下列函數如果調用成功,則返回一個標識通信端口的句柄,否則返回-1:

HADLE CreateFile(PCTSTR lpFileName, //通信端口名,如"COM1"
WORD dwDesiredAccess, //對資源的訪問類型
WORD dwShareMode, //指定共享模式,COM不能共享,該參數為0
PSECURITY_ATTRIBUTES lpSecurityAttributes,
//安全描述符指針,可為NULL
WORD dwCreationDisposition, //創建方式
WORD dwFlagsAndAttributes, //文件屬性,可為NULL
HANDLE hTemplateFile //模板文件句柄,置為NULL
);

  獲得/設置COM屬性

  下列函數可以獲得COM口的設備控制塊,從而獲得相關參數:

BOOL WINAPI GetCommState(
 HANDLE hFile, //標識通信端口的句柄
 LPDCB lpDCB //指向一個設備控制塊(DCB結構)的指針
);

  如果要調整通信端口的參數,則需要重新配置設備控制塊,再用WIN32 API SetCommState()函數進行設置:

BOOL SetCommState(
 HANDLE hFile, //標識通信端口的句柄
 LPDCB lpDCB //指向一個設備控制塊(DCB結構)的指針
);

  DCB結構包含了串口的各項參數設置,如下:

typedef struct _DCB
{
 // dcb
 DWORD DCBlength; // sizeof(DCB)
 DWORD BaudRate; // current baud rate
 DWORD fBinary: 1; // binary mode, no EOF check
 DWORD fParity: 1; // enable parity checking
 DWORD fOutxCtsFlow: 1; // CTS output flow control
 DWORD fOutxDsrFlow: 1; // DSR output flow control
 DWORD fDtrControl: 2; // DTR flow control type
 DWORD fDsrSensitivity: 1; // DSR sensitivity
 DWORD fTXContinueOnXoff: 1; // XOFF continues Tx
 DWORD fOutX: 1; // XON/XOFF out flow control
 DWORD fInX: 1; // XON/XOFF in flow control
 DWORD fErrorChar: 1; // enable error replacement
 DWORD fNull: 1; // enable null stripping
 DWORD fRtsControl: 2; // RTS flow control
 DWORD fAbortOnError: 1; // abort reads/writes on error
 DWORD fDummy2: 17; // reserved
 WORD wReserved; // not currently used
 WORD XonLim; // transmit XON threshold
 WORD XoffLim; // transmit XOFF threshold
 BYTE ByteSize; // number of bits/byte, 4-8
 BYTE Parity; // 0-4=no,odd,even,mark,space
 BYTE StopBits; // 0,1,2 = 1, 1.5, 2
 char XonChar; // Tx and Rx XON character
 char XoffChar; // Tx and Rx XOFF character
 char ErrorChar; // error replacement character
 char EofChar; // end of input character
 char EvtChar; // received event character
 WORD wReserved1; // reserved; do not use
} DCB;

  讀寫串口

  在讀寫串口之前,還要用PurgeComm()函數清空緩沖區,并用SetCommMask ()函數設置事件掩模來監視指定通信端口上的事件,其原型為:

BOOL SetCommMask(
 HANDLE hFile, //標識通信端口的句柄
 DWORD dwEvtMask //能夠使能的通信事件
);

  串口上可能發生的事件如下表所示:

事件描述
EV_BREAKA break was detected on input.
EV_CTSThe CTS (clear-to-send) signal changed state.
EV_DSRThe DSR(data-set-ready) signal changed state.
EV_ERRA line-status error occurred. Line-status errors are CE_FRAME, CE_OVERRUN, and CE_RXPARITY.
EV_RINGA ring indicator was detected.
EV_RLSDThe RLSD (receive-line-signal-detect) signal changed state.
EV_RXCHARA character was received and placed in the input buffer.
EV_RXFLAGThe event character was received and placed in the input buffer. The event character is specified in the device's DCB structure, which is applied to a serial port by using the SetCommState function.
EV_TXEMPTYThe last character in the output buffer was sent.

  在設置好事件掩模后,我們就可以利用WaitCommEvent()函數來等待串口上發生事件,其函數原型為:

BOOL WaitCommEvent(
 HANDLE hFile, //標識通信端口的句柄
 LPDWORD lpEvtMask, //指向存放事件標識變量的指針
 LPOVERLAPPED lpOverlapped, // 指向overlapped結構
);

  我們可以在發生事件后,根據相應的事件類型,進行串口的讀寫操作:

BOOL ReadFile(HANDLE hFile, //標識通信端口的句柄
 LPVOID lpBuffer, //輸入數據Buffer指針
 DWORD nNumberOfBytesToRead, // 需要讀取的字節數
 LPDWORD lpNumberOfBytesRead, //實際讀取的字節數指針
 LPOVERLAPPED lpOverlapped //指向overlapped結構
);
BOOL WriteFile(HANDLE hFile, //標識通信端口的句柄
 LPCVOID lpBuffer, //輸出數據Buffer指針
 DWORD nNumberOfBytesToWrite, //需要寫的字節數
 LPDWORD lpNumberOfBytesWritten, //實際寫入的字節數指針
 LPOVERLAPPED lpOverlapped //指向overlapped結構
);

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/451686.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/451686.shtml
英文地址,請注明出處:http://en.pswp.cn/news/451686.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

git中使用fork

在git中使用fork相當于你在原項目的主分支上又建立了一個分支&#xff0c;你可以在該分支上任意修改。如果想將你的修改合并到原項目中時&#xff0c;可以pull request&#xff0c;這樣原項目的作者如果認同你的修改&#xff0c;就可以將你修改的東西合并到原項目的主分支上去。…

一、【Collection、泛型】

主要內容 Collection集合迭代器增強for泛型 教學目標 能夠說出集合與數組的區別 說出Collection集合的常用功能 能夠使用迭代器對集合進行取元素 能夠說出集合的使用細節 能夠使用集合存儲自定義類型 能夠使用foreach循環遍歷集合 能夠使用泛型定義集合對象 能夠理解泛型上下…

字符裝換

2019獨角獸企業重金招聘Python工程師標準>>> 字母大小寫轉換 a →A char toUpperCase( char ch){ if((ch >a) && (ch <z)){ return (char)(ch - 32); // 主要 這里(char)是必要的&#xff0c;因為char -32是返回的數值&#xff0c;必須轉換成對應的字…

解決 Unable to translate SQLException with Error code ‘17059‘, will now try the fallback translator

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 1.報錯&#xff1a; Unable to translate SQLException with Error code 17059, will now try the fallback translator 報錯如下&…

企業使用開源軟件的風險

很多時候&#xff0c;我們過高地估計了開源軟件面臨的版權威脅&#xff0c;開源軟件并非天生就比專有軟件存在更多風險。雖然在企業中開源軟件越來越普及&#xff0c;但開源軟件始終難以擺脫知識產權帶來的陰影。2007年&#xff0c;微軟聲稱開源軟件侵犯了它235項專利&#xff…

杭電多校 Harvest of Apples 莫隊

問題 B: Harvest of Apples 時間限制: 1 Sec 內存限制: 128 MB 提交: 78 解決: 35 [提交] [狀態] [討論版] [命題人:admin] 題目描述 There are n apples on a tree, numbered from 1 to n. Count the number of ways to pick at most m apples. 輸入 The first line of the …

linux和GNU之間的關系

Linux只是一個操作系統內核而已&#xff0c;而GNU提供了大量的自由軟件來豐富在其之上各種應用程序。 因此&#xff0c;嚴格來講&#xff0c;Linux這個詞本身只表示Linux內核&#xff0c;但在實際上人們已經習慣了用Linux來形容整個基于Linux內核&#xff0c;并且使用GNU 工程各…

二、【List、Set、數據結構、Collections】

主要內容 數據結構List集合Set集合Collections 教學目標 能夠說出List集合特點 能夠說出常見的數據結構 能夠說出數組結構特點 能夠說出棧結構特點 能夠說出隊列結構特點 能夠說出單向鏈表結構特點 能夠說出Set集合的特點 能夠說出哈希表的特點 使用HashSet集合存儲自定義元素…

@Java | Thread synchronized - [ 線程同步鎖 基本使用]

對實現了Runnable或者Callable接口類&#xff0c;可以通過多線程執行同一實例的run或call方法&#xff0c;那么對于同一實例中的局部變量&#xff08;非方法變量&#xff09;就會有多個線程進行更改或讀取&#xff0c;這就會導致數據不一致&#xff0c;synchronized(關鍵字)可以…

解決bad SQL grammar []; nested exception is java.sql.SQLSyntaxErrorException: ORA-00911: 無效字符

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 1. 報錯&#xff1a; ### Cause: java.sql.SQLSyntaxErrorException: ORA-00911: 無效字符; bad SQL grammar []; nested exception is …

開源代碼的使用 二次開發

開源開發&#xff0c;就我的理解&#xff0c;有三種。 1、當作底層基礎&#xff0c;使用。例如大家使用mysql就算。有人會認為我說錯了。但我認為&#xff0c;開發不代表就是要同一個語言&#xff0c;甚至修改代碼。例如我們使用動態庫&#xff0c;原先的動態庫是什么寫的并不重…

Java Application和Java Applet

Java Applet和Java Application 主要區別&#xff1a; &#xff08;1&#xff09;運行方式不同。Java Applet程序不能單獨運行&#xff0c;它必須依附于一個用HTML語言編寫的網頁并嵌入其中&#xff0c;通過與Java兼容的瀏覽器來控制執行。 Java Application是完整的程序&a…

激活prompt

1.下載SQLPrompt 2. 斷網&#xff0c; 打開注冊機&#xff0c;拷貝驗證碼 2. 點擊activate&#xff0c; 拷貝代碼 轉載于:https://www.cnblogs.com/zxhome/p/9459415.html

Map 四種獲取 key 和 value 值的方法,以及對 map 中的元素排序

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。1. 獲取map的值主要有四種方法&#xff0c;分為兩類&#xff1a; 調用 map.keySet() 方法來獲取 key 和 value 的值&#xff1b; 通…

三、【Map】

主要內容 Map集合 教學目標 能夠說出Map集合特點 使用Map集合添加方法保存數據 使用”鍵找值”的方式遍歷Map集合 使用”鍵值對”的方式遍歷Map集合 能夠使用HashMap存儲自定義鍵值對的數據 能夠使用HashMap編寫斗地主洗牌發牌案例 第一章 Map集合 1.1 概述 現實生活中&am…

五種開源協議的比較(BSD,Apache,GPL,LGPL,MIT) – 整理

當Adobe、Microsoft、Sun等一系列巨頭開始表現出對”開源”的青睞時&#xff0c;”開源”的時代即將到來&#xff01; 最初來自&#xff1a;sinoprise.com/read.php?tid-662-page-e-fpage-1.html&#xff08;遺憾的是這個鏈接已經打不開了&#xff09;&#xff0c;我基本未改…

[轉]自然語言處理中的Attention Model:是什么及為什么

自然語言處理中的Attention Model&#xff1a;是什么及為什么 https://blog.csdn.net/malefactor/article/details/50550211/* 版權聲明&#xff1a;可以任意轉載&#xff0c;轉載時請標明文章原始出處和作者信息 .*/ author: 張俊林 要是關注深度學習在自然語言處理方面…

關西旅游地名讀法學習

京都個人旅行ための自己勉強 京都篇 伏見稲荷大社「ふしみいなりだいしゃ」 京都府京都市伏見區深草にある神社。舊稱は稲荷神社 全國に約三萬社あるといわれる稲荷神社の総本社である。 初詣では近畿地方の社寺で最多の參拝者を集める。(日本第&#xff14;位)。 清水寺 「き…

jsp頁面c標簽循環map , c:foreach 循環map

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 <c:forEach items"${customerMap}" var"item"> ${item.code} ${item.name} </c:forEach> map…