點選上方 藍字 江湖評談 設為關註/星標
前言
最近進行托管和非托管頻繁操作,遇到了一些坑記錄下。分別為標題標註的: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')
結尾
以上是實際操作中遇到的坑,分享了三個知識點,希望對大家有所幫助。
往期 精彩回顧