【问题标题】:How to call C++ code, and the code it calls, from C#如何从 C# 调用 C++ 代码及其调用的代码
【发布时间】:2019-07-03 13:40:41
【问题描述】:

TL;DR

我有旧的 c++ 代码,它会做一些事情(有时会返回一些东西),调用其他 cpp 代码,但不是完整的类/obj。我无法更改此代码。我正在制作新的 c# 代码,我希望从中调用 c++ 代码。我不明白是创建一个调用原始遗留 c++ 的 dll,还是创建一个 CLR?这也称为旧版 cpp。下面我有我已经实现的示例代码(有问题)。

主要

我有无法更改的 legacy.cpp 和 legacy.h。

这不是类/对象,只有公共函数、值和#defines。 legacy.cpp 和 .h 都 #include 其他文件和 cpp 库来完成它的工作。

我有一个新项目,我可以在其中添加 C#、C++ 代码

我无法理解我需要做什么/研究才能从我的新 C# 代码中调用 legacy.cpp 中定义的任何函数(或值/定义)。

我看过的一些内容包括

CLR

我目前尝试创建一个 CLR(我觉得这不是我在这种情况下需要的),但是我在 clr_wrapper.cpp 中遇到了问题,它找不到对 foo() 的引用

//Wrapper.cpp

#include "Wrapper.h"

#include "abs\path\to\legacy\code\legacy.h"
#include "abs\path\to\legacy\code\legacy.cpp"

int foo_Wrapper()
{
    return foo(); //int foo() is declared in legacy.h and defined in legacy.cpp
}
#pragma once

#include "abs\path\to\legacy\code\legacy.h"
#include "abs\path\to\legacy\code\legacy.cpp"

using namespace System; //What things use this? 
                        //Can I just prepend System:: to whatever needs it?
namespace Wrapper {
    public ref class Wrapper
    {
    public:
        int foo_Wrapper();
    };
}

foo_Wrapper() 无法调用 foo()。

我对这种方法的困惑是,我似乎需要将 clr 包装器制作成一个对象类,其中包含将根据需要调用的成员函数。导致obj.foo() 的语法。如果我选择做某种 CLR 包装器,这是需要做的吗?

DLL

我还考虑过将这一切都变成一个 dll,例如 (How to call C++ DLL in C#)

但是我对设置这个感到困惑。我目前的想法是让一个cpp dll调用原始cpp(即创建legacyDll.dll,它将调用foo(),然后我的主c#将调用extern“C”中定义的__declspec(dllexport)函数{}

当前设置(来自“如何在 c sharp 中调用 c dll”) dllmain.cpp

// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
#include <iostream>
#include <Windows.h>

using namespace std;

extern "C"
{
    __declspec(dllexport) void bar_Dll()
    {
        cout << "calling bar() in legacy code" << endl;
    }

    __declspec(dllexport) int foo_Dll()
    {
        cout << "calling foo() in legacy code" << endl;
        //realistically I would have,
        //return foo()
    }
}

Class1.cs

using System;
using System.Runtime.InteropServices;

namespace Test_DLL_Calling
{
    class Class1
    {
        [DllImport("dllmain.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern void bar_Dll();

        [DllImport("dllmain.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern int foo_Dll();

        public static void Main(string[] arg)
        {
            bar_Dll(); //The specified module could not be found (Exception)

            Console.WriteLine(foo_Dll()); //Im guessing this will also ^^
        }
    }
}

这部分我不关注。属性是什么以及为什么以它们的方式完成?

【问题讨论】:

  • "属性是什么以及为什么要按照它们的方式完成?" 这就是文档的用途。对此类问题的回答应该是查找这些属性的文档以了解它们的作用或指示。 Microsoft 提供了一个方便的.NET API browser,它使查找任何类/类型(当然包括属性类型)及其想要了解的成员变得非常简单。 (或者,在 Visual Studio 中,将文本插入符号/光标移动到您想了解更多信息的属性上,然后按 F1 键)
  • 一个 dll 有多个入口点。每个入口点都是 dll 中的一个公共函数。所以 dll 有两个方法 bar_Dll 和 foo_Dll 并且每个都返回一个整数。
  • 制作一个新的 c++/CLI .DLL(c++/CLI dll 是原生 托管代码的混合体)"P" 这将在现有的 c/c++ .DLL "E" 周围充当一个简单的代理/外观模式。您的 C# 代码调用 "P" 中的类型并且不查看/关心 "E""P" 将调用路由到 "E" 并将结果(如果有)作为托管类型返回。 无需处理繁琐的 dllexport 和/或 p-invoke。顺便说一句,根据您的需要,如果您不想,您不一定需要将所有 c++ 方法公开给 .NET。保持包装简单

标签: c# c++ dll


【解决方案1】:

好的,所以你有一个需要使用的标题和 cpp。为了使用它,您必须将 c++ 转换为 C 代码。这几乎就是您在展示的 DLL 示例代码中看到的内容。但是,我确实建议您从标头中删除包含,因为我不确定这将如何转换为 C 代码。而是将包含在 cpp 文件中。

除了展示一大堆示例代码之外,我发现回答这个问题相当困难。完整代码在:https://github.com/ze413X/Cpp-Code-Gems/ 其中“CSharpExampleUsingCpp”从 MainWindow.xaml.cs 调用使用目录中文件的“DLLExample”包括和源。

DLL:

暴露要使用的函数的标题:

#pragma once

#define DLLExport _declspec(dllexport)

extern "C" DLLExport void __cdecl GetCppText(char* str, int* strLength);

extern "C" DLLExport void __cdecl DLLPrint();

.cpp

#include "../includes/DLLExampleCode.h"

#include <string>
#include <iostream>

void __cdecl GetCppText(char* str, int* strLength)
{
    std::string text = "This is called from within the DLL.\0";
    if (*strLength < text.length() || str == nullptr)
    {
        return;
    }
    memset((void*)str, 0, (*strLength) * sizeof(char));
    strcpy_s(str, *strLength,text.c_str());
    *strLength = text.length();
}

void __cdecl DLLPrint()
{
    std::cout << "This is printed from inside DLLExample.dll.\n";
}

C#:

using System.Runtime.InteropServices;

namespace CSharpExampleUsingCpp
{
    public partial class MainWindow : Window
    {
const string PATH = "DLLExample.dll";

[DllImport(PATH, CallingConvention = CallingConvention.Cdecl)]
public static unsafe extern void GetCppText(byte[] str, out System.Int32 strLength);

....

private void CppInteropButton_Click(object sender, RoutedEventArgs e)
        {
            System.Int32 size = 256;
            System.Byte[] str = new byte[size];
            for (int i = 0; i < size; i++)
            {
                str[i] = (byte)'1';
            }

            GetCppText(str, out size);
            string result = System.Text.Encoding.UTF8.GetString(str, 0, size);
            CppInteropButtonTextBox.Text = result;
}

虽然,重新阅读我获取字符串的解决方案可能不是最好的方法。您可能可以编组该东西以避免所有这些愚蠢的 char* 转换。在我写它的那个时候,我可能有一些很好的理由。不过,这应该更容易用谷歌搜索。

【讨论】:

  • 所以我有点遵循这个,我在哪里可以罚款/创建 .dll,在你的例子中是 DLLExample.dll?我要创建它吗?
  • 该项目是适用于 Windows 的 Visual Studio 2017 项目。但是,如果您不使用它,您可以通过任何一种方式浏览文件。 Stuff.sln 包含 3 个项目。东西,DLLExample 和 CSharpExampleUsingCpp。因此,将启动项目设置为 CSharpExampleUsingCpp,您可以轻松地使用调试器跟踪代码。但是,如果您无法遵循这一点,您可以在 Stuff/DLLExample 目录中找到“DLLExample”的所有必要代码。甚至还有一个 instructions.txt 如何在 VS2017 中设置 .dll。笔记;该项目未上传构建。总是假设你自己做。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-21
  • 1970-01-01
  • 1970-01-01
  • 2011-04-29
  • 1970-01-01
  • 2023-03-25
相关资源
最近更新 更多