.NET托管指针

2024-06-11 17:31:18 浏览数 (1)

前言

尽管大部分时候对于底层更喜欢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编译,不如非托管来的直接。某些方面可以和非托管形成互补,已完成需要的需求以及项目疑难点,提高效率。

0 人点赞