2

I have been struggling with this topic for 1 week now and nothing I tried seems to work. I made a very simple C# class:

namespace SimpleMathLib
{
    public class SimpleMath
    {
        public float SumFloat(float a, float b)
        {
            return a + b;
        }

        public int SumInt(int a, int b)
        {
            return a + b;
        }
    }
}

in a Visual Studio 2022 solution configured to build a DLL: VS C# project settings

I did follow a few online tutorial but the one that gave me less problems was the one about creating a C++ wrapper that uses CLR and compile it as a static library (.lib). Here are the scripts I added in this wrapper:

// SimpleMathLibWrapper.h
#pragma once

class SimpleMathLibWrapperPrivate;

class __declspec(dllexport) SimpleMathLibWrapper
{
    private:
        SimpleMathLibWrapperPrivate* _private;

    public:
        SimpleMathLibWrapper();
        ~SimpleMathLibWrapper();

        float SumFloat(const float a, const float b);
        int SumInt(const int a, const int b);
};

// SimpleMathLibWrapper.cpp
#include "..\public\SimpleMathLibWrapper.h"

#include <msclr\auto_gcroot.h>
#include <msclr\marshal_cppstd.h>

using namespace System::Runtime::InteropServices;
using namespace SimpleMathLib;

class SimpleMathLibWrapperPrivate
{
    public:
        msclr::auto_gcroot<SimpleMath^> simpleMathCSharp;
};

SimpleMathLibWrapper::SimpleMathLibWrapper()
{
    _private = new SimpleMathLibWrapperPrivate();
    _private->simpleMathCSharp = gcnew SimpleMath();
}

SimpleMathLibWrapper::~SimpleMathLibWrapper()
{
    delete _private;
}

float SimpleMathLibWrapper::SumFloat(const float a, const float b)
{
    return _private->simpleMathCSharp->SumFloat(a, b);
}

int SimpleMathLibWrapper::SumInt(const int a, const int b)
{
    return _private->simpleMathCSharp->SumInt(a, b);
}

and these are the settings of the Visual Studio CLR library project I had to change: C++ CLR Wrapper tab1 C++ CLR Wrapper tab2 moreover, I added the compiled C# DLL reference to the project C++ CLR Wrapper references

Now, for the C++ executable project, it is a Visual Studio C++ console project with the following script:

// SimpleMathLibUser.cpp : This file contains the 'main' function. Program execution begins and ends there.
//

//#define LOAD_DLL_MANUALLY

#include <iostream>
#include <Windows.h>

#ifndef LOAD_DLL_MANUALLY
    #include "SimpleMathLibWrapper.h"
#endif // !LOAD_DLL_MANUALLY


#ifdef LOAD_DLL_MANUALLY
    void PrintExecutablePath()
    {
        TCHAR exePath[MAX_PATH];
        GetModuleFileName(NULL, exePath, MAX_PATH);
        char narrowExePath[MAX_PATH];

        // convert the string to a narrow character string
        if (WideCharToMultiByte(CP_ACP, 0, exePath, -1, narrowExePath, MAX_PATH, 0, 0) == 0)
        {
            std::cerr << "Failed to convert the path to a narrow character string." << std::endl;
            return;
        }

        char* lastSlash = strrchr(narrowExePath, '\\');
        if (lastSlash != NULL)
        {
            *lastSlash = '\0';
        }

        std::cout << "Current directory: " << narrowExePath << std::endl << std::endl;
    }
#endif // LOAD_DLL_MANUALLY

int main()
{
#ifdef LOAD_DLL_MANUALLY
    PrintExecutablePath();

    // load the DLL.
    HMODULE mathLib = LoadLibrary(TEXT("..\\Plugins\\SimpleMathLibWrapper.dll"));

    if (mathLib == NULL)
    {
        std::cerr << "Failed to load the DLL." << std::endl;
        return 1;
    }

    // get a pointer to functions from the DLL.
    float (*SumFloat)(float, float) = (float (*)(const float, const float))GetProcAddress(mathLib, "SumFloat");
    int (*SumInt)(int, int) = (int (*)(const int, const int))GetProcAddress(mathLib, "SumInt");

    if (SumFloat == NULL)
    {
        std::cerr << "Failed to find the 'SumFloat' function in the DLL." << std::endl;
        return 1;
    }

    if (SumInt == NULL)
    {
        std::cerr << "Failed to find the 'SumInt' function in the DLL." << std::endl;
        return 1;
    }

    // call the functions.
    float resultFloat = SumFloat(10.f, 5.f);
    std::cout << "Float Sum: " << resultFloat << std::endl;

    int resultInt = SumInt(2, 3);
    std::cout << "Int Sum: " << resultInt << std::endl;

    // unload the DLL.
    FreeLibrary(mathLib);
#else
    SimpleMathLibWrapper wrapper;

    std::cout << "Float Sum: " << wrapper.SumFloat(10.f, 5.f) << std::endl;
    std::cout << "Int Sum: " << wrapper.SumInt(2, 3) << std::endl;
#endif // LOAD_DLL_MANUALLY

    return 0;
}

As you can see, for this I tried 2 approaches:

  1. loading the wrapper manually
  2. using the linker

These are the settings I added for the linker solution (which is currently the one that at least compile and seems to work until a certain point): C++ executable project tab1 C++ executable project tab2 C++ executable project tab3 C++ executable project tab4

It doesn't matter what .NET version the C# dll is compiled with, when I run the executable, I always get the following error:

Unhandled Exception: System.IO.FileNotFoundException: Could not load file or assembly 'netstandard, Version=2.1.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51' or one of its dependencies. Impossible to find the scpecified file.
   in SimpleMathLibWrapper.{ctor}(SimpleMathLibWrapper* )
   in mainCRTStartup()

What I know is that both Runtime and SDK for the .NET version are installed and properly referenced in the system. Whatever version of the target framework I select in the C# VS project, the result is the same; only the assembly name and version changes.

I also looked for this specific issue on the internet but most of the solutions where to try to open the project directly from the .sln file and not from VS which didn't work for me.

Loading the DLL manually causes a set of different issues that I wasn't able to figure out, so, I kept the code for reference but bailed on trying to fix it.

I know this is a very long post and I encourage you to ask me for more details in case I missed something. Hopefully, finding a solution here will be able to help a lot more people in the future.

Thanks !!

7
  • This article said: C++/CLI projects cannot target .NET Standard Commented Oct 26, 2023 at 9:42
  • 2
    C++/CLI have always seemed like an afterthought, with very limited improvements over the years. I would start with using .net 4.8 , since I at least know that works, and upgrade when you got something working. I have not tried C++/CLI in .Net 5+, but I would expect some issues and limitations. I'm also unsure if this will work if the application is not a .net application. The CLR would need to be loaded to run any managed code, and I would not assume that this is done automatically. Commented Oct 26, 2023 at 9:59
  • 2
    The error message is accurate, netstandard.dll v2.1 is not available for .NET 4.x. Which is what you targeted by selecting /clr instead of /clr:netcore and not picking a specific .NET version. In this scenario you really want to minimize the number of dependencies you drag in, do favor a C# .NETFramework project that targets 4.x Commented Oct 26, 2023 at 11:59
  • 2
    It looks like you got it working using C++/CLI; I just wanted to explain why your attempts with LoadLibrary and GetProcAddress failed. GetProcAddress can only find functions which are listed in the export table of the DLL. The export table is a very different beast from the .NET metadata that enables other .NET projects to consume the DLL (and also runtime reflection). .NET DLLs can have exports, but you need to use MSIL to create them. There are some post-build tools that can be used with a DLL produced by the C# compiler, but C# alone cannot create exports. Commented Oct 26, 2023 at 15:20
  • 1
    @Apache81: That crossed my mind, but you were using the names from the C# code. The C++ wrapper code doesn't have global functions named SumFloat and SumInt, it has non-static member functions which don't match in name, don't match in signature, and don't match in calling convention. Commented Oct 27, 2023 at 14:39

1 Answer 1

2

After reading some of the comments I received, I was able to finally have everything working !!!

First thing, the C# DLL needs to be compiled with a .NET Framework version that is less or equal than 4.7.2 because of CLR.

What should you do to achieve this? Edit the .csproj file like so:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net472</TargetFramework>
    <ImplicitUsings>disable</ImplicitUsings>
    <Nullable>disable</Nullable>
    <SignAssembly>False</SignAssembly>
    <AssemblyOriginatorKeyFile>SimpleMathManaged</AssemblyOriginatorKeyFile>
  </PropertyGroup>

</Project>

making sure that <TargetFramework> points to a correct version, the <ImplicitUsing> is disabled so the project will not use the GlobalUsing that are not available for that framework and you also need to disable the <Nullable>, also not supported.

After that, recompile the DLL, the wrapper (I didn't change anything there) and also the C++ executable to update to the latest wrapper LIB. Make sure the C# DLL is copied where the .exe file is (I forgot to mention this on my original post), run the project and everything should properly work !!! :) enter image description here Finally...

Basically my initial mistake was to compile the DLL using the .NET 6.0 (the default when you create a new C# project with Visual Studio) which is higher then 4.7.2 and not supported. The Wrapper compilation process was not launching any warning or error but when used from the executable it wasn't working.

The second mistake was to try with .Net Standard 2.1 which, again, is not supported and no warnings thrown.

When I tried the .NET 4.8 as suggested by JonasH and Hans Passant (thank you, guys) in the comments, I finally got a warning from the wrapper that was telling me that the targeted .NET Framework was not supported because higher than 4.7.2 and then, finally targeting the correct version, everything worked.

I really hope that this solution can help everyone with the same problem because it was not fun to find a solution.

UPDATE !!!

For the sake of completeness, I would like to add another thing. I compiled the C++ wrapper as a static library (.lib) because when I tried to link the DLL in the C++ executable Visual Studio project: enter image description here I was getting this error:

Error LNK1302 only support linking safe .netmodules; unable to link ijw/native .netmodule
    SimpleMathLibUser D:\dev\C++\SimpleMathLibWrapper\x64\Debug\SimpleMathLibWrapper.dll

If you want to have your C++ wrapper as a dynamic library (.dll), the linker of the C++ executable Visual Studio project should link the .obj file instead of directly specifying the .dll one: enter image description here

Sign up to request clarification or add additional context in comments.

1 Comment

Big kudos for coming back with your solution ! Glad that you got it working :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.