當前位置: 妍妍網 > 碼農

【C++靜態私有欄位】+【C# .cctor】+【C++ if(條件斷點)】

2024-06-10碼農

點選上方 藍字 江湖評談 設為關註/星標




前言

最近進行托管和非托管頻繁操作,遇到了一些坑記錄下。分別為標題標註的:C++靜態私有欄位存取,C#的全域靜態建構函式.cctor,以及C++ if(延伸的條件斷點)。這其中的兩個C++問題,分別對應C#分析下。且了解下 <Module>.cctor的原理。

C++靜態私有欄位存取

例子:

classAAA{private:static AAA aa;staticint i;char ar;public:static AAA* get3a();staticintgetint();chararr(){return ar; }};//int AAA::i = 10;//AAA AAA::aa;int AAA::getint(){return i;}AAA* AAA::get3a() {return &aa;}intmain(int argc,char** argv){ AAA::getint(); AAA::get3a();}

註意上面程式碼,註釋的兩行。

//int AAA::i = 10;//AAA AAA::aa;

i和aa正是類AAA的私有欄位,如果不對它們進行全域賦值,則VC++編譯器會提示

無法解析的外部符號 "private: static int AAA::i" (?i@AAA@@0HA)無法解析的外部符號 "private: static class AAA AAA::aa" (?aa@AAA@@0V1@A)

然C#對於靜態私有欄位的存取,如下即可,不需要全域設定其值。如果以C#的寫法套用在C++上,這是一個坑,需要註意。不得不說在物件導向方面,C#的爽點還是滿滿的。

public classAAA {privatestatic AAA aa;privatestaticint i;publicstaticintgetint() {return i; }}staticvoidMain(string[] args) { AAA.getint();}

C# <Module>..cctor

<Module>模組是一個虛擬的模組,它一般放在.NET目錄-】數據流-】#~-】表TypeDef表項裏面。這裏的.cctor函式是這個模組的靜態預設建構函式。它執行在托管Main入口之前,執行在System.Private.Corelib.dll之後。如果你想要在托管Main函式之前做一些事情,它是首選。C#程式碼裏面不能夠編輯它, 可以透過Mono.Cecil 對托管DLL添加這個函式。

staticvoid AddModuleCCtor(string ModulePath){ string assemblyPath = ModulePath+ "\\ConsoleApp5.dll"; // 請替換為您的目標程式集路徑 AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(assemblyPath); ModuleDefinition module = assembly.MainModule; TypeDefinition moduleType = module.Types.FirstOrDefault(t => t.Name == "<Module>");if (moduleType == null) { moduleType = new TypeDefinition("""<Module>", TypeAttributes.NotPublic | TypeAttributes.Auto class | TypeAttributes.Ansi class | TypeAttributes.BeforeFieldInit, module.TypeSystem.Object);module.Types.Add(moduleType); } MethodDefinition cctor = moduleType.Methods.FirstOrDefault(m => m.Name == ".cctor");if (cctor == null) { cctor = new MethodDefinition(".cctor", MethodAttributes.Static | MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,module.TypeSystem.Void); moduleType.Methods.Add(cctor); ILProcessor il = cctor.Body.GetILProcessor(); il.Append(il.Create(OpCodes.Ldstr, "Module static constructor is called.")); il.Append(il.Create(OpCodes.Call, module.ImportReference(typeof(Console).GetMethod("WriteLine"new Type[] { typeof(string) })))); il.Append(il.Create(OpCodes.Ret)); }else { ILProcessor il = cctor.Body.GetILProcessor();var nop = cctor.Body.Instructions.FirstOrDefault(i => i.OpCode == OpCodes.Nop);if (nop != null) { il.InsertBefore(nop, il.Create(OpCodes.Ldstr, "Module static constructor is called.")); il.InsertBefore(nop, il.Create(OpCodes.Call, module.ImportReference(typeof(Console).GetMethod("WriteLine"new Type[] { typeof(string) })))); } } assembly.Write(ModulePath + "\\modified.dll");}

這裏有一個坑需要註意,比如本例中對 ConsoleApp5.dll 程式集進行了 <Module>..cctor函式添加,形成了新的 m odified.dll 托管DLL,這裏需要註意,這兩個托管DLL不能放在同一目錄,否則會執行出錯。保留你需要執行的托管DLL,以及xxx.runtimeconfig.json執行時檔即可。其它檔全部刪掉,即可正常執行。在.NET8裏面,這是一個巨坑,尋找了很久才發現。

至於這個BUG是怎麽引起的,這裏先放一放,後面有時間再深入了解下。

C++ if 和C# if以及條件斷點

先看下C++ if操作:

#include<stdio.h>#include<Windows.h>#include<string.h>intmain(){char str[] = "Hello, world!";char substr[] = "world";if (strstr(str, substr)) {printf("The substring \"%s\" is found in the string.\n", substr); }else {printf("The substring \"%s\" is not found in the string.\n", substr); } getchar();return0;}

這裏的strstr函式是對字串進行判斷,是否包含的意思。如果包含則返回包含的字串。一般的來說if括弧裏面是true或者false,但是這裏if括弧裏面顯然是字串,它也被if視為true進行了分支執行。再看一段程式碼:

#include<stdio.h>#include<Windows.h>#include<string.h>intmain(){int a = 1000;if (a) {printf("true"); }else {printf("false"); } getchar();return0;}

這裏依然輸出的是true,在C++裏面只要不是0或者NULL,if統統視為true。這跟C#是不同的,C#如下程式碼執行通不過:

staticvoidMain(string[] args){int a = 1000;if(a) { Console.WriteLine("true"); }else { Console.WriteLine("false"); } Console.ReadLine();}

vs直接報錯,int不能轉bool型別。由此延伸了一個條件斷點,以上的 <Module>..cctor在CLR裏面的條件斷點為例:

strstr(pMD->m_pszDebug className,"<Module>")

C++只需要strstr返回了字串,就視為true。而字串只能 <Module> 返回,所以當JIT Compile的pMD的m_pszDebug className是<Module>的時候就可以斷下來了。當然如果有多個 <Module>( 這裏是如果,實際套用 一般只有一個),我們把pMD的函式名( m_pszDebugMethodName )帶上,它的名稱是:.cctor,那麽條件斷點如下:

strstr(pMD->m_pszDebug className,"<Module>")&&(*(pMD->m_pszDebugMethodName+1)=='c')&&(*(pMD->m_pszDebugMethodName+2)=='c')

結尾

以上是實際操作中遇到的坑,分享了三個知識點,希望對大家有所幫助。

往期 精彩回顧