程序员开发实例大全宝库

网站首页 > 编程文章 正文

C#与C++交互开发系列之函数参数传递之值传递

zazugpt 2024-09-02 04:22:18 编程文章 24 ℃ 0 评论

前言

很多时候,我们在进行桌面软件程序开发的时候,会遇到C#调用C++开发的动态库。那么必然要面对函数传递参数的各种问题。今天我们就开启这个系列的学习和研究,详细解读如何交互。在学习C#调用的时候,也了解C++代码关于函数的声明,参数的定义,数据的解析,指针的使用,数据的托管内存和非托管内存之间的拷贝和内存释放等等内容。下面我们来一起熟悉:C#与C++交互开发系列之函数参数传递之值传递。

C++部分

1、新增C++动态库,我们命名为CppLibrary

2、通过类向导,我们添加类SimpleMath文件

3、用来承载我们最简单的加减乘除的四则运算。

下面是SimpleMath.h的内容:

#pragma once

#ifndef SIMPLE_MATH_H
#define SIMPLE_MATH_H

#define API extern "C" __declspec(dllexport)

API double add(double a, double b);
API double subtract(double a, double b);
API double multiply(double a, double b);
API double divide(double a, double b);

#endif // SIMPLE_MATH_H

下面是SimpleMath.cpp

#include "pch.h"
#include "SimpleMath.h"

double add(double a, double b)
{
	return a + b;
}

double subtract(double a, double b)
{
	return a - b;
}

double multiply(double a, double b) 
{
	return a * b;
}

double divide(double a, double b) 
{
	if (b == 0) {
		return 0; 
	}
	return a / b;
}

这里简单的解释下,在SimpleMath.h中的内容 SimpleMath.cpp实现比较容易理解:

  • 1. `#pragma once`:这是一个预处理指令,用于确保头文件只被编译一次。当编译器遇到 `#pragma once` 时,会将当前文件标记为已包含,并在后续引用该文件时跳过再次包含。
  • 2. `#ifndef SIMPLE_MATH_H` 和 `#define SIMPLE_MATH_H`:这是常用的防止头文件重复包含的标准方法。`#ifndef` 检查一个标识符是否未定义,如果未定义,则继续处理后续代码。`#define` 定义了这个标识符,用于防止重复包含。这种组合的作用是,如果 `SIMPLE_MATH_H` 这个标识符未定义(即第一次包含该头文件),则会继续处理后续代码,同时定义了 `SIMPLE_MATH_H` 标识符,防止下次重复包含。
  • 3. `#define API extern "C" __declspec(dllexport)`:这是一个宏定义,用于声明函数的外部可见性。`extern "C"` 用于告诉编译器按照 C 语言的约定进行函数名称的命名,而 `__declspec(dllexport)` 则用于指示这些函数将被导出到动态链接库中,以便其他程序可以使用这些函数
  • 4. 声明了四个数学运算函数 `add`、`subtract`、`multiply` 和 `divide`,这些函数将在其他文件中定义实现。
  • 5. `#endif`:这是条件编译的结束标记,表示条件编译块的结束。

C#部分

1、我们新增控制台项目,命名为CSharpApp

2、声明一个包装类CppLibraryWapper,用来封装对CppLibrary库的引用,之所以要这样封装,是出于集成化思维的考虑,便于统一管理。在这里,我们使用P/Invoke技术

namespace CSharpApp
{
    public static class CppLibraryWapper
    {
        [DllImport("CppLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern double add(double a, double b);

        [DllImport("CppLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern double subtract(double a, double b);

        [DllImport("CppLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern double multiply(double a, double b);

        [DllImport("CppLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern double divide(double a, double b);
    }
}

P/Invoke(Platform Invocation Services)是.NET提供的一项功能,用于在托管代码(如C#或VB.NET)中调用非托管代码(如C或C++)的功能。它允许.NET应用程序访问和调用系统级API、动态链接库(DLL)或共享对象(SO)中的函数。

3、编写Program代码:

static void Main(string[] args)
{
    double a = 100;
    double b = 10;

    Console.WriteLine(#34;Add = {CppLibraryWapper.add(a, b)}");
    Console.WriteLine(#34;subtract = {CppLibraryWapper.subtract(a, b)}");
    Console.WriteLine(#34;multiply = {CppLibraryWapper.multiply(a, b)}");
    Console.WriteLine(#34;Divide = {CppLibraryWapper.divide(a, b)}");
    Console.ReadLine();
}

4、运行试试

这个时候,我们发现报错了,System.DllNotFoundException:“Unable to load DLL 'CppLibrary.dll' or one of its dependencies: 找不到指定的模块。 (0x8007007E)”。找不到CppLibrary.dllDLL文件。

这个错误也是比较常见的一个错误。这个时候我们的解决办法就是。调整CppLibrary项目的输出目录为。

$(SolutionDir)CSharpApp\bin\$(Configuration)\net8.0

应用生效后,重新编译CppLibrary项目。

5、再一次遇到bug

再一次尝试运行。发现又出现了新的问题。无法找到Divide方法。原来包装类中使用的函数名,于CppLibrary库中不一致。由此说明,函数名必须一致。严格区分大小写。

修改完毕后,我们继续执行。

这一次,我们成功执行完毕,获得所预计的结果。

关于值传递

前期我们的内容搭建完毕,现在我们在C++项目中新增一个方法:

# SimpleMath.h
API void modifyValue(double value);

# SimpleMath.cpp
void modifyValue(double value)
{
	value += 10;
}

我们在C#项目中写入调用代码

[DllImport("CppLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void modifyValue(double a);

Console.WriteLine(#34;a = {a}");
CppLibraryWapper.modifyValue(a);
Console.WriteLine(#34;a = {a}");

运行起来,我们会发现,输出的结果没有变化。

思考下,这是为什么呢?原来是因为:

在 C# 中,参数传递默认情况下是值传递。这意味着当你调用一个方法时,传递给方法的是实参的值的副本,而不是实参本身。在方法内对参数的任何更改都不会影响到原始值虽然在 ModifyValue 方法内部将 value 修改为110.在 C++ 中,函数参数默认也是通过值传递的。在函数调用时,实参的值被复制到形参中,函数内部的任何修改都只会影响形参的值,而不会影响到原始实参。

总结

无论是在 C# 还是在 C++ 中,默认情况下,函数参数都是通过值传递的。这意味着在函数内部对参数的任何更改都不会影响到原始值。如果想要函数内部对参数的修改能够影响到原始值,可以考虑使用什么方法呢?这个问题,我们一下篇文章继续分析。

如果本文对你有帮助,我将非常荣幸。

如果你对P/Invoke参数传递有自己的见解,欢迎留言交流。

如果你喜欢我的文章,谢谢三连,点赞,关注,转发吧!!

#头条创作挑战赛# #编程经验# #记录我的2024#

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表