C 与 C#混合编程 示例 基于VS1. 引言为什么需要混合编程在软件开发中我们经常会遇到这样的场景C拥有高性能的底层库如图形处理、物理模拟而C#在UI开发、快速原型设计方面更有优势。混合编程让我们能够取长补短——用C处理计算密集型任务用C#构建用户界面。本文将通过具体示例展示如何在Visual Studio中实现C与C#的混合编程。## 2. 基础概念理解两种语言的交互方式C和C#的混合编程主要有三种方式-P/InvokePlatform InvokeC#直接调用C的DLL导出函数-C/CLI微软提供的中间语言允许在.NET环境中直接使用C代码-COM互操作通过COM接口实现跨语言调用本文重点介绍最常用的P/Invoke方式因为它无需修改C代码即可直接调用。## 3. 环境准备创建VS解决方案首先在Visual Studio中创建一个包含C动态链接库和C#控制台应用程序的解决方案。### 步骤1创建C DLL项目1. 打开VS选择“创建新项目” → “动态链接库(DLL)” → 命名为“CppLibrary”2. 在解决方案资源管理器中右键“解决方案” → “添加” → “新建项目”3. 选择“控制台应用(.NET Framework)” → 命名为“CSharpApp”## 4. 基础示例C#调用C函数### 4.1 C端编写导出函数在CppLibrary项目中修改dllmain.cppcpp// dllmain.cpp : 定义DLL应用程序的入口点#include pch.h// 导出函数计算两个整数的和extern C __declspec(dllexport) int Add(int a, int b){ return a b;}// 导出函数计算圆的面积extern C __declspec(dllexport) double CalculateCircleArea(double radius){ const double PI 3.141592653589793; return PI * radius * radius;}关键点-extern C防止C名称修饰使C#能正确找到函数-__declspec(dllexport)将函数标记为可导出### 4.2 C#端调用DLL函数在CSharpApp项目中修改Program.cscsharpusing System;using System.Runtime.InteropServices;namespace CSharpApp{ class Program { // 导入C DLL中的函数 [DllImport(CppLibrary.dll, CallingConvention CallingConvention.Cdecl)] public static extern int Add(int a, int b); [DllImport(CppLibrary.dll, CallingConvention CallingConvention.Cdecl)] public static extern double CalculateCircleArea(double radius); static void Main(string[] args) { Console.WriteLine( C 与 C# 混合编程示例 ); // 调用C函数计算加法 int sum Add(15, 27); Console.WriteLine($15 27 {sum}); // 调用C函数计算圆面积 double area CalculateCircleArea(5.0); Console.WriteLine($半径为5的圆面积 {area:F2}); Console.WriteLine(\n按任意键退出...); Console.ReadKey(); } }}重要说明-DllImport属性告诉C#从哪个DLL导入函数-CallingConvention必须与C导出的调用约定一致这里是Cdecl- 函数签名必须与C端完全匹配包括参数类型和返回值### 4.3 运行配置要使程序正确运行需要确保C#项目能找到C生成的DLL1. 右键CSharpApp项目 → “属性” → “生成事件” → “生成后事件命令行”2. 添加copy $(SolutionDir)CppLibrary\$(OutDir)CppLibrary.dll $(TargetDir)或者直接设置CppLibrary为CSharpApp的依赖项右键CSharpApp → “添加” → “引用” → “项目” → 勾选CppLibrary。## 5. 进阶示例处理复杂数据结构实际开发中我们经常需要传递字符串、数组或自定义结构。下面演示如何传递字符串和数组。### 5.1 C端处理字符串和数组在CppLibrary中添加新函数cpp// 导出函数处理字符串反转字符串extern C __declspec(dllexport) void ReverseString(char* str, int length){ for (int i 0; i length / 2; i) { char temp str[i]; str[i] str[length - 1 - i]; str[length - 1 - i] temp; }}// 导出函数计算数组元素的和extern C __declspec(dllexport) int SumArray(int* arr, int size){ int sum 0; for (int i 0; i size; i) { sum arr[i]; } return sum;}### 5.2 C#端调用带指针参数的函数csharpusing System;using System.Runtime.InteropServices;using System.Text;namespace CSharpApp{ class Program { // 导入处理字符串的函数 [DllImport(CppLibrary.dll, CallingConvention CallingConvention.Cdecl)] public static extern void ReverseString(IntPtr str, int length); // 导入处理数组的函数 [DllImport(CppLibrary.dll, CallingConvention CallingConvention.Cdecl)] public static extern int SumArray(int[] arr, int size); static void Main(string[] args) { Console.WriteLine( 进阶示例处理复杂数据结构 ); // 示例1字符串处理 string original Hello World; Console.WriteLine($原始字符串: {original}); // 将字符串转换为非托管内存 IntPtr ptr Marshal.StringToHGlobalAnsi(original); ReverseString(ptr, original.Length); // 从非托管内存读取结果 string reversed Marshal.PtrToStringAnsi(ptr, original.Length); Console.WriteLine($反转后: {reversed}); // 释放非托管内存 Marshal.FreeHGlobal(ptr); // 示例2数组处理 int[] numbers { 10, 20, 30, 40, 50 }; Console.WriteLine($\n数组: [{string.Join(, , numbers)}]); int total SumArray(numbers, numbers.Length); Console.WriteLine($数组元素和: {total}); Console.WriteLine(\n按任意键退出...); Console.ReadKey(); } }}关键知识点-内存管理C#使用Marshal类在托管和非托管内存间转换数据-字符串传递使用StringToHGlobalAnsi创建非托管字符串副本-数组传递C#数组可以直接作为参数传递因为它们是连续内存块-资源释放必须手动释放通过Marshal分配的非托管内存## 6. 高级技巧错误处理与性能优化### 6.1 错误处理策略csharp// 使用try-catch捕获P/Invoke异常try{ int result Add(100, 200);}catch (DllNotFoundException ex){ Console.WriteLine($找不到DLL: {ex.Message});}catch (EntryPointNotFoundException ex){ Console.WriteLine($找不到函数入口: {ex.Message});}catch (AccessViolationException ex){ Console.WriteLine($访问冲突可能是内存错误: {ex.Message});}### 6.2 性能优化建议1.批量操作避免频繁调用小函数尽量将多个操作合并到大函数中2.内存预分配对于需要反复调用的场景预先分配非托管内存3.使用MarshalAs属性精确控制数据封送行为4.考虑C/CLI对于复杂交互C/CLI提供更好的性能和控制## 7. 总结通过本文的学习我们掌握了在Visual Studio中实现C与C#混合编程的核心方法1.基础流程创建C DLL项目使用extern C和__declspec(dllexport)导出函数在C#中使用DllImport导入并调用2.数据类型传递掌握了整数、浮点数、字符串和数组在两种语言间的传递方式3.内存管理理解了非托管内存的分配、使用和释放的重要性4.错误处理学会了捕获可能出现的异常情况混合编程不是银弹它增加了项目的复杂性。建议在以下场景使用- 需要复用已有的C高性能库- C处理计算密集型任务C#负责UI和业务逻辑- 需要访问C特有的系统资源通过合理设计接口和良好的内存管理C与C#的混合编程可以充分发挥两种语言的优势构建出既高效又易维护的应用程序。
C++ 与 C#混合编程 示例 (基于VS)
C 与 C#混合编程 示例 基于VS1. 引言为什么需要混合编程在软件开发中我们经常会遇到这样的场景C拥有高性能的底层库如图形处理、物理模拟而C#在UI开发、快速原型设计方面更有优势。混合编程让我们能够取长补短——用C处理计算密集型任务用C#构建用户界面。本文将通过具体示例展示如何在Visual Studio中实现C与C#的混合编程。## 2. 基础概念理解两种语言的交互方式C和C#的混合编程主要有三种方式-P/InvokePlatform InvokeC#直接调用C的DLL导出函数-C/CLI微软提供的中间语言允许在.NET环境中直接使用C代码-COM互操作通过COM接口实现跨语言调用本文重点介绍最常用的P/Invoke方式因为它无需修改C代码即可直接调用。## 3. 环境准备创建VS解决方案首先在Visual Studio中创建一个包含C动态链接库和C#控制台应用程序的解决方案。### 步骤1创建C DLL项目1. 打开VS选择“创建新项目” → “动态链接库(DLL)” → 命名为“CppLibrary”2. 在解决方案资源管理器中右键“解决方案” → “添加” → “新建项目”3. 选择“控制台应用(.NET Framework)” → 命名为“CSharpApp”## 4. 基础示例C#调用C函数### 4.1 C端编写导出函数在CppLibrary项目中修改dllmain.cppcpp// dllmain.cpp : 定义DLL应用程序的入口点#include pch.h// 导出函数计算两个整数的和extern C __declspec(dllexport) int Add(int a, int b){ return a b;}// 导出函数计算圆的面积extern C __declspec(dllexport) double CalculateCircleArea(double radius){ const double PI 3.141592653589793; return PI * radius * radius;}关键点-extern C防止C名称修饰使C#能正确找到函数-__declspec(dllexport)将函数标记为可导出### 4.2 C#端调用DLL函数在CSharpApp项目中修改Program.cscsharpusing System;using System.Runtime.InteropServices;namespace CSharpApp{ class Program { // 导入C DLL中的函数 [DllImport(CppLibrary.dll, CallingConvention CallingConvention.Cdecl)] public static extern int Add(int a, int b); [DllImport(CppLibrary.dll, CallingConvention CallingConvention.Cdecl)] public static extern double CalculateCircleArea(double radius); static void Main(string[] args) { Console.WriteLine( C 与 C# 混合编程示例 ); // 调用C函数计算加法 int sum Add(15, 27); Console.WriteLine($15 27 {sum}); // 调用C函数计算圆面积 double area CalculateCircleArea(5.0); Console.WriteLine($半径为5的圆面积 {area:F2}); Console.WriteLine(\n按任意键退出...); Console.ReadKey(); } }}重要说明-DllImport属性告诉C#从哪个DLL导入函数-CallingConvention必须与C导出的调用约定一致这里是Cdecl- 函数签名必须与C端完全匹配包括参数类型和返回值### 4.3 运行配置要使程序正确运行需要确保C#项目能找到C生成的DLL1. 右键CSharpApp项目 → “属性” → “生成事件” → “生成后事件命令行”2. 添加copy $(SolutionDir)CppLibrary\$(OutDir)CppLibrary.dll $(TargetDir)或者直接设置CppLibrary为CSharpApp的依赖项右键CSharpApp → “添加” → “引用” → “项目” → 勾选CppLibrary。## 5. 进阶示例处理复杂数据结构实际开发中我们经常需要传递字符串、数组或自定义结构。下面演示如何传递字符串和数组。### 5.1 C端处理字符串和数组在CppLibrary中添加新函数cpp// 导出函数处理字符串反转字符串extern C __declspec(dllexport) void ReverseString(char* str, int length){ for (int i 0; i length / 2; i) { char temp str[i]; str[i] str[length - 1 - i]; str[length - 1 - i] temp; }}// 导出函数计算数组元素的和extern C __declspec(dllexport) int SumArray(int* arr, int size){ int sum 0; for (int i 0; i size; i) { sum arr[i]; } return sum;}### 5.2 C#端调用带指针参数的函数csharpusing System;using System.Runtime.InteropServices;using System.Text;namespace CSharpApp{ class Program { // 导入处理字符串的函数 [DllImport(CppLibrary.dll, CallingConvention CallingConvention.Cdecl)] public static extern void ReverseString(IntPtr str, int length); // 导入处理数组的函数 [DllImport(CppLibrary.dll, CallingConvention CallingConvention.Cdecl)] public static extern int SumArray(int[] arr, int size); static void Main(string[] args) { Console.WriteLine( 进阶示例处理复杂数据结构 ); // 示例1字符串处理 string original Hello World; Console.WriteLine($原始字符串: {original}); // 将字符串转换为非托管内存 IntPtr ptr Marshal.StringToHGlobalAnsi(original); ReverseString(ptr, original.Length); // 从非托管内存读取结果 string reversed Marshal.PtrToStringAnsi(ptr, original.Length); Console.WriteLine($反转后: {reversed}); // 释放非托管内存 Marshal.FreeHGlobal(ptr); // 示例2数组处理 int[] numbers { 10, 20, 30, 40, 50 }; Console.WriteLine($\n数组: [{string.Join(, , numbers)}]); int total SumArray(numbers, numbers.Length); Console.WriteLine($数组元素和: {total}); Console.WriteLine(\n按任意键退出...); Console.ReadKey(); } }}关键知识点-内存管理C#使用Marshal类在托管和非托管内存间转换数据-字符串传递使用StringToHGlobalAnsi创建非托管字符串副本-数组传递C#数组可以直接作为参数传递因为它们是连续内存块-资源释放必须手动释放通过Marshal分配的非托管内存## 6. 高级技巧错误处理与性能优化### 6.1 错误处理策略csharp// 使用try-catch捕获P/Invoke异常try{ int result Add(100, 200);}catch (DllNotFoundException ex){ Console.WriteLine($找不到DLL: {ex.Message});}catch (EntryPointNotFoundException ex){ Console.WriteLine($找不到函数入口: {ex.Message});}catch (AccessViolationException ex){ Console.WriteLine($访问冲突可能是内存错误: {ex.Message});}### 6.2 性能优化建议1.批量操作避免频繁调用小函数尽量将多个操作合并到大函数中2.内存预分配对于需要反复调用的场景预先分配非托管内存3.使用MarshalAs属性精确控制数据封送行为4.考虑C/CLI对于复杂交互C/CLI提供更好的性能和控制## 7. 总结通过本文的学习我们掌握了在Visual Studio中实现C与C#混合编程的核心方法1.基础流程创建C DLL项目使用extern C和__declspec(dllexport)导出函数在C#中使用DllImport导入并调用2.数据类型传递掌握了整数、浮点数、字符串和数组在两种语言间的传递方式3.内存管理理解了非托管内存的分配、使用和释放的重要性4.错误处理学会了捕获可能出现的异常情况混合编程不是银弹它增加了项目的复杂性。建议在以下场景使用- 需要复用已有的C高性能库- C处理计算密集型任务C#负责UI和业务逻辑- 需要访问C特有的系统资源通过合理设计接口和良好的内存管理C与C#的混合编程可以充分发挥两种语言的优势构建出既高效又易维护的应用程序。