1. 程式人生 > 其它 >巨集與WINDOWS API簡單使用

巨集與WINDOWS API簡單使用

巨集與WINDOWS API簡單使用

#define定義巨集

定義巨集

語法: #define <巨集名> <字串>

#define PI 3.1415926

#define PI 3.1415926

int main() {
    cout << PI << endl;
}
//結果:3.1415926

定義一個H識別符號。,程式碼中的H將會被刪除。

#define H


int main() {


    int H a=1;

    cout << a << endl;

}

取消巨集定義

語法:#undef H

定義複雜的巨集表示式

#define MAX(a,b) (a)>(b)?(a):(b)

int main() {

    int a = 5;
    int b = 10;
    int c = MAX(a, b);
    cout << c << endl;
}

預編譯指令

程式的編譯過程可以分為預處理、編譯、彙編三部分,其中預處理是首先執行的過程,預處理過程掃描程式原始碼,對其進行初步的轉換,產生新的原始碼提供給編譯器。
預處理過程讀入原始碼之後,會檢查程式碼裡包含的預處理指令,完成諸如包含其他原始檔、定義巨集、根據條件決定編譯時是否包含某些程式碼的工作

#指令

預處理指令以#號開頭,並且#號必須是該行除了任何空白字元外的第一個字元。
#後是指令關鍵字,在關鍵字和#號之間允許存在任意個數的空白字元。整行語句構成了一條預處理指令,該指令將在編譯器進行編譯之前對原始碼做某些轉換。
單純一個#號表示空指令,沒有任何作用。

#include指令

二 #include指令
#include預處理指令的作用是在指令處展開被包含的檔案。展開被包含的檔案之後,在程式碼就可以正常地呼叫該檔案中所宣告的變數和函式。#include指令有兩種使用方法
#include <xxx.h>
#include "xxx.h"

#define、#undef指令

define指令定義了一個識別符號及一個串,識別符號稱為巨集名,源程式中巨集名的每次出現都會用其定義的串進行替換,稱為巨集替換。

undef指令取消一個已定義的巨集。

巨集一般使用大寫字母定義,其可以出現在程式的任意地方。巨集替換僅僅是以文字串代替巨集識別符號的過程,該過程很容易出現一些邏輯上的錯誤,需要仔細處理一些關於括號的問題。

#if、#elif、#else、#endif指令

這幾個指令稱為條件編譯指令,可對程式原始碼的各部分有選擇地進行編譯。
跟一般的if、else if、else語句類似,如果一個條件上的值為真,則編譯它對應的程式碼,否則提過這些程式碼,測試下一個條件上的值是否為真。注意,作為條件的表示式是在編譯時求值的,它必須僅含常量及已定義過的識別符號,不可使用變數,也不可以含有操作符sizeof(sizeof也是編譯時求值)。
命令#endif標識一個#if塊的結束。

#ifdef、#ifndef、#endif指令

這幾個也是條件編譯指令,其檢查後面指定的巨集是否已經定義,然後根據檢查結果選擇是否要編譯後面語句。其中#ifdef表示”如果有定義“,#ifndef表示”如果沒有定義“。

#line指令

C語言中可以使用__FILE__表示本行語句所在原始檔的檔名,使用__LINE__表示本行語句在原始檔中的位置資訊。#line指令可以重新設定這兩個變數的值,其語法格式為
#line number["filename"]

其中第二個引數檔名是可省略的,並且其指定的行號在實際的下一行語句才會發生作用。

#error指令

#error指令在編譯時輸出編譯錯誤資訊,可以方便程式設計師檢查出現的錯誤


void test5()
{
#define OPTION 3
#if OPTION == 1
cout << "Option: 1" << endl;
#elif OPTION == 2
cout << "Option: 2" << endl;
#else
#error ILLEGAL OPTION! //fatal error C1189: #error :  ILLEGAL OPTION!
#endif

#pragma指令

該指令用來來設定編譯器的狀態或者是指示編譯器完成一些特定的動作,它有許多不同的引數。

  1. pragma once

在標頭檔案的最開始加入這條指令可以保證標頭檔案只被編譯一次。它可以實現上述使用#ifndef實現不重複包含標頭檔案同樣的功能,但可能會有部分編譯系統不支援。

  1. pragma message

該指令能夠讓編譯器遇到這條指令時就在編譯輸出視窗中將訊息文字打印出來。其使用方法為:#pragma message(“訊息文字”)
通過這條指令我們可以方便地記錄在是否在原始碼中定義過某個巨集

  1. #pragma warning

該指令能夠控制編譯器發出警告的方式,其用法舉例如:#pragma warning(disable : 4507 34; once : 4385; error : 164)
這個指令有三部分組成,其中disable部分表示忽略編號為4507和34的警告資訊,once部分表示編號為4385的警告資訊只顯示一次,error部分表示把編號為164的警告資訊當做錯誤。
另外,其還有兩個用法

pragma warning(push [, n]):儲存所有警告資訊的現有的警告狀態,後面n是可選的,表示把全域性警告等級設為n。

pragma warning(pop):彈出最後一個警告資訊,取消在入棧和出棧之間所作的一切改動。

  1. pragma comment

    該指令將一個註釋記錄放入一個物件檔案或可執行檔案中。其使用方法為:#pragma comment(comment-type ,["commentstring"])
    其中comment-type是一個預定義的識別符號,指定註釋的型別,應該是compiler,exestr,lib,linker之一。常用的是lib關鍵字,可以幫我們連入一個庫檔案。 如

    pragma comment(lib, "my.lib")

  2. pragma hdrstop

    該指令表示預編譯標頭檔案到此為止,後面的標頭檔案不進行預編譯。

  3. pragma resource

    該指令表示把指定檔案中的資源加入工程,如

    pragma resource "*.dfm"

  4. pragma code_seg

該指令能夠設定程式中函式程式碼存放的程式碼段,開發驅動程式的時候會使用到。使用方法為:#pragma code_seg(["section-name" [,"section-class"] ])。

  1. pragma data_seg

該指令建立一個新的資料段並定義共享資料。一般用於DLL中,在DLL中定義一個共享的有名字的資料段,這個資料段中的全域性變數可以被多個程序共享,否則多個程序之間無法共享DLL中的全域性變數。

  1. #pragma pack

該指令規定資料在記憶體中的對齊長度

WINDOWS API資料型別

WINDOWS資料型別
BOOL int
BYTE unsingend char
INT int
CONST const
DWORD unsingend long
DWORD32 unsingend int
DWORD64 unsingend long long
FLOAT float
HANDLE void
HINSTANCE 指標
HKEK 指標
HWND 指標
UINT unsingend int
LPARAM long
WPARAM unsingend int
LPCSTR const char*
LPCWSTR const wchar_t*
VOID void
TCHAR 如果定義UNICODE巨集為WCHAR,否則為CHAR
WINAPI __stdcall
LPCSTSTR TCHAR*
WORD unsingend char
LPDWORD DWORD*

訊息框

使用MessageBox來做案例

int MessageBox(
  HWND    hWnd,
  LPCTSTR lpText,
  LPCTSTR lpCaption,
  UINT    uType
);

第一個引數型別為HWND,轉到定義

DECLARE_HANDLE            (HWND);

...
    
#define DECLARE_HANDLE(name) struct name##__{int unused;};
    typedef struct name##__ *name

實際上這裡就是一個結構體,定意為 *name傳入name##__即HWND,而name##__{int unused;}呼叫結構體並且傳入int型別資訊。

看到第二和第三個引數LPCTSTR

typedef LPCWSTR PCTSTR, LPCTSTR;
...
typedef _Null_terminated_ CONST WCHAR UNALIGNED *LPCUWSTR, *PCUWSTR;

是一個LPCWSTRPCTSTR型別,是一個WCHAR型別引數

_NullNull_terminated 巨集的含義 巨集是一個頭部註解,藐視了一些型別.如何用作函式的引數以及返回值.

第三個引數實際上不用看了在這裡簡單寫一下。

typedef unsigned int        UINT;

案例:

int main(){
    
MessageBox(0, L"hhh", L"hhh", MB_OK);
    
}

注意點

在這裡面多字符集的編碼和unicode編碼還不太一樣。看到MessageBox中定義的程式碼。

#ifdef UNICODE
    return MessageBoxW(
#else
    return MessageBoxA(
#endif
        hWnd,
    lpText,
    lpCaption,
    uType
        );
}

預編譯指令中,如果為UNICODE編碼則返回MessageBoxW,否則返回MessageBoxA

對比一下MessageBoxA和MessageBoxW

MessageBoxW(
    _In_opt_ HWND hWnd,
    _In_opt_ LPCWSTR lpText,
    _In_opt_ LPCWSTR lpCaption,
    _In_ UINT uType);


MessageBoxA(
    _In_opt_ HWND hWnd,
    _In_opt_ LPCSTR lpText,
    _In_opt_ LPCSTR lpCaption,
    _In_ UINT uType);

第二、三使用的資料型別不一致。

Link視窗案例

GetStdHandle

首先需要獲取輸入輸出,則需要呼叫GetStdHandle來實現。先來看看api文件。

HANDLE WINAPI GetStdHandle(
  _In_ DWORD nStdHandle
);

_In_其實沒有什麼實際一樣,只是標識這裡是一個接受輸入的。

DWORD型別的nStdHandle引數進行傳入。返回值為HANDLE

int main() {
    HANDLE in;
    HANDLE out;
    HANDLE error;
    in = GetStdHandle(STD_INPUT_HANDLE);
    out = GetStdHandle(STD_OUTPUT_HANDLE);
    error = GetStdHandle(STD_ERROR_HANDLE);
    if (in != INVALID_HANDLE_VALUE) {
        cout << "開啟輸入控制代碼成功" << endl;
    
    }
    
}

SetConsoleWindowInfo

設定控制檯螢幕緩衝區視窗的當前大小和位置

引數:

hConsoleOutput [in]
控制檯螢幕緩衝區的控制代碼。控制代碼必須具有GENERIC_READ訪問許可權。有關更多資訊,請參閱控制檯緩衝區安全和訪問許可權。

bAbsolute [in]
如果此引數為TRUE,則座標指定視窗的新左上角和右下角。如果為FALSE,則座標是相對於當前視窗角座標的。

lpConsoleWindow [in]
指向SMALL_RECT結構的指標,該結構指定視窗的新左上角和右下角。
BOOL WINAPI SetConsoleWindowInfo(
  _In_       HANDLE     hConsoleOutput,
  _In_       BOOL       bAbsolute,
  _In_ const SMALL_RECT *lpConsoleWindow
);

SMALL_RECT型別為一個結構體

typedef struct _SMALL_RECT {
    SHORT Left;
    SHORT Top;
    SHORT Right;
    SHORT Bottom;
} SMALL_RECT, *PSMALL_RECT;

實現程式碼:

int main() {



    HANDLE in;
    in = GetStdHandle(STD_INPUT_HANDLE);
    
    SMALL_RECT small_rect{ 0,0,30,30 };


    SetConsoleWindowInfo(in, TRUE, &small_rect);
    SetConsoleTitle(L"aaaa");

    while (true);

    return 0;
}