前言
尽管大部分时候对于底层更喜欢C/C 和汇编,它们对于软/硬(件)的操控可以精确到bit。但是有些场景依然要用到托管指针,可以混合提高开发效率。本篇简略看下。
例子说明
一个最简单操作即是IntPtr类型,它虽然是一个nint,但却是一个货真价实的指针,类似于C语言的*符号。
声明:
代码语言:javascript复制IntPtr ptr;
如果要把托管def函数变成指针:
代码语言:javascript复制 示例函数:
public static void def()
{
Console.WriteLine("def");
}
函数委托:
delegate void delegatedef();
委托实例:
static delegatedef deldef ;
可以
代码语言:javascript复制IntPtr f = Marshal.GetFunctionPointerForDelegate(def);
把指针变成托管函数呢?
代码语言:javascript复制deldef = Marshal.GetDelegateForFunctionPointer(f, typeof(delegatedef)) as delegatedef;
然后直接调用即可:
代码语言:javascript复制deldef();
def函数是静态的,这里在.NET8里面会提示如下错误:
代码语言:javascript复制Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
此时可以通过反射获取函数指针:
代码语言:javascript复制 MethodInfo methodInfoo = typeof(Program).GetMethod("def", BindingFlags.Public | BindingFlags.Static);
IntPtr functionPointer = methodInfoo.MethodHandle.GetFunctionPointer();
然后如上调用即可,这时候就不报错了,代码如下:
代码语言:javascript复制deldef = Marshal.GetDelegateForFunctionPointer(functionPointer , typeof(delegatedef)) as delegatedef;
deldef();
上面是一个简单的操作,再来看操作IntPtr指针,,以上面指针functionPointer为例,向IntPtr指针指向的地址赋值:
代码语言:javascript复制 Marshal.WriteIntPtr(functionPointer,value);
读取IntPtr指针指向的地址值:
代码语言:javascript复制IntPtr ptr = Marshal.ReadIntPtr(functionPointer);
有时候读取/写入的指针指向的内存受到保护,比如不能读或者不能写,或者不能执行,这时候可以用API:VirtualProtect改写IntPtr指向内存的属性:
它的声明如下:
代码语言:javascript复制 [DllImport("kernel32.dll", SetLastError = true)]
private static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);
比如我要让写入functionPointer的地方的内存属性能读,能写,能执行,代码如下,0x40即表示PAGE_EXECUTE_READWRITE。
代码语言:javascript复制 VirtualProtect(ptr1, 8, 0x40, out oldProtect);
Marshal.WriteIntPtr(functionPointer,value);
VirtualProtect(ptr1, 8, oldProtect, out oldProtect);
综合例子
上面大致介绍了托管指针的操作,下面看一个操作JIT的例子。通过托管和非托管互操,利用托管/非托管指针等知识。
JIT导出函数getJit
代码语言:javascript复制[DllImport("clrjit.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr getJit();
读取到JIT的编译函数CompileMethod,然后对这个函数进行def函数替换,也即是上面的functionPointer。这样的话,可以直接操作内存。代码如下:
代码语言:javascript复制 IntPtr ptr = getJit();
uint oldProtect;
IntPtr ptr1 = Marshal.ReadIntPtr(Marshal.ReadIntPtr(ptr));
VirtualProtect(ptr1, 8, 0x40, out oldProtect);
Marshal.WriteIntPtr(ptr1, functionPointer);
VirtualProtect(ptr1, 8, oldProtect, out oldProtect);
下面再来看下非托管的C
代码语言:javascript复制//ConsoleApplication4.cpp
#include <cstdint>
#include<Windows.h>
void* GlobalPtr;
DWORD OldProtect;
typedef int(*CompileMethodDelegate)(long long* compHnd,
long long* methodInfo,
unsigned flags,
uint8_t** entryAddress,
uint32_t* nativeSizeOfCode);
extern "C" __declspec(dllexport) int CompileMethodZhuanZhe(long long* compHnd,
long long* methodInfo,
unsigned flags,
uint8_t** entryAddress,
uint32_t* nativeSizeOfCode)
{
/* byte* ilcode = (byte*)(methodInfo 0x02);
byte* codesize = (byte*)(methodInfo 0x3);*
VirtualProtect(GlobalPtr, 8, 0x40, &OldProtect);
CompileMethodDelegate def = (CompileMethodDelegate)GlobalPtr;
VirtualProtect(GlobalPtr, 8, OldProtect, &OldProtect);
int nRet = def(compHnd, methodInfo, flags, entryAddress, nativeSizeOfCode);
return 0;
}
extern "C" __declspec(dllexport) compileMethod_def hook(void * intptr)
{
VirtualProtect(GlobalPtr, 8, 0x40, &OldProtect);
GlobalPtr = intptr;
VirtualProtect(GlobalPtr, 8, OldProtect, &OldProtect);
CompileMethodDelegate def = &CompileMethodZhuanZhe;
return def;
}
extern "C" __declspec(dllexport) int Add(int a, int b,int c,int d,int e) {
//return Compile(a, b 1);
return 0;
}
此时C#就可以
代码语言:javascript复制 [DllImport("ConsoleApplication4.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr hook(IntPtr intptr);
调用
代码语言:javascript复制IntPtr ptr = getJit();
uint oldProtect;
ptr1 = Marshal.ReadIntPtr(ptr);
IntPtr ptr2 = Marshal.ReadIntPtr(ptr1);
IntPtr hookptr = hook(ptr2);
VirtualProtect(ptr1, 8, 0x40, out oldProtect);
Marshal.WriteIntPtr(ptr1, hookptr);
VirtualProtect(ptr1, 8, oldProtect, out oldProtect);
这样托管指针,非托管指针,托管/非托管都进行了操作。以上简单的例子。
结尾
托管的指针同样可以达到非托管的效果,但是托管依然需要经过JIT编译,不如非托管来的直接。某些方面可以和非托管形成互补,已完成需要的需求以及项目疑难点,提高效率。