CppSharp
CppSharp copied to clipboard
A potential bug with C/C++ "MarshalAs" generation and a general architecture question (repro provided)
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
bindirectory/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
.dllwould 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++
.dllwhich hasextern "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 🙂
Here's the output of running the command if it's useful:
https://gist.github.com/GavinRay97/d0cff88e9fe39f0b3bf9997233d143f9
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.
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?
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

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:

I changed this in two places from Type[] to System.Type[]
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:

Make sure that CppSharp.CppParser.dll is inside your bin folder next to other managed DLLs.
It may not be automatically copied by VS.
Make sure that
CppSharp.CppParser.dllis 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:

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)