C#与C++高效互操作指南

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;
		
		}
	}
}
相关推荐
CSDN_RTKLIB2 小时前
std::string打印原始字节查看是否乱码
c++
shilei_c2 小时前
qt qDebug无输出问题解决
开发语言·c++·算法
jghhh012 小时前
基于C#的CAN总线BMS上位机开发方案
开发语言·c#
一切尽在,你来2 小时前
C++ 零基础教程 - 第4讲-实现简单计算器
开发语言·c++
是店小二呀2 小时前
Visual Studio C++ 工程架构深度解析:从 .vcxproj 到 Qt MOC 的文件管理实录
c++·qt·visual studio
枫叶丹42 小时前
【Qt开发】Qt系统(十二)-> Qt视频
c语言·开发语言·c++·qt·音视频
浅念-2 小时前
C语言文件操作
c语言·c++·经验分享·笔记·学习
Ivanqhz2 小时前
向量化计算
开发语言·c++·后端·算法·支持向量机·rust
一切尽在,你来2 小时前
C++ 零基础教程 - 第 7 讲 bool运算符和选择结构教程
c++