cs
using System;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace ConsoleApp9.C__C__
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
struct GT_POint
{
public int x;
public int y;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public struct PersonInfo
{
public int Age;
public double Height;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
public string Name;
}
internal class Demo001
{
internal const string DLLPath = @"ConsoleApplication3.dll";
[DllImport(DLLPath, CallingConvention = CallingConvention.Cdecl)]
internal extern static int Add(int a, int b);
[DllImport(DLLPath, CallingConvention = CallingConvention.Cdecl)]
internal extern static double Add_double(double a, double b);
[DllImport(DLLPath, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
static extern void PrintMessage(string message);
[DllImport(DLLPath, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
static extern void PrintMessageW(string message);
// C# - 方式1:使用 IntPtr
[DllImport(DLLPath, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
static extern IntPtr GetGreeting();
[DllImport(DLLPath, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
static extern bool GetComputerName(StringBuilder buffer, ref int size);
[DllImport(DLLPath, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
static extern GT_POint CreatePoint(int a, int b);
[DllImport(DLLPath, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
static extern void GetPersonInfo(ref PersonInfo p);
[DllImport(DLLPath, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
unsafe static extern void ProcessPoints(GT_POint* points, int count);
[DllImport(DLLPath, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
static extern void ProcessPoints(IntPtr points, int count);
[DllImport(DLLPath, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
static extern unsafe void ProcessData(byte* buffer, int bufferSize);
public static void Test001()
{
{
//值类型直接传递
int a = 100;
int b = 20;
var addddd = Add(a, b);
Console.WriteLine($"Invoke Add:{a}+{b}={addddd}");
}
{
//字符串传递
PrintMessage("C# 传递 string to c++");
PrintMessageW("C# 传递 string to c++,,宽字符版本!");
string greeting = Marshal.PtrToStringAnsi(GetGreeting());
// 使用
int size = 500;
var buffer = new StringBuilder(size);
var f3 = GetComputerName(buffer, ref size);
string name = buffer.ToString();
}
{
//结构体传递
var s5 = CreatePoint(100, 200);
PersonInfo info = new PersonInfo();
var info_sz = Marshal.SizeOf(info);
GetPersonInfo(ref info);
var f33 = info.Height == 1.75;
}
{
//结构体数组
// 使用
var points = new GT_POint[10];
{
var lastItem = points.Last();
Console.WriteLine($"start {lastItem.x},{lastItem.y}");
}
unsafe
{
for (int i = 0; i < points.Length; i++)
{
points[i] = new GT_POint { y = i, x = i * 2 };
}
// 2. 固定数组内存,获取首地址(关键:fixed 防止GC移动)
fixed (GT_POint* pPoints = points) // pPoints 指向数组首元素的地址
{
ProcessPoints(new IntPtr(pPoints), points.Length);
}
for (int i = 0; i < points.Length; i++)
{
points[i] = new GT_POint { y = i, x = i * 2 };
}
fixed (GT_POint* pPoints = points) // pPoints 指向数组首元素的地址
{
ProcessPoints(pPoints, points.Length);
}
}
/*
GCHandle 跨方法、跨异步、长期固定 稍复杂(需手动 Alloc/Free) 高(可传递句柄,随时获取地址)
unsafe + fixed 局部代码块(仅在 fixed 内有效) 简洁 低(仅局部使用,无法跨方法)
*/
{
for (int i = 0; i < points.Length; i++)
{
points[i] = new GT_POint { y = i, x = i * 2 };
}
// 2. 关键:固定数组内存,创建GCHandle(防止GC移动/回收数组)
//用 GCHandle 固定托管数组内存,解决 GC 移动导致的指针失效问题,实现 C# 与 C++ 的直接内存交互。
GCHandle handle = GCHandle.Alloc(points, GCHandleType.Pinned);
try
{
// 3. 获取固定后的数组首地址(非托管指针,可直接传给C++)
IntPtr ptr = handle.AddrOfPinnedObject();
ProcessPoints(ptr, points.Length);
// 核心用途:将ptr传递给C++函数,C++可直接读写该内存
// 示例:调用C++ DLL函数,传入ptr和数组长度
// CppNativeMethod.WriteBuffer(ptr, buffer.Length);
}
finally
{
// 4. 释放GCHandle,解除内存固定(必须执行,否则内存泄漏)
handle.Free();
}
}
{
var lastItem = points.Last();
Console.WriteLine($"end {lastItem.x},{lastItem.y}");
}
}
//替代 unsafe + fixed 写法(fixed 仅支持局部代码块,GCHandle 可跨方法、跨异步使用)。
{
ProcessTempData();
}
{
Stopwatch stopwatch = new Stopwatch();
int trr = 100000;
stopwatch.Start();
for (int i = 0; i < trr; i++)
{
T11();
}
stopwatch.Stop();
Console.WriteLine($"stackalloc 耗时: {stopwatch.ElapsedMilliseconds} ms");
stopwatch.Start();
for (int i = 0; i < trr; i++)
{
T12();
}
stopwatch.Stop();
Console.WriteLine($"new 耗时: {stopwatch.ElapsedMilliseconds} ms");
}
}
static void T12()
{
unsafe
{
const int BufferSize = 256; // 栈上安全大小
// stackalloc 分配栈内存(零GC,地址固定)
var buffer = new byte[BufferSize];
// 1. C# 写入初始数据
for (int i = 0; i < BufferSize; i++)
buffer[i] = (byte)i;
//Console.WriteLine("C# 写入前 4 字节:" + buffer[0] + ", " + buffer[1] + ", " + buffer[2] + ", " + buffer[3]);
// 2. 直接把栈指针传给 C++(零拷贝,无需 fixed/GCHandle)
fixed (byte* ptr = buffer)
{
ProcessData(ptr, BufferSize);
}
// 3. C++ 已经修改了 buffer,C# 直接读取结果
//Console.WriteLine("C++ 修改后前 4 字节:" + buffer[0] + ", " + buffer[1] + ", " + buffer[2] + ", " + buffer[3]);
}
}
static void T11()
{
unsafe
{
const int BufferSize = 256; // 栈上安全大小
// stackalloc 分配栈内存(零GC,地址固定)
byte* buffer = stackalloc byte[BufferSize];
// 1. C# 写入初始数据
for (int i = 0; i < BufferSize; i++)
buffer[i] = (byte)i;
//Console.WriteLine("C# 写入前 4 字节:" + buffer[0] + ", " + buffer[1] + ", " + buffer[2] + ", " + buffer[3]);
// 2. 直接把栈指针传给 C++(零拷贝,无需 fixed/GCHandle)
ProcessData(buffer, BufferSize);
// 3. C++ 已经修改了 buffer,C# 直接读取结果
//Console.WriteLine("C++ 修改后前 4 字节:" + buffer[0] + ", " + buffer[1] + ", " + buffer[2] + ", " + buffer[3]);
}
}
static unsafe void ProcessTempData()
{
/*
结合你之前的 C# ↔ C++ 互操作场景,stackalloc 非常适合传递临时、小体积的缓冲区给 C++,因为:
栈内存地址固定,无需 GCHandle 或 fixed 固定(栈不会被 GC 移动);
用完即释放,无内存泄漏风险,比堆分配更安全高效。
在高频调用的方法(如循环、事件回调、性能敏感逻辑)中,
频繁用 new 创建小数组会产生大量堆垃圾(GC garbage),触发 GC 回收导致程序卡顿。
stackalloc 分配的内存不进入 GC 托管,完全规避 GC 压力,适合高频短生命周期的内存需求。
仅支持值类型:只能分配 byte/int/struct 等值类型,不能分配引用类型(如 string、class)。
内存大小受限:栈空间很小(默认 1MB 左右),只能分配小内存(通常几 KB 到几十 KB,严禁分配 MB 级以上),否则会触发 StackOverflowException(栈溢出)。
生命周期固定:内存仅在当前方法执行期间有效,方法返回后立即释放,不能返回 stackalloc 的指针 / Span(会指向无效内存,导致野指针)。
无 GC 回收,无初始化:分配的内存不会自动初始化为 0,需手动清零;也不能被 GC 管理,无需手动释放。
传统用法需 unsafe:用指针接收 stackalloc 必须开启 unsafe 上下文;结合 Span<T> 可无需 unsafe。
满足以下全部条件时,优先用 stackalloc:
需要临时、短生命周期的内存(方法内用完即弃);
内存体积很小(KB 级,避免栈溢出);
追求极致性能,或需要减少 GC 压力(高频调用场景);
与非托管代码交互时,传递临时指针(无需 fixed);
分配的是值类型,且无需跨方法返回该内存。
*/
// stackalloc 在栈上分配100字节的缓冲区(无 GC 开销)
byte* buffer = stackalloc byte[100];
// 模拟数据处理:写入、读取
for (int i = 0; i < 100; i++)
{
buffer[i] = (byte)(i % 256);
}
Console.WriteLine("处理完成,栈缓冲区自动释放");
// 方法结束,buffer 指向的栈内存自动释放,无需手动管理
}
}
}
C++ 部分
cpp
#pragma once
#include <iostream>
#include <string>
//windows上不管是C还是C++,默认使用的都是__stdcall方式。
#ifdef _WIN32
#define IB_MSHOW_API __declspec(dllexport)
#else
#define IB_MSHOW_API
#endif
#pragma pack(push, 1)
struct File_RECT {
int left;
int top;
int right;
int bottom;
};
struct GT_RECT {
int left;
int top;
int right;
int bottom;
unsigned char r;
unsigned char g;
unsigned char b;
};
struct GT_POINT {
int x;
int y;
};
struct PersonInfo {
int Age;
double Height;
char Name[50];
};
#pragma pack(pop)
cpp
// ConsoleApplication3.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include "export.h"
#include <string>
using namespace std;
extern "C" {
IB_MSHOW_API int Add(int a, int b) {
return a + b + 100;
}
IB_MSHOW_API double Add_double(double a, double b) {
return a + b + 100;
}
IB_MSHOW_API void PrintMessage(const char* message) {
printf("%s\n", message);
}
IB_MSHOW_API void PrintMessageW(const wchar_t* message) {
wprintf(L"%s\n", message);
}
IB_MSHOW_API const char* GetGreeting() {
return "Hello from C++, 大家好!!";
}
IB_MSHOW_API bool GetComputerName(char* dest, int* size) {
const char* src = "你的计算机名称:MYCOMPUTER";
int inSZ = *size;
int sz = *size = strlen(src);
if (inSZ < sz)
{
return false;
}
// 安全拷贝:最多拷贝9个字符(留1个位置给\0)
errno_t ret = strncpy_s(dest, sz + 1, src, sz);
if (ret == 0) {
printf("拷贝成功:%s\n", dest); // 输出:Hello Wor(9个字符+自动补\0)
return true;
}
else {
printf("拷贝失败,错误码:%d\n", ret);
}
// 填充 buffer
/* strncpy(buffer, "你的计算机名称:MYCOMPUTER", *size);
*size = strlen(buffer);*/
return false;
}
IB_MSHOW_API GT_POINT CreatePoint(int x, int y) {
return GT_POINT{ x, y };
}
IB_MSHOW_API void __cdecl GetPersonInfo(PersonInfo* info) {
auto p8 = sizeof(PersonInfo);
const char* src = "John Doe 没有中文名称";
//strcpy(info->Name, "John Doe 没有中文名称");
//int sz = strlen(src);
//errno_t ret = strncpy_s(info->Name, sz + 1, src, 50);
strcpy_s(info->Name, 50, src);
int m2 = sizeof(double);
info->Age = 30;
info->Height = 1.75f;
}
IB_MSHOW_API void __cdecl ProcessPoints(GT_POINT* points, int count)
{
for (int i = 0; i < count; i++) {
points[i].x += 200;
points[i].y += 2000;
}
}
IB_MSHOW_API void __cdecl ProcessData(void* points, int count)
{
for (int i = 0; i < count; i++)
{
int a = 5;
}
}
}