C
C#•2y ago
Up

C++ Interop - writing a shim to use an existing c++ library [Answered]

So I'm currently trying to make a shim for a C++ library that I can then call from C#. The original library uses CMake, not MSBuild, so I've set up Cmake, let it generate the .vcxproj files, made a 2nd C++ project for my shim classes and made it depend on the generated projects. Then I've added that + the generated projects into my C# msbuild solution. Now I am trying to call a hello world function that I made in my shim library form C#, but that immediately fails with no indicator as to what went wrong. all I get is System.Runtime.InteropServices.SEHException: 'External component has thrown an exception.'. How I declared it on the C++ side:
extern "C" {
void __declspec(dllexport) __stdcall CLIP_Hello();
}
extern "C" {
void __declspec(dllexport) __stdcall CLIP_Hello();
}
How I declared it on the C# side:
[DllImport("ClipShim", EntryPoint = "CLIP_Hello", CallingConvention = CallingConvention.StdCall)]
public static extern void Hello();
[DllImport("ClipShim", EntryPoint = "CLIP_Hello", CallingConvention = CallingConvention.StdCall)]
public static extern void Hello();
any help appreciated!
71 Replies
Davide
Davide•2y ago
dumb question, have you implemented it in the c++ file or only declared?
Up
Up•2y ago
The part I posted is my header file, I've implemented it in a corresponding source file. All it does is print hello world tho, so I omitted that part from the post
Davide
Davide•2y ago
have you tried another calling convention such as __cdecl?
Up
Up•2y ago
yes, same result.
canton7
canton7•2y ago
What's actually in your CLIP_Hello function? I know you think it's unimportant, but it does look like it's throwing an exception...
Up
Up•2y ago
a single printf("Hello world");, but I've tried without that (empty method) and that didn't change anything
canton7
canton7•2y ago
What happens if you remove the C++ library altogether, and just call an empty method in the shim library (although it isn't shimming anything any more)
Up
Up•2y ago
this is an empty method in the shim library
canton7
canton7•2y ago
"Then I've added that + the generated projects into my C# msbuild solution." -- what do you mean by "added" exactly? Right, but is the shim library linked against the original library at all?
Up
Up•2y ago
solution --> add --> existing project yes
canton7
canton7•2y ago
Right, so I'm saying to try it without that linking Wait, are you trying to do p/invoke or C++/cli? If you're doing p/invoke, the native dll doesn't get added to your .NET project at all (well, you can add it as a 'None' file type, and set it to copy to the output directory, to make things easier). The DLL just has to be in the bin/ directory, next to the compiled .NET assembly
Up
Up•2y ago
p/invoke, and I have made visual studio add it to the .net project ok so.. one thing at a time:
canton7
canton7•2y ago
You added it as a file to the .NET project, or as a project to the .NET solution? You said one, and then the other
Up
Up•2y ago
what do you mean "added"
clip is the native library I am trying to use. problem: it uses CMake. so setting that up it generated vcxproj files for clip, ZERO_CHECK, ALL_BUILD and a bunch of test stuff that I have ignored and not imported. ClipShim is my shim library, that depends on clip.
Up
Up•2y ago
added a project to the solution, made my C# project depend on it
canton7
canton7•2y ago
"made my C# project depend on it" -- how, exactly? .NET assemblies can't depend directly on native assemblies
Up
Up•2y ago
build dependencies --> project dependencies --> checked the ClipShim project
canton7
canton7•2y ago
Oh right, just as a build dependency
Up
Up•2y ago
now when I build my C# project, it also compiles the shim library
canton7
canton7•2y ago
Does the shim library DLL actually end up in your .NET project's bin folder?
Up
Up•2y ago
yes, let me get to that xD
Up
Up•2y ago
next up, in the shim project's properties (Configuration Properties --> Advanced), I set it to copy references + debug symbols to the out dir.
Up
Up•2y ago
now when I build (also verified it works with publishing to folder), I get a ClipShim.dll (and pdb) file next to my C# exe file
canton7
canton7•2y ago
And ClipShip dynamically links Clip? Or statically links?
Up
Up•2y ago
where do I check that again? (sorry not too well versed with vcxprojects)
canton7
canton7•2y ago
Is Clip a .dll or .so?
Up
Up•2y ago
source, gets compiled when building ClipShim
canton7
canton7•2y ago
What does it get compiled to?
Up
Up•2y ago
dll afaik
canton7
canton7•2y ago
OK, so dynamically linked. Is the Clip dll present in the bin folder of your .NET application?
Up
Up•2y ago
no
canton7
canton7•2y ago
Well, that's one problem (that's related to why I asked you to try removing the dependency from ClipShim to Clip, and see whether that changed anything)
Up
Up•2y ago
if I enable the same copy behavior as I did above for the shim project, I get the following issue: 1) it produces a .lib file just fine 2) it fails at trying to copy <solution dir>\x64\Debug\ZERO_CHECK because that doesn't exist
canton7
canton7•2y ago
Forget about the build dependency stuff for now, and just manually copy the dlls into the right place Once that's working, you can work backwards and fix each subsequent problem as you come to it. You'll be starting from a working base, which makes it a lot easier
Up
Up•2y ago
removed the clip dependency alltogether, but still get the same exception as I did initially, with it unble to call into the shim dll
canton7
canton7•2y ago
Great, it's good to narrow things down Can you check the full list of compiler flags for Shim, and see whether /clr is in there anywhere?
Up
Up•2y ago
you're probably asking for this?
canton7
canton7•2y ago
Right, so you are building a C++/CLI library. I did ask...
Up
Up•2y ago
sorry.. this is all very unfamiliar territory
canton7
canton7•2y ago
You're trying to combine two worlds. C++/CLI projects are C++ but compiled into IL (or at least, a mix of native code and IL), and they run on the .NET runtime. You can reference a C++/CLI project from a C# project in the normal way -- as far as C# is concerned, it's a normal .NET assembly which it can just call into Native C++ projects don't run on the .NET runtime and don't have any IL in them, and C# interops with those by using p/invoke
Up
Up•2y ago
ok so.. given that the clip library itself is NOT a .net library, what should I do from here?
canton7
canton7•2y ago
So, pick one. Either compile the project as a native C++ library and p/invoke, or compile it as C++/CLI and reference it in the way you'd reference any other .NET assembly (without using pinvoke at all) C++/CLI projects can reference normal C++ projects -- it's what makes them really useful for interop
Up
Up•2y ago
ohhh I see so what changes would I make to my declarations? (if I go the C++/CLI route)
canton7
canton7•2y ago
I didn't think .NET Core had good support for C++/CLI, but looking at that setting, that might have changed. C++/CLI is more powerful -- you get to use objects, the C++ code can use .NET types, etc, and you don't have to flatten everything down into pointers and C function calls If you're going the proper C++/CLI route, your shim library declares .NET classes but with using C++ syntax. It's slightly odd until you get used to it. It would define .NET classes using ref class SomeType etc, and reference other .NET types using managed pointers (^ pointers) and gcnew
Up
Up•2y ago
you see this all started out because apparently nobody ever bothered to support more than text in clipboards on .net 😅 and while I could just p/invoke 2 function calls for windows, that expands into 1k+ lines on linux very quickly...
canton7
canton7•2y ago
But those types compile to IL, and can be used directly from your C# library If this needs to work on linux, just check what the C++/CLI story is there. I'm not sure what support is like
Up
Up•2y ago
linux support was the main reason I'm doing this, yeah. (again, on windows it'd be 2 simple p/invoke calls)
canton7
canton7•2y ago
If you need linux support, you probably don't want to be building the C++ shim library with VS to begin with 😛
Up
Up•2y ago
well, msbuild that does work on linux as far as I know
canton7
canton7•2y ago
Well, MSVC is the relevant bit Which is a windows C++ compiler
Up
Up•2y ago
heh..
canton7
canton7•2y ago
Yeah, if this all needs to run on linux you're looking at p/invoke, and probably looking to build your shim library with gcc/clang
Up
Up•2y ago
well msvc works on linux just fine (once you manage to install it) but yeah I got both gcc and clang set up on here so might as well use one of those
canton7
canton7•2y ago
I'm surprised by that. You're not running it in wine or something are you?
Up
Up•2y ago
no, I'm currently on windows
canton7
canton7•2y ago
As in, when you say "well msvc works on linux just fine"
Up
Up•2y ago
I've had to use it for a project before and made it work in a docker container on linux host, that is but yeah it may have used wine under the hood, don't remember exactly, been over a year
canton7
canton7•2y ago
Did you make it work in wine, or natively? If it was using wine, then the compiled dll will probably only have worked under wine... https://docs.microsoft.com/en-us/cpp/linux/download-install-and-setup-the-linux-development-workload?view=msvc-170 suggests good support for linux, but that says it's using gcc/clang, and it's a fairly recent article So I still don't think MSVC can build native linux binaries
Up
Up•2y ago
the pipeline was set up to use that linux container to produce both windows + linux binaries, I used the windows ones, other dev used linux. idk whether that was with or without wine
canton7
canton7•2y ago
Either way, I'm pretty sure you want to be building with gcc/clang here. And that'll definitely give you a native binary, not a C++/CLI one then p/invoke should work fine
Up
Up•2y ago
yeah uh.. I'm gonna recreate the shim project from scratch, I somehow managed to kill VS just now 😅 what template would be best for this? (for the shim)
canton7
canton7•2y ago
I haven't tried to do a GCC/clang project in VS before. The link I posted above looks decent, otherwise for something quick and dirty I'd just avoid VS altogether
Up
Up•2y ago
wouldn't have any issues with that, I prefer rider. thing is, I still want it to be set up cleanly so that building the application automatically builds the shim, and building the shim automatically builds the cpp lib problem is all the "linux" workflow stuff expects a remote environment to compile on, and while I could set that up, I don't want to, given that I already have all the necessary toolchains right on my windows system, don't need them on WSL
canton7
canton7•2y ago
Tbh I'd reach for another build system, and make that call dotnet build
Up
Up•2y ago
aaand here we go again. made a new DLL project (gonna see about the toolchain later as it right now only lets me choose a nonexistent clang-cl or visual studio msvc). removed the autogenerated precompiled header stuff because I don't want that right now. now building the solution succeeds, however the shim library builds to a different folder and I can't get it to copy to the proper build dir. and launching my c# project is no longer possible because it now tries to execute the shim dll, which is of course not possible (urgh it had automatically selected the shim as startup project, fixed..) ok ended up hardcoding the output path, but eyyy seems to work now :D so I can call into my shim library just fine now; however as soon as I try to depend on clip, I'm getting linker errors for not being able to resolve external functions. with MSVC:
1>clip_win.obj : error LNK2019: unresolved external symbol __imp_SHCreateMemStream referenced in function "bool __cdecl clip::win::read_png(unsigned char const *,unsigned int,class clip::image *,struct clip::image_spec *)" (?read_png@win@clip@@YA_NPEBEIPEAVimage@2@PEAUimage_spec@2@@Z)
1>clip_win.obj : error LNK2019: unresolved external symbol __imp_SHCreateMemStream referenced in function "bool __cdecl clip::win::read_png(unsigned char const *,unsigned int,class clip::image *,struct clip::image_spec *)" (?read_png@win@clip@@YA_NPEBEIPEAVimage@2@PEAUimage_spec@2@@Z)
with clang-cl:
1>lld-link : error : undefined symbol: __declspec(dllimport) SHCreateMemStream
1>>>> referenced by <solution dir>\ClipShim\clip\clip_win_wic.h:170
1>>>> clip.dir\Debug\clip_win.obj:(bool __cdecl clip::win::read_png(unsigned char const *, unsigned int, class clip::image *, struct clip::image_spec *))
1>lld-link : error : undefined symbol: __declspec(dllimport) SHCreateMemStream
1>>>> referenced by <solution dir>\ClipShim\clip\clip_win_wic.h:170
1>>>> clip.dir\Debug\clip_win.obj:(bool __cdecl clip::win::read_png(unsigned char const *, unsigned int, class clip::image *, struct clip::image_spec *))
canton7
canton7•2y ago
Looks like you're not linking whatever library provides those functions?
Up
Up•2y ago
possibly, but that seems to be some windows builtin stuff? yeah.. it's in Shlwapi.h, which is part of the windows SDK
canton7
canton7•2y ago
Yep, which you'll need to link
Up
Up•2y ago
HOW intellisense literally finds it and thinks it's valid, but the compiler doesn't
canton7
canton7•2y ago
It'll be getting that from the header: that's the only place which holds the signature info anyway. It's not until the linker kicks in that it realises it can't actually find a symbol with that name anywhere This is fairly fundamental c/c++ stuff - you'll get better help on the c/c++ discord I think
Accord
Accord•2y ago
✅ This post has been marked as answered!