[轉]Passing Managed Structures With Strings To Unmanaged Code Part 2

来源:http://www.cnblogs.com/czytcn/archive/2017/11/30/7927977.html
-Advertisement-
Play Games

1. Introduction.1.1 In part 1 of this series of blogs we studied how to pass a managed structure (which contains strings) to unmanaged code. The struc... ...


1. Introduction.

1.1 In part 1 of this series of blogs we studied how to pass a managed structure (which contains strings) to unmanaged code. The structure was passed as an “in” (by-value) parameter, i.e. the structure was passed to the unmanaged code as a read-only parameter.

1.2 Here in part 2, we shall explore the techniques for receiving such a structure from unmanaged code as an “out” parameter. I have previously written about marshaling managed structures to and from unmanaged code. This series of blogs aim to study how to marshal structures which specifically contain strings.

2. Sample Structure, Unmanaged String Representations and Marshaling Direction.

2.1 We shall continue to use the test structures that we have bult up from part 1.

2.2 Although the direction of parameter passing is now reversed (i.e. from unmanaged to managed code), the ways that a string structure member is represented in unmanaged code remain the same :

  • As a fixed length inline NULL-terminated character array (aka C-style string).
  • As a pointer to a NULL-terminated character array (better known as LPCSTR in C/C++ lingo).
  • As a BSTR.

2.3 Throughout the various sections below, attention will be given to the effects of marshaling an “out” parameter : that any initially set string values will not be received by the called API. The marshaling direction is from the unmanaged side to the managed side. Hence only the string allocated on the unmanaged side and returned “outwardly” to the managed side will be marshaled.

3. Receiving a Structure with a Fixed-Length Inline String.

3.1 For the demonstrative codes presented in this section, we shall use the C# structure TestStruct01 :

[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct TestStruct01
{
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 21)]
  public string m_strString;
};

3.2 The C++ equivalent is as usual :

#pragma pack (1)

struct TestStruct01
{
  char m_szString[21];
};

3.3 An example C++-based unmanaged API that takes such a structure as an “out” parameter is listed below :

void __stdcall SetValueTestStruct01(/*[out]*/ TestStruct01* ptest_struct_01)
{
  strcpy (ptest_struct_01 -> m_szString, "From unmanaged code.");
}

3.4 The C# declaration for such an API is listed below :

[DllImport("TestDLL01.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void SetValueTestStruct01([Out] out TestStruct01 test_struct_01);

3.5 And a sample C# code for invoking the API :

static void SetValueTestStruct01()
{
  TestStruct01 test_struct_01;

  SetValueTestStruct01(out test_struct_01);

  DisplayTestStruct01(test_struct_01);
}

Notice that the “test_struct_01” TestStruct01 structure was instantiated but not initialized before the call to SetValueTestStruct01(). This is acceptable to the compiler because “test_struct_01” is used as an “out” parameter which means that its value is to be set by the SetValueTestStruct01() API.

We then used DisplayTestStruct01() (an API we have already met in part 1) to display the value set for “test_struct_01.m_strString”.

3.6 Under the covers, the following is how the interop marshaler performs the marshaling process :

  • When the SetValueTestStruct01() API was called, the interop marshaler will allocate a buffer with space large enough to hold the structure which is listed in point 3.2.
  • Note that this temporary buffer need not always be freshly allocated using Marshal.AllocHGlobal() or Marshal.AllocCoTaskMem(). This buffer may be from a cached and re-usable memory area managed by the CLR.
  • Wherever it originates, this memory buffer serves to store a temporary unmanaged version of the “TestStruct01” structure.
  • The interop marshaler will then pass a pointer to this structure to the SetValueTestStruct01() API.
  • The API will assign value to the “m_szString” field of the “TestStruct01” struct pointed to by the input “ptest_struct_01” parameter.
  • When the API returns, the interop marshaler will assign the string value in the unmanaged “m_szString” field to the counterpart “m_strString” field of the managed “test_struct_01” structure.
  • The managed “test_struct_01” structure will now be completely set.
  • At this time, the interop marshaler is free to re-use (for other purposes) the original buffer space which was used to hold the temporary structure.

3.7 The DisplayTestStruct01() API will display the following expected output on the console :

test_struct_01.m_szString : [From unmanaged code.].

3.8 This is hence how we receive a structure (with a string field typed as an inline embedded character array) from unmanaged code. It is quite simple since most of the marshaling work is done by the interop marshaler.

3.9 It is the interop marshaler that allocates memory space for the unmanaged structure. This space includes the inline embedded character array field. The unmanaged API assigns character values into this character array field and the interop marshaler transcribes this value into the managed structure. The interop marshaler then either destroys the unmanaged structure or re-uses the memory occuppied by it.

3.10 Now, what if inside SetValueTestStruct01(), we had set an initialized value to test_struct_01.m_strString before passing test_struct_01 to SetValueTestStruct01() ? Listed below is a function which performs this :

// SetValueTestStruct01_WithInitialization() will
// call SetValueTestStruct01() but with parameter
// initialized.
static void SetValueTestStruct01_WithInitialization()
{
  TestStruct01 test_struct_01 = new TestStruct01();

  test_struct_01.m_strString = "Initial string.";

  // When SetValueTestStruct01() is called, the interop
  // marshaler will not copy the current value of
  // test_struct_01.m_strString to the unmanaged
  // counterpart field.
  SetValueTestStruct01(out test_struct_01);
  // When SetValueTestStruct01() completes, the
  // string value in the unmanaged m_szString
  // field will be used to overwrite the
  // value in the managed m_strString field.

  DisplayTestStruct01(test_struct_01);
}

3.11 What will happen is that when the SetValueTestStruct01() API is called, the interop marshaler will not copy the current value of test_struct_01.m_strString to the m_szString field of the unmanaged TestStruct01 structure. Hence inside SetValueTestStruct01(), ptest_struct_01 -> m_szString will contain all NULL characters. This is due to the fact that the parameter is an “out” parameter and so no marshaling of the fields of the managed struct will be done towards the direction of the API call.

3.12 Then when SetValueTestStruct01() returns, the string value in the unmanaged m_szString field will be used to overwrite the value in the managed m_strString field.

4. Receiving the Structure with a Pointer to a NULL-terminated Character Array.

4.1 In this section, we use the TestStruct02 structure that we first defined in part 1 :

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct TestStruct02
{
  [MarshalAs(UnmanagedType.LPStr)]
  public string m_strString;
};

4.2 The corresponding C++ equivalent of such a structure is :

#pragma pack(1)

struct TestStruct02
{
  LPCSTR m_pszString;
};

4.3 An example C++-based unmanaged API that takes such a structure as an “out” parameter is listed below :

void __stdcall SetValueTestStruct02(/*[out]*/ TestStruct02* ptest_struct_02)
{
  char szTemp[] = "From unmanaged code.";
  ptest_struct_02 -> m_pszString = (LPSTR)::CoTaskMemAlloc(sizeof(szTemp));
  strcpy ((LPSTR)(ptest_struct_02 -> m_pszString), szTemp);
}

4.4 The C# declaration for such an API is listed below :

[DllImport("TestDLL01.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void SetValueTestStruct02([Out] out TestStruct02 test_struct_02);

4.5 And a sample C# code for invoking the API :

static void SetValueTestStruct02()
{
  TestStruct02 test_struct_02;

  SetValueTestStruct02(out test_struct_02);

  DisplayTestStruct02(test_struct_02);
}

Just like the example we met in section 3, the “test_struct_02” TestStruct02 structure was instantiated but not initialized before the call to SetValueTestStruct02(). This is acceptable to the compiler because “test_struct_02” is used as an “out” parameter which means that its value is to be set by the SetValueTestStruct02() API.

We then used DisplayTestStruct02() (an API we have already met in part 1) to display the value set for “test_struct_02.m_strString”.

4.6 Under the covers, the following are the processes that take place throughout the SetValueTestStruct02() API call :

  • When the SetValueTestStruct02() API was called, the interop marshaler will allocate a buffer with space large enough to hold the unmanaged “TestStruct02” structure which is listed in point 4.2.
  • Just like the example we studied in section 3, this temporary buffer need not always be freshly allocated using Marshal.AllocHGlobal() or Marshal.AllocCoTaskMem(). This buffer may be from a cached and re-usable memory area managed by the CLR.
  • Wherever it originates, this memory buffer serves to store a temporary unmanaged version of the “TestStruct02” structure.
  • At this time, the “m_pszString” member of this “TestStruct02” structure will be set to all zeroes.
  • As SetValueTestStruct02() executes, it will use the ::CoTaskMemAlloc() API to allocate an ANSI string buffer which can hold the string “From unmanaged code.”. The structure’s m_pszString member is then made to point to this buffer.
  • Then, when SetValueTestStruct02() returns, the string buffer pointed to by m_pszString is used to create a managed string that holds the same characters in Unicode. The string buffer pointed to by m_pszString is then freed using Marshal.FreeCoTaskMem().

4.7 The DisplayTestStruct02() API will display the following expected output on the console screen :

test_struct_02.m_pszString : [From unmanaged code.].

4.8 This is hence how we receive a structure (with a string field typed as a pointer to a NULL-terminated character array) from unmanaged code. This time, the unmanaged code shares some responsibility in allocating memory for the unmanaged string field.

4.9 Note that the memory allocation must be performed using ::CoTaskMemAlloc(). This is the protocol. The interop marshaler will then use Marshal.FreeCoTaskMem() to free this memory after using the contained character data to create the corresponding managed string field.

4.10 Now, what happens if before being passed to the SetValueTestStruct02() API, test_struct_02.m_strString is set to some string value (like the code shown below) ?

// SetValueTestStruct02_WithInitialization() will
// call SetValueTestStruct01() but with parameter
// initialized.
static void SetValueTestStruct02_WithInitialization()
{
  TestStruct02 test_struct_02 = new TestStruct02();

  test_struct_02.m_strString = "Initial string.";

  // When SetValueTestStruct02() is called, the interop
  // marshaler will not allocate any character buffer
  // in memory. The m_pszString field of the unmanaged
  // representation of the TestStruct02 struct will
  // be NULL.
  SetValueTestStruct02(out test_struct_02);
  // When SetValueTestStruct02() returns, the
  // string pointed to by the unmanaged m_pszString
  // field will be used to overwrite the
  // value in the managed m_strString field.

  DisplayTestStruct02(test_struct_02);
}

4.11 What will happen is that when SetValueTestStruct02() is called, the interop marshaler will not allocate any character buffer in memory to contain the ANSI string “Initial string.” . The m_pszString field of the unmanaged representation of the TestStruct02 struct will be NULL.This is what SetValueTestStruct02() will see when it executes. This is because the parameter is an “out” parameter and so no marshaling of the fields of the managed struct will be done towards the direction of the API call.

4.12 Then when SetValueTestStruct02() returns, the string pointed to by the unmanaged m_pszString field will be used to overwrite the value in the managed m_strString field.

5. Receiving a Structure with a BSTR.

5.1 For this section, we use the TestStruct03 structure that we first defined in part 1 :

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct TestStruct03
{
  [MarshalAs(UnmanagedType.BStr)]
  public string m_strString;
};

5.2 The corresponding C++ equivalent of such a structure is :

#pragma pack(1)

struct TestStruct03
{
  BSTR m_bstr;
};

5.3 An example C++-based unmanaged API that takes such a structure as an “out” parameter is listed below :

void __stdcall SetValueTestStruct03(/*[out]*/ TestStruct03* ptest_struct_03)
{
  ptest_struct_03 -> m_bstr = ::SysAllocString(L"BSTR from unmanaged code.");
}

5.4 The C# declaration for such an API is listed below :

[DllImport("TestDLL01.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void SetValueTestStruct03([Out] out TestStruct03 test_struct_03);

5.5 And a sample C# code for invoking the API :

static void SetValueTestStruct03()
{
  TestStruct03 test_struct_03;

  SetValueTestStruct03(out test_struct_03);

  DisplayTestStruct03(test_struct_03);
}

Similar to the prevous examples that we have seen earlier, the TestStruct03 structure “test_struct_03” was instantiated but not initialized before the call to SetValueTestStruct03(). This is acceptable to the compiler because “test_struct_03” is used as an “out” parameter which means that its value is to be set by the SetValueTestStruct03() API.

We then used DisplayTestStruct03() (an API we have already met in part 1) to display the value set for “test_struct_03.m_strString”.

5.6 Under the covers, the following are the processes that take place throughout the SetValueTestStruct02() API call :

  • When the SetValueTestStruct03() API was called, the interop marshaler will allocate a buffer with space large enough to hold the unmanaged “TestStruct03” structure which is listed in point 5.2.
  • Just like the previous example we have studied, this temporary buffer need not always be freshly allocated using Marshal.AllocHGlobal() or Marshal.AllocCoTaskMem(). This buffer may be from a cached and re-usable memory area managed by the CLR.
  • Wherever it originates, this memory buffer serves to store a temporary unmanaged version of the “TestStruct03” structure.
  • At this time, the “m_bstr” member of this “TestStruct03” structure will be set to all zeroes.
  • As SetValueTestStruct03() executes, it will use the ::SysAllocString() API to allocate a BSTR which can hold the Unicode string “BSTR from unmanaged code.”. The structure’s “m_bstr” member is then made to point to this buffer.
  • Then, when SetValueTestStruct03() returns, the BSTR pointed to by “m_bstr” is used to create a managed string that holds the same characters. The BSTR pointed to by “m_bstr” is then freed using Marshal.FreeBSTR() (which eventually calls ::SysFreeString()).

5.7 The DisplayTestStruct03() API will display the following expected output on the console screen :

test_struct_03.m_bstr : [BSTR from unmanaged code.].

5.8 We have thus examined how we receive from unmanaged code a structure with a string field typed as a pointer to a BSTR. Just like the example we saw in section 4, the unmanaged code shares some responsibility in allocating memory for the BSTR.

5.9 The protocol for this is very important : the BSTR allocation must be performed using ::SysAllocString(). The interop marshaler will then use Marshal.FreeBSTR() to free the BSTR after using it to create the corresponding managed string field.

5.10 Again we ask the same question : what if, prior to being passed to the SetValueTestStruct03() API, test_struct_03.m_strString was set to some initialization string (like the code below) ?

// SetValueTestStruct03_WithInitialization() will
// call SetValueTestStruct03() but with parameter
// initialized.
static void SetValueTestStruct03_WithInitialization()
{
  TestStruct03 test_struct_03 = new TestStruct03();

  test_struct_03.m_strString = "Initial string.";

  // When SetValueTestStruct03() is called, the interop
  // marshaler will not allocate any BSTR for "Initial string.".
  // The m_bstr field of the unmanaged
  // representation of the TestStruct03 struct will
  // be NULL.
  SetValueTestStruct03(out test_struct_03);
  // When SetValueTestStruct03() returns, the
  // BSTR pointed to by the unmanaged m_bstr
  // field will be used to overwrite the
  // value in the managed m_strString field.

  DisplayTestStruct03(test_struct_03);
}

5.11 What will happen is that when SetValueTestStruct03() is called, the interop marshaler will not allocate any BSTR for “Initial string.”. The m_bstr field of the unmanaged representation of the TestStruct03 struct will be set to NULL. Once again, this is all because the parameter is an “out” parameter and so no marshaling of the fields of the managed struct will be done towards the direction of the API call.

5.12 Then, when SetValueTestStruct03() returns, the BSTR pointed to by the unmanaged m_bstr field will be used to overwrite the value in the managed m_strString field.

6. Special Handling for a Structure Passed by Pointer.

6.1 If the arrangement is such that a managed structure (which contains a string field) is to be passed as a pointer to an unmanaged API, and then get its string field assigned by the unmanaged API, special processing will need to be done.

6.2 As usual an unmanaged representation of the structure must be constructed somewhere in memory. And then a pointer to this unmanaged structure is to be passed to the API.

6.3 Furthermore, because the spirit and intent of what we are accomplishing now is to have the structure returned as an “out” parameter, the structure must be initialized (to null values) properly before being passed to the unmanaged API. This point will be emphasized in the example codes which will be presented later on.

6.4 Let’s revisit the SetValueTestStruct01() API :

void __stdcall SetValueTestStruct01(/*[out]*/ TestStruct01* ptest_struct_01)
{
  strcpy (ptest_struct_01 -> m_szString, "From unmanaged code.");
}

6.5 The original C# declaration for SetValueTestStruct01() is as follows (as per point 3.4) :

[DllImport("TestDLL01.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void SetValueTestStruct01([Out] out TestStruct01 test_struct_01);

6.6 Now, because the unmanaged API really does take a pointer to an unmanaged TestStruct01 structure, we can import the same API using a different declaration :

[DllImport("TestDLL01.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "SetValueTestStruct01")]
public static extern void SetValueTestStruct01_ByPointer(IntPtr ptest_struct_01);

6.7 This time, instead of passing in a managed TestStruct01 structure to the API, we need to pass an IntPtr which points to an unmanaged representation of a TestStruct01 structure. The following is a sample code :

static void SetValueTestStruct01_ByPointer()
{
  TestStruct01 test_struct_01 = new TestStruct01();

  // Determine the size of the test_struct_01 for marshaling.
  int iSizeOfTestStruct01 = Marshal.SizeOf(typeof(TestStruct01));
  // Allocate in unmanaged memory a block of memory the size
  // of an unmanaged TestStruct01 structure.
  IntPtr ptest_struct_01 = Marshal.AllocHGlobal(iSizeOfTestStruct01);

  // Define an array of bytes with iSizeOfTestStruct01
  // number of elements.
  byte[] byZeroBytes = new byte[iSizeOfTestStruct01];
  // Set all elements of the array to zero.
  for (int i = 0; i < byZeroBytes.Length; i++)
  {
    byZeroBytes[i] = 0;
  }
  // Transfer the zero bytes from the byte array to
  // the unmanaged memory which now serves as the
  // unmanaged counterpart to test_struct_01.
  // We need to do this because we do not want to
  // pass a structure that contains garbage data.
  Marshal.Copy(byZeroBytes, 0, ptest_struct_01, iSizeOfTestStruct01);

  // Call the API using a pointer to the unmanaged test_struct_01.
  SetValueTestStruct01_ByPointer(ptest_struct_01);
  // We now transfer the contents of the unmanaged TestStruct01
  // (pointed to by ptest_struct_01) to the managed memory
  // of test_struct_01.
  test_struct_01 = (TestStruct01)Marshal.PtrToStructure(ptest_struct_01, typeof(TestStruct01));
  // We must remember to destroy the test_struct_01 structure.
  // Doing this will free any fields which are references
  // to memory (e.g. m_strString).
  Marshal.DestroyStructure(ptest_struct_01, typeof(TestStruct01));
  // Finally, the block of memory allocated for the
  // unmanaged test_struct_02 must itself be freed.
  Marshal.FreeHGlobal(ptest_struct_01);
  ptest_struct_01 = IntPtr.Zero;

  DisplayTestStruct01(test_struct_01);
}

6.8 Basically, the marshaling must be done completely manually. The following are pertinent points concerning the code above :

  • A managed TestStruct01 structure is to be allocated. Note that we will be calling an API that will set the “m_strString” field value of this structure and so we need not assign any value to this field.
  • After the structure has been allocated, an unmanaged representation of this structure will need to be allocated and initialized properly.
  • To do this, we need to calculate the size of the unmanaged representation. This is done by using Marshal.SizeOf() which uses the StructLayoutAttribute of the structure and various MarshalAsAttributes assigned to the fields to perform the calculation.
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 射線:從一個點往一個方向,發射一根無限長的射線,這根射線會與場景中帶有 Collider 組件的物體發生碰撞。 射線的使用: 根據上面的代碼: hitInfo.point:表示碰撞點的坐標。 Physics.Raycast():使用這個方法檢測射線時,因為該方法重載很多,一定要弄清楚自己使用的是哪個 ...
  • 實現代碼(C#) 1、發送GET指令 2、接收批量回覆 3、 結果: 代碼重構 1、發送指令 2、接收回覆 3、GET和SET指令 4、重構後的代碼 是不是簡潔很多??? 5、結果 ...
  • 結論: 1、EF 查詢 比ADO慢,甚至直接報告超時錯誤,原因不詳。 2、在原生ADO.Net中 使用 參數化查詢 比 直接使用sql拼接 慢幾十倍!!! ADO.Net代碼測試 EF代碼(已捨棄) 資料庫內部測試 sqlHelper類 博客園非常蛋疼的說:字數少有150字不能發佈的首頁。 答:親, ...
  • using Newtonsoft.Json; using System; using System.Collections.Concurrent; using System.Diagnostics; using System.IO; using System.Threading; using Sys... ...
  • 文檔中包含圖片的話,會使得整個文檔比較大,占用存儲空間且不利於快速、高效的傳輸文件。針對一些包含大量高質圖片的PDF文檔,若是對圖片進行壓縮,可以有效減少文檔的占用空間。並且,在文檔傳輸過程中也可以減少傳送時間,提高效率。本文將介紹2種通過C#來實現PDF圖片文檔壓縮的方法。使用此方法,需要用到最新 ...
  • 相關下載:https://system.data.sqlite.org/index.html/doc/trunk/www/downloads.wiki 在Visual Studio中要支持訪問SQLite文件數據源,首先需要安裝SQLite .NET的相關組件,安裝完SQLite組件後,就可以在Vi ...
  • 首先定義一個泛型委托類型,如下: 實現泛型委托的主體代碼,並調用: 以上代碼展示了委托類型Function<T>主體定義的四種方式,分別是實名委托、匿名委托、Lambda表達式、expression表達式樹。 從Function<T>委托主體的代碼定義來看是越來越簡單和友好,這些變化很大部分應歸功於 ...
  • 轉至:http://www.cnblogs.com/li-peng/p/3154381.html 整理了一下表達式樹的一些東西,入門足夠了 先從ConstantExpression 開始一步一步的來吧 它表示具有常量值的表達式 我們選建一個控制台應用程式 ConstantExpression _co ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...