CppSharp icon indicating copy to clipboard operation
CppSharp copied to clipboard

A potential bug with C/C++ "MarshalAs" generation and a general architecture question (repro provided)

Open GavinRay97 opened this issue 4 years ago • 7 comments
trafficstars

I'm not sure if what I am trying to do is possible, so I'll try to keep this brief. I have a general question, and then what I think may be a bug in the generated code.

Note: The reproduction (including the headers and bin directory/CLI .exe) is here: https://github.com/GavinRay97/reaper-cppsharp-reproduction

Question:

  • What I'd like to do is use the C++/CLI generator to create a mixed C++/CLI & C# .dll
  • This .dll would be loaded by a third-party application, which provides headers for it's API/SDK
    • https://github.com/justinfrankel/reaper-sdk/blob/main/sdk/reaper_plugin.h
    • https://github.com/justinfrankel/reaper-sdk/blob/main/sdk/reaper_plugin_functions.h
  • The entrypoint of the DLL needs to be a custom-named method, which looks something like this:
    • extern "C" {
      __declspec(dllexport)
      int ReaperPluginEntry(HINSTANCE hInstance, reaper_plugin_info_t *rec) {  
        if (rec) {
          // REAPERAPI_LoadAPI(rec->GetFunc) uses the "GetFunc()" method to iterate over about ~1,000 function names in a table
          // and loads the definitions from the host application into application-global pointer variables.
          if (rec->caller_version != REAPER_PLUGIN_VERSION || !rec->GetFunc || !REAPERAPI_LoadAPI(rec->GetFunc))
            return 0;
          // "reaper_plugin_info_t" struct "rec" has "Register()" and "GetFunc()" methods for communicating with the API/SDK
          if (!rec->Register ||
              !rec->Register("pcmsrc",&myRegStruct) ||
              !rec->Register("pcmsink",&mySinkRegStruct))
            return 0;
          // our plugin registered, return success
          return 1;
        }
        return 0;
        }
      }
      
  • So I was wondering whether I could:
    • Generate C/C++ bindings to the types in those two headers
    • Write a C/C++ .dll which has extern "C" { __declspec(dllexport) int ReaperPluginEntry() } that just passes control over to C# .dll
    • From C#, call/interact with the C++ methods, so the end-experience to the developer is that they're just writing C# code

Bug?:

Running the following, on the branch compile-symbols-clang from commit https://github.com/mono/CppSharp/commit/7c9073c100dfadd98f697136ceb91d8147bdab40, I get some strange output: (Also happens on Nuget package release, I tried doing this programmatically from C# first before building it from source and realizing there was a CLI)

$ "./Release_x64/CppSharp.CLI.exe" -o ./reaper-generated --debug --verbose \
  --generator=cli --platform=win --arch=x64 \
  --exceptions --rtti --checksymbols \
  -D=REAPERAPI_IMPLEMENT \
  ./include/reaper_plugin_functions.h

But essentially, it appears to generate a lot of code like this: https://github.com/GavinRay97/reaper-cppsharp-reproduction/blob/master/reaper-generated/reaper_plugin.h#L267-L271

[System::Runtime::InteropServices::UnmanagedFunctionPointer(System::Runtime::InteropServices::CallingConvention::Cdecl)] 
delegate bool Func_bool___IntPtr_[MarshalAs(UnmanagedType_CustomMarshaler,_MarshalTypeRef_=_typeof(CppSharp_Runtime_UTF8Marshaller))]_string(::System::IntPtr __instance, System::String^ arg1);

There are these [MarshalAs(UnmanagedType_CustomMarshaler,_MarshalTypeRef_=_typeof(CppSharp_Runtime_UTF8Marshaller))] interspersed in the middle of a lot of strings, and according to Visual Studio is an error:

  • Func_bool___IntPtr_[MarshalAs(UnmanagedType_CustomMarshaler,_MarshalTypeRef_=_typeof(CppSharp_Runtime_UTF8Marshaller))]_string

I'm not sure whether this is intended and Visual Studio is mistaken (and I'm too clueless to have known) or whether this is incorrect output. I don't know anything much about C++/CLI or C# unfortunately.

It also seems to ignore all of the functions/variables inside of reaper_plugin_functions.h -- only a single definition is generated from that file. 🤔


Feel free to close this if this isn't in line with your issues, but any help/advice would be appreciated. Thank you 🙂

GavinRay97 avatar Mar 30 '21 21:03 GavinRay97

Here's the output of running the command if it's useful:

https://gist.github.com/GavinRay97/d0cff88e9fe39f0b3bf9997233d143f9

GavinRay97 avatar Mar 30 '21 21:03 GavinRay97

That looks like a bug for sure, it's for some reason going through a C# only code path, even though it's set up for C++/CLI.

Can you try setting up a breakpoint in https://github.com/mono/CppSharp/blob/main/src/Generator/Types/Std/Stdlib.CSharp.cs#L104 and check/paste the call stack?

This week I am extremely busy so sorry but cannot take a look myself at this time.

tritao avatar Mar 31 '21 07:03 tritao

That looks like a bug for sure, it's for some reason going through a C# only code path, even though it's set up for C++/CLI.

Can you try setting up a breakpoint in https://github.com/mono/CppSharp/blob/main/src/Generator/Types/Std/Stdlib.CSharp.cs#L104 and check/paste the call stack?

This week I am extremely busy so sorry but cannot take a look myself at this time.

More than happy to do this, appreciate any response =) (I'm not entirely sure how to go about doing this though, to be honest)

What I'm going to try is creating a new file, test.cs inside CppSharp project folder, with this content:

using System;
using CppSharp.AST;
using CppSharp.Generators;

namespace CppSharp
{
  public class ReaperSDKParser : ILibrary
  {
    /// Setup the driver options here.
    public void Setup(Driver driver)
    {
      var options = driver.Options;
      options.GeneratorKind = GeneratorKind.CLI;
      options.OutputDir = "./reaper-generated";
      // I'm not sure what this does but it sounds important/useful
      options.UsePropertyDetectionHeuristics = true;

      var module = options.AddModule("Reaper");
      module.IncludeDirs.Add(@"./reaper-sdk/sdk");
      module.Headers.Add("reaper_plugin_functions.h");
    }

    /// Setup your passes here.
    public void SetupPasses(Driver driver)
    {

    }

    /// Do transformations that should happen before passes are processed.
    public void Preprocess(Driver driver, ASTContext ctx)
    {

    }

    /// Do transformations that should happen after passes are processed.
    public void Postprocess(Driver driver, ASTContext ctx)
    {

    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      ConsoleDriver.Run(new ReaperSDKParser());
    }
  }
}

And then I'll set the breakpoint on that line in Visual Studio, and do dotnet run test.cs?

GavinRay97 avatar Mar 31 '21 14:03 GavinRay97

EDIT: This is probably because I have the solution built from the docs using the "release" mode suggested. I'll reclone the project and run the build process to generate a debug solution 🤦

EDIT 2: Nevermind, not doing that 😅

C:\Users\rayga\Projects\tmp\CppSharp\build (compile-symbols-clang -> origin)
λ sh build.sh generate -configuration Debug -platform x64
Downloading: https://github.com/mono/CppSharp/releases/download/CppSharp/llvm-c40cea-windows-vs2019-x64-Debug.7z
Error: llvm-c40cea-windows-vs2019-x64-Debug.7z is unavailable.
Please create your own LLVM package by executing the following commands:
./build.sh clone_llvm
./build.sh build_llvm
./build.sh package_llvm

I get this:

  Message: 
    System.BadImageFormatException : Could not load file or assembly 'CppSharp.Parser.CLI, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. An attempt was made to load a program with an incorrect format.
  Stack Trace: 
    Driver.ctor(DriverOptions options)
    ConsoleDriver.Run(ILibrary library) line 413
    CSharpTests.TestReaperSDKParser() line 1626

image

Also one note, not sure if this matters, but I had to change one file to get the tests to build when I tried to put them in the Csharp tests directory:

image

I changed this in two places from Type[] to System.Type[]

GavinRay97 avatar Mar 31 '21 15:03 GavinRay97

So I tried also to use a linked project/solution dependency on CppSharp.Gen, and I get the same error. I can see that the .dll is in the folder that it's trying to read, but opening the dependency view shows it's missing two .dlls:

image

GavinRay97 avatar Mar 31 '21 17:03 GavinRay97

Make sure that CppSharp.CppParser.dll is inside your bin folder next to other managed DLLs. It may not be automatically copied by VS.

tritao avatar Apr 01 '21 16:04 tritao

Make sure that CppSharp.CppParser.dll is inside your bin folder next to other managed DLLs. It may not be automatically copied by VS.

I apologize for being quite likely annoying, but I think I am a bit out of my depth here with this .NET build and debugging tooling 😓

I've copied all the .dll's manually, but it's complaining that the architecture conflict between x64 and MSIL is some issue:

image

If I could trouble you to give me maybe minimum-viable steps for how to add a single .cs file in the CppSharp project which I can do dotnet run on that creates the ConsoleRunner() and will trigger the breakpoint I would be grateful 🙏

(Or however is easiest to test this just to get the stack trace)

GavinRay97 avatar Apr 01 '21 17:04 GavinRay97