本文是讀偉民哥翻譯的 .NET記憶體管理寶典 這本書的筆記,我認為讀書的程序也需要實踐,這樣對一知半解的知識也有較為清晰的了解,在閱讀到 string 在記憶體的布局時,我看到 RuntimeHelpers 的 OffsetToStringData 資料,據說此屬性可以獲取到字串的字符在記憶體存放的實際地址,本文將來寫一個混合 C# 和 C++\CLI 的應用來進行測驗
本文將完全采用 .NET 6 進行撰寫,分別創建 .NET 6 的 C# 控制臺程式,和 .NET 6 的 C++\CLI 空專案,這里需要稍微說明的是 C++\CLI 是通過 C++ 撰寫的 .NET 應用程式,基于 .NET 運行時運行的程式
在 C++\CLI 專案里面添加一個叫 Foo 的類,在類里面添加一個方法,用來輸出字串的內容
namespace JuyurchelhiLewecujai
{
public ref class Foo
{
public:
static void Output(System::String^ input);
};
}
以上代碼放在 Foo.h 檔案里面,接下來實作 Output 方法,期望是在此方法里面獲取在 .NET 定義的字串物件的實際存放字符的記憶體指標,實作方法如下
#include "Foo.h"
#include <iostream>
#include "vcclr.h"
void JuyurchelhiLewecujai::Foo::Output(System::String^ input)
{
const pin_ptr<const wchar_t> p = PtrToStringChars(input);
wchar_t const* c = p;
wprintf(L"%s", c);
}
通過 VCClr 提供的 PtrToStringChars 方法可以取出 input 字串里面的實際存放字符的指標,接著采用 pin_ptr 定住此物件,為什么需要采用 pin_ptr 定住?原因是 .NET 世界隨時都會有 GC 將物件的地址變更,因此為了進行安全使用,需要使用 pin_ptr 定住此物件,這樣在 GC 時就不會修改此物件的記憶體地址,細節請參閱 從C++到C++/CLI - feisky - 博客園
另一個細節是咱在 .NET 里面的字串的編碼格式都是 Unicode 也就是 U16 編碼方式,需要對應到 wchar_t 型別,也需要使用 wprintf 輸出而不能使用 printf 輸出,否則將會讀取到 \0 而只輸出第一個字符,當然了,在 C++\CLI 專案里面依然是不推薦使用 iostream 進行輸出的
那以上的 PtrToStringChars 是通過什么魔法進行實作的?可以看到此方法的實作如下
//
// get an interior gc pointer to the first character contained in a System::String object
//
inline __const_Char_ptr PtrToStringChars(__const_String_handle s) {
_Byte_ptr bp = const_cast<_Byte_ptr>(reinterpret_cast<__const_Byte_ptr>(s));
if( bp != _NULLPTR ) {
bp += System::Runtime::CompilerServices::RuntimeHelpers::OffsetToStringData;
}
return reinterpret_cast<__const_Char_ptr>(bp);
}
核心邏輯就是通過 RuntimeHelpers 的 OffsetToStringData 屬性獲取相對于字串型別的地址的實際字符存放地址
嘗試在 C# 專案里面呼叫剛才定義的 Foo 型別的 Output 代碼,方法如下
class Program
{
static void Main(string[] args)
{
JuyurchelhiLewecujai.Foo.Output("Hello");
}
}
運行控制臺專案,可以看到輸出了 Hello 文本,這也就是說字串的記憶體布局里面,存放字符陣列的地方就是在距離字串物件指標的 RuntimeHelpers.OffsetToStringData 的地方
然而在 .NET 5 和以上版本,標記了 OffsetToStringData 方法過時,官方推薦使用 GetPinnableReference 代替,關于 GetPinnableReference 請參閱 C#7.3 新增功能 - 張傳寧 - 博客園
更改 C++\CLI 代碼如下
void JuyurchelhiLewecujai::Foo::Output(System::String^ input)
{
auto pinString = &input->GetPinnableReference();
wprintf(L"%s", pinString);
}
本文所有代碼放在 github 和 gitee 歡迎訪問
可以通過如下方式獲取本文的源代碼,先創建一個空檔案夾,接著使用命令列 cd 命令進入此空檔案夾,在命令列里面輸入以下代碼,即可獲取到本文的代碼
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 414b803c3c4faa93d1075c28c85e5826c611d9cb
以上使用的是 gitee 的源,如果 gitee 不能訪問,請替換為 github 的源
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
獲取代碼之后,進入 CemholerecelQerrairdoway 檔案夾
更多記憶體相關,我推薦偉明的 《.NET記憶體管理寶典 - 提高代碼質量、性能和可擴展性》 這本書
博客園博客只做備份,博客發布就不再更新,如果想看最新博客,請到 https://blog.lindexi.com/

本作品采用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可,歡迎轉載、使用、重新發布,但務必保留文章署名[林德熙](http://blog.csdn.net/lindexi_gd)(包含鏈接:http://blog.csdn.net/lindexi_gd ),不得用于商業目的,基于本文修改后的作品務必以相同的許可發布,如有任何疑問,請與我[聯系](mailto:[email protected]),
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/508793.html
標籤:其他
下一篇:請教一個生產演算法
