『轉載』從記憶體資源中載入C++程式集:CMemLoadDll

来源:http://www.cnblogs.com/shuxiaolong/archive/2017/10/18/CMemLoadDll_CPPLib.html
-Advertisement-
Play Games

MemLoadDll.h MemLoadDll.cpp ...


MemLoadDll.h

#if !defined(Q_OS_LINUX)  
#pragma once  
  
typedef   BOOL (__stdcall *ProcDllMain)(HINSTANCE, DWORD,  LPVOID );  
  
class CMemLoadDll  
{  
public:  
    CMemLoadDll();  
    ~CMemLoadDll();  
    BOOL    MemLoadLibrary( void *lpFileData , int DataLength);  // Dll file data buffer  
    FARPROC MemGetProcAddress(LPCSTR lpProcName);  
private:  
    BOOL isLoadOk;  
    BOOL CheckDataValide(void *lpFileData, int DataLength);  
    int  CalcTotalImageSize();  
    void CopyDllDatas(void *pDest, void *pSrc);  
    BOOL FillRavAddress(void *pBase);  
    void DoRelocation(void *pNewBase);  
    int  GetAlignedSize(int Origin, int Alignment);  
private:  
    ProcDllMain pDllMain;  
  
private:  
    DWORD  pImageBase;  
    PIMAGE_DOS_HEADER pDosHeader;  
    PIMAGE_NT_HEADERS pNTHeader;  
    PIMAGE_SECTION_HEADER pSectionHeader;  
  
};  
#endif  

 

MemLoadDll.cpp

#if !defined(Q_OS_LINUX)  
  
#include <windows.h>  
#include <assert.h>  
#include "MemLoadDll.h"  
  
#include "QDebug"  
  
  
CMemLoadDll::CMemLoadDll()  
{  
    isLoadOk = FALSE;  
    pImageBase = NULL;  
    pDllMain = NULL;  
}  
  
CMemLoadDll::~CMemLoadDll()  
{  
    if(isLoadOk)  
    {  
        assert(pImageBase != NULL);  
        assert(pDllMain != NULL);  
        //脫鉤,準備卸載dll  
        pDllMain((HINSTANCE)pImageBase,DLL_PROCESS_DETACH,0);  
        VirtualFree((LPVOID)pImageBase, 0, MEM_RELEASE);  
    }  
}  
  
//MemLoadLibrary函數從記憶體緩衝區數據中載入一個dll到當前進程的地址空間,預設位置0x10000000  
//返回值: 成功返回TRUE , 失敗返回FALSE  
//lpFileData: 存放dll文件數據的緩衝區  
//DataLength: 緩衝區中數據的總長度  
BOOL CMemLoadDll::MemLoadLibrary(void *lpFileData, int DataLength)  
{  
    if (pImageBase != NULL)  
    {  
        return FALSE;  //已經載入一個dll,還沒有釋放,不能載入新的dll  
    }  
  
    //檢查數據有效性,並初始化  
    if (!CheckDataValide(lpFileData, DataLength))  
    {  
        return FALSE;  
    }  
  
    //計算所需的載入空間  
    int ImageSize = CalcTotalImageSize();  
    if (ImageSize == 0)  
    {  
        return FALSE;  
    }  
  
    // 分配虛擬記憶體  
    void *pMemoryAddress = VirtualAlloc((LPVOID)NULL, ImageSize,  
                                        MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);  
    if (pMemoryAddress == NULL)  
    {  
        return FALSE;  
    }  
    else  
    {  
        CopyDllDatas(pMemoryAddress, lpFileData); //複製dll數據,並對齊每個段  
  
        //重定位信息  
        if (pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress > 0  
                && pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size > 0)  
        {  
            DoRelocation(pMemoryAddress);  
        }  
  
        //填充引入地址表  
        if (!FillRavAddress(pMemoryAddress))  //修正引入地址表失敗  
        {  
            VirtualFree(pMemoryAddress, 0, MEM_RELEASE);  
            return FALSE;  
        }  
  
        //修改頁屬性。應該根據每個頁的屬性單獨設置其對應記憶體頁的屬性。這裡簡化一下。  
        //統一設置成一個屬性PAGE_EXECUTE_READWRITE  
        unsigned long old;  
        VirtualProtect(pMemoryAddress, ImageSize, PAGE_EXECUTE_READWRITE, &old);  
    }  
  
    //修正基地址  
    pNTHeader->OptionalHeader.ImageBase = (DWORD)pMemoryAddress;  
  
    //接下來要調用一下dll的入口函數,做初始化工作。  
    pDllMain = (ProcDllMain)(pNTHeader->OptionalHeader.AddressOfEntryPoint + (DWORD) pMemoryAddress);  
  
    BOOL InitResult = pDllMain((HINSTANCE)pMemoryAddress, DLL_PROCESS_ATTACH, 0);  
    if (!InitResult)  //初始化失敗  
    {  
        pDllMain((HINSTANCE)pMemoryAddress, DLL_PROCESS_DETACH, 0);  
        VirtualFree(pMemoryAddress, 0, MEM_RELEASE);  
        pDllMain = NULL;  
        return FALSE;  
    }  
  
    isLoadOk = TRUE;  
    pImageBase = (DWORD)pMemoryAddress;  
    return TRUE;  
}  
  
//MemGetProcAddress函數從dll中獲取指定函數的地址  
//返回值: 成功返回函數地址 , 失敗返回NULL  
//lpProcName: 要查找函數的名字或者序號  
FARPROC  CMemLoadDll::MemGetProcAddress(LPCSTR lpProcName)  
{  
    if (pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress == 0 ||  
            pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size == 0)  
    {  
        return NULL;  
    }  
  
    if (!isLoadOk)  
    {  
        return NULL;  
    }  
  
    DWORD OffsetStart = pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;  
    DWORD Size = pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;  
  
    PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)((DWORD)pImageBase + pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);  
    int iBase = pExport->Base;  
    int iNumberOfFunctions = pExport->NumberOfFunctions;  
    int iNumberOfNames = pExport->NumberOfNames; //<= iNumberOfFunctions  
    LPDWORD pAddressOfFunctions = (LPDWORD)(pExport->AddressOfFunctions + pImageBase);  
    LPWORD  pAddressOfOrdinals = (LPWORD)(pExport->AddressOfNameOrdinals + pImageBase);  
    LPDWORD pAddressOfNames  = (LPDWORD)(pExport->AddressOfNames + pImageBase);  
  
    int iOrdinal = -1;  
  
    if (((DWORD)lpProcName & 0xFFFF0000) == 0)  //IT IS A ORDINAL!  
    {  
        iOrdinal = (DWORD)lpProcName & 0x0000FFFF - iBase;  
    }  
    else     //use name  
    {  
        int iFound = -1;  
  
        for (int i = 0; i < iNumberOfNames; i++)  
        {  
            char *pName = (char * )(pAddressOfNames[i] + pImageBase);  
            if (strcmp(pName, lpProcName) == 0)  
            {  
                iFound = i;  
                break;  
            }  
        }  
        if (iFound >= 0)  
        {  
            iOrdinal = (int)(pAddressOfOrdinals[iFound]);  
        }  
    }  
  
    if (iOrdinal < 0 || iOrdinal >= iNumberOfFunctions )  
    {  
        return NULL;  
    }  
    else  
    {  
        DWORD pFunctionOffset = pAddressOfFunctions[iOrdinal];  
        if (pFunctionOffset > OffsetStart && pFunctionOffset < (OffsetStart + Size)) //maybe Export Forwarding  
        {  
            return NULL;  
        }  
        else  
        {  
            return (FARPROC)(pFunctionOffset + pImageBase);  
        }  
    }  
  
}  
  
// 重定向PE用到的地址  
void CMemLoadDll::DoRelocation( void *NewBase)  
{  
    /* 重定位表的結構: 
    // DWORD sectionAddress, DWORD size (包括本節需要重定位的數據) 
    // 例如 1000節需要修正5個重定位數據的話,重定位表的數據是 
    // 00 10 00 00   14 00 00 00      xxxx xxxx xxxx xxxx xxxx 0000 
    // -----------   -----------      ---- 
    // 給出節的偏移  總尺寸=8+6*2     需要修正的地址           用於對齊4位元組 
    // 重定位表是若幹個相連,如果address 和 size都是0 表示結束 
    // 需要修正的地址是12位的,高4位是形態字,intel cpu下是3 
    */  
    //假設NewBase是0x600000,而文件中設置的預設ImageBase是0x400000,則修正偏移量就是0x200000  
    DWORD Delta = (DWORD)NewBase - pNTHeader->OptionalHeader.ImageBase;  
  
    //註意重定位表的位置可能和硬碟文件中的偏移地址不同,應該使用載入後的地址  
    PIMAGE_BASE_RELOCATION pLoc = (PIMAGE_BASE_RELOCATION)((unsigned long)NewBase  
                                  + pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);  
    while ((pLoc->VirtualAddress + pLoc->SizeOfBlock) != 0)  //開始掃描重定位表  
    {  
        WORD *pLocData = (WORD *)((int)pLoc + sizeof(IMAGE_BASE_RELOCATION));  
        //計算本節需要修正的重定位項(地址)的數目  
        int NumberOfReloc = (pLoc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);  
        for ( int i = 0 ; i < NumberOfReloc; i++)  
        {  
            if ( (DWORD)(pLocData[i] & 0xF000) == 0x00003000)  //這是一個需要修正的地址  
            {  
                // 舉例:  
                // pLoc->VirtualAddress = 0x1000;  
                // pLocData[i] = 0x313E; 表示本節偏移地址0x13E處需要修正  
                // 因此 pAddress = 基地址 + 0x113E  
                // 裡面的內容是 A1 ( 0c d4 02 10)  彙編代碼是: mov eax , [1002d40c]  
                // 需要修正1002d40c這個地址  
                DWORD *pAddress = (DWORD *)((unsigned long)NewBase + pLoc->VirtualAddress + (pLocData[i] & 0x0FFF));  
                *pAddress += Delta;  
            }  
        }  
        //轉移到下一個節進行處理  
        pLoc = (PIMAGE_BASE_RELOCATION)((DWORD)pLoc + pLoc->SizeOfBlock);  
    }  
}  
  
//填充引入地址表  
BOOL CMemLoadDll::FillRavAddress(void *pImageBase)  
{  
    // 引入表實際上是一個 IMAGE_IMPORT_DESCRIPTOR 結構數組,全部是0表示結束  
    // 數組定義如下:  
    //  
    // DWORD   OriginalFirstThunk;         // 0表示結束,否則指向未綁定的IAT結構數組  
    // DWORD   TimeDateStamp;  
    // DWORD   ForwarderChain;             // -1 if no forwarders  
    // DWORD   Name;                       // 給出dll的名字  
    // DWORD   FirstThunk;                 // 指向IAT結構數組的地址(綁定後,這些IAT裡面就是實際的函數地址)  
  
    int i;  
  
    unsigned long Offset = pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress ;  
    if (Offset == 0)  
    {  
        return TRUE;    //No Import Table  
    }  
    PIMAGE_IMPORT_DESCRIPTOR pID = (PIMAGE_IMPORT_DESCRIPTOR)((unsigned long) pImageBase + Offset);  
    while (pID->Characteristics != 0 )  
    {  
        PIMAGE_THUNK_DATA pRealIAT = (PIMAGE_THUNK_DATA)((unsigned long)pImageBase + pID->FirstThunk);  
        PIMAGE_THUNK_DATA pOriginalIAT = (PIMAGE_THUNK_DATA)((unsigned long)pImageBase + pID->OriginalFirstThunk);  
        //獲取dll的名字  
        WCHAR buf[256]; //dll name;  
        BYTE *pName = (BYTE *)((unsigned long)pImageBase + pID->Name);  
        for (i = 0; i < 256; i++)  
        {  
            if (pName[i] == 0)  
            {  
                break;  
            }  
            buf[i] = pName[i];  
        }  
        if (i >= 256)  
        {  
            return FALSE;    // bad dll name  
        }  
        else  
        {  
            buf[i] = 0;  
        }  
        HMODULE hDll = GetModuleHandle(buf);  
        if (hDll == NULL)  
        {  
            hDll = LoadLibrary(buf);  
        }  
        if (hDll == NULL)  
        {  
            return FALSE;    //NOT FOUND DLL  
        }  
        //獲取DLL中每個導出函數的地址,填入IAT  
        //每個IAT結構是 :  
        // union { PBYTE  ForwarderString;  
        //   PDWORD Function;  
        //   DWORD Ordinal;  
        //   PIMAGE_IMPORT_BY_NAME  AddressOfData;  
        // } u1;  
        // 長度是一個DWORD ,正好容納一個地址。  
        for (i = 0; ; i++)  
        {  
            if (pOriginalIAT[i].u1.Function == 0)  
            {  
                break;  
            }  
            FARPROC lpFunction = NULL;  
            if (pOriginalIAT[i].u1.Ordinal & IMAGE_ORDINAL_FLAG)  //這裡的值給出的是導出序號  
            {  
                lpFunction = GetProcAddress(hDll, (LPCSTR)(pOriginalIAT[i].u1.Ordinal & 0x0000FFFF));  
            }  
            else     //按照名字導入  
            {  
                //獲取此IAT項所描述的函數名稱  
                PIMAGE_IMPORT_BY_NAME pByName = (PIMAGE_IMPORT_BY_NAME)  
                                                ((DWORD)pImageBase + (DWORD)(pOriginalIAT[i].u1.AddressOfData));  
                //    if(pByName->Hint !=0)  
                //     lpFunction = GetProcAddress(hDll, (LPCSTR)pByName->Hint);  
                //    else  
                lpFunction = GetProcAddress(hDll, (char *)pByName->Name);  
            }  
  
            if (lpFunction != NULL)  //找到了!  
            {  
                pRealIAT[i].u1.Function = (DWORD)lpFunction;//(PDWORD) lpFunction;  
            }  
            else  
            {  
                return FALSE;  
            }  
        }  
  
        //move to next  
        pID = (PIMAGE_IMPORT_DESCRIPTOR)( (DWORD)pID + sizeof(IMAGE_IMPORT_DESCRIPTOR));  
    }  
  
    return TRUE;  
}  
  
//CheckDataValide函數用於檢查緩衝區中的數據是否有效的dll文件  
//返回值: 是一個可執行的dll則返回TRUE,否則返回FALSE。  
//lpFileData: 存放dll數據的記憶體緩衝區  
//DataLength: dll文件的長度  
BOOL CMemLoadDll::CheckDataValide(void *lpFileData, int DataLength)  
{  
    //檢查長度  
    if (DataLength < sizeof(IMAGE_DOS_HEADER))  
    {  
        return FALSE;  
    }  
    pDosHeader = (PIMAGE_DOS_HEADER)lpFileData;  // DOS頭  
    //檢查dos頭的標記  
    if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)  
    {  
        return FALSE;    //0x5A4D : MZ  
    }  
  
    //檢查長度  
    if ((DWORD)DataLength < (pDosHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS)) )  
    {  
        return FALSE;  
    }  
    //取得pe頭  
    pNTHeader = (PIMAGE_NT_HEADERS)( (unsigned long)lpFileData + pDosHeader->e_lfanew); // PE頭  
    //檢查pe頭的合法性  
    if (pNTHeader->Signature != IMAGE_NT_SIGNATURE)  
    {  
        return FALSE;    //0x00004550 : PE00  
    }  
    if ((pNTHeader->FileHeader.Characteristics & IMAGE_FILE_DLL) == 0) //0x2000  : File is a DLL  
    {  
        return FALSE;  
    }  
    if ((pNTHeader->FileHeader.Characteristics & IMAGE_FILE_EXECUTABLE_IMAGE) == 0) //0x0002 : 指出文件可以運行  
    {  
        return FALSE;  
    }  
    if (pNTHeader->FileHeader.SizeOfOptionalHeader != sizeof(IMAGE_OPTIONAL_HEADER))  
    {  
        return FALSE;  
    }  
  
    //取得節表(段表)  
    pSectionHeader = (PIMAGE_SECTION_HEADER)((int)pNTHeader + sizeof(IMAGE_NT_HEADERS));  
    //驗證每個節表的空間  
    for (int i = 0; i < pNTHeader->FileHeader.NumberOfSections; i++)  
    {  
        if ((pSectionHeader[i].PointerToRawData + pSectionHeader[i].SizeOfRawData) > (DWORD)DataLength)  
        {  
            return FALSE;  
        }  
    }  
    return TRUE;  
}  
  
//計算對齊邊界  
int CMemLoadDll::GetAlignedSize(int Origin, int Alignment)  
{  
    return (Origin + Alignment - 1) / Alignment * Alignment;  
}  
  
//計算整個dll映像文件的尺寸  
int CMemLoadDll::CalcTotalImageSize()  
{  
    int Size;  
    if (pNTHeader == NULL)  
    {  
        return 0;  
    }  
    int nAlign = pNTHeader->OptionalHeader.SectionAlignment; //段對齊位元組數  
  
    // 計算所有頭的尺寸。包括dos, coff, pe頭 和 段表的大小  
    Size = GetAlignedSize(pNTHeader->OptionalHeader.SizeOfHeaders, nAlign);  
    // 計算所有節的大小  
    for (int i = 0; i < pNTHeader->FileHeader.NumberOfSections; ++i)  
    {  
        //得到該節的大小  
        int CodeSize = pSectionHeader[i].Misc.VirtualSize ;  
        int LoadSize = pSectionHeader[i].SizeOfRawData;  
        int MaxSize = (LoadSize > CodeSize) ? (LoadSize) : (CodeSize);  
  
        int SectionSize = GetAlignedSize(pSectionHeader[i].VirtualAddress + MaxSize, nAlign);  
        if (Size < SectionSize)  
        {  
            Size = SectionSize;    //Use the Max;  
        }  
    }  
    return Size;  
}  
//CopyDllDatas函數將dll數據複製到指定記憶體區域,並對齊所有節  
//pSrc: 存放dll數據的原始緩衝區  
//pDest:目標記憶體地址  
void CMemLoadDll::CopyDllDatas(void *pDest, void *pSrc)  
{  
    // 計算需要複製的PE頭+段表位元組數  
    int  HeaderSize = pNTHeader->OptionalHeader.SizeOfHeaders;  
    int  SectionSize = pNTHeader->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER);  
    int  MoveSize = HeaderSize + SectionSize;  
    //複製頭和段信息  
    memmove(pDest, pSrc, MoveSize);  
  
    //複製每個節  
    for (int i = 0; i < pNTHeader->FileHeader.NumberOfSections; ++i)  
    {  
        if (pSectionHeader[i].VirtualAddress == 0 || pSectionHeader[i].SizeOfRawData == 0)  
        {  
            continue;  
        }  
        // 定位該節在記憶體中的位置  
        void *pSectionAddress = (void *)((unsigned long)pDest + pSectionHeader[i].VirtualAddress);  
        // 複製段數據到虛擬記憶體  
        memmove((void *)pSectionAddress,  
                (void *)((DWORD)pSrc + pSectionHeader[i].PointerToRawData),  
                pSectionHeader[i].SizeOfRawData);  
    }  
  
    //修正指針,指向新分配的記憶體  
    //新的dos頭  
    pDosHeader = (PIMAGE_DOS_HEADER)pDest;  
    //新的pe頭地址  
    pNTHeader = (PIMAGE_NT_HEADERS)((int)pDest + (pDosHeader->e_lfanew));  
    //新的節表地址  
    pSectionHeader = (PIMAGE_SECTION_HEADER)((int)pNTHeader + sizeof(IMAGE_NT_HEADERS));  
    return ;  
}  
#endif  

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在“求佛保佑伺服器不宕機”、“殺程式員祭天”的環境下,程式員每天可謂是戰戰兢兢,接到電話和簡訊都嚇得瑟瑟發抖,為了我們的安全,及時發現伺服器運行問題已不僅僅是運維的問題了。本文總結了常見的伺服器監控指標,希望各位開發人員都搞一個腳本運行著以保障自己的生命安全。 ...
  • 本系列文章主要介紹linux下主流的開源郵件系統postfix的搭建過程,構建一個通過postfix虛擬用戶管理的完整的郵件系統, 該系統包括以下組件: 郵件收髮端postfix,dovecot, 郵件管理端:extmail,extman 安全認證:cyrus-sasl 防病毒,防垃圾 本文主要介紹 ...
  • Expect是在Tcl基礎上創建起來的,它還提供了一些Tcl所沒有的命令,它可以用來做一些linux下無法做到交互的一些命令操作,在遠程管 理方面發揮很大的作用。 spawn命令激活一個Unix程式來進行互動式的運行。 send命令向進程發送字元串。 expect 命令等待進程的某些字元串。 exp ...
  • 本文目錄:1. nginx簡介2. nginx處理請求的過程簡單說明3. nginx命令4. nginx模塊及http功能速覽5. nginx配置文件簡單說明 5.1 main和events段 5.2 http段 5.2.1 配置文件概覽 5.2.2 root指令和alias指令 5.2.3 loc ...
  • 1》概述 作為一名運維人員,保證數據的安全是根本職責,所以在維護系統的時候,要慎重和細心,但是有時也難免發生出現數據被誤刪除的情況,這個時候該如何 快速、有效地恢複數據呢? 1>如何使用rm –rf命令 在Linux系統下,通過 rm –rf 可以將任何數據直接從硬碟刪除,並且沒有任何提示,同時Li ...
  • ...
  • vs2015卸載後註冊表還會存在vs2015的信息,下次安裝的時候會讀註冊表裡面記錄的路徑,不能自己選擇路徑。 解決方法: 1.在vs安裝文件的路徑打開命令,shift+滑鼠右鍵 2.輸入命令:cn_visual_studio_enterprise.exe/U /Force 3.經過第2步,程式會把 ...
  • 做慣了後臺的沐雨一向覺得數據列表是一個系統裡面最重要的東西 所以熟悉ext也就從表格開始入手了 想看官方代碼的同學進這裡 官方預設表格教程 或是直接看我的也行,直接拷貝過來的 另外官方文檔沒有中文版的讓我這個二級都沒過的學渣倍感壓力啊 1. Views部分代碼 官方文檔一直沒看到下圖Layout的代 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...