For example, to build the dynamic library in Example 1-2, you need to define the. CodeWarrior 10.0 for Mac OS X will provide dynamic variants of its runtime. Most linkers create import libraries automatically when you build a DLL, but in.
by Dan Belcher (Last modified: 05 Dec 2018)
This guide demonstrates how to wrap a C/C++ library in order to call into it from .NET.
We present a sample solution that uses Platform Invoke (PInvoke), which allows .NET code to call functions that are implemented in a C/C++ DLL (Windows) or dylib (macOS). It is important to understand that the main utility of this advanced approach is in the ability to call the same C/C++ code on both platforms from .NET.
First, we will build a simple C/C++ library that adds two numbers together. After that, we will examine the wrapping .NET code required to call into our library from a RhinoCommon plugin on both Windows and Mac.
This guide does not presume you are a C/C++ or .NET expert, but assumes you have a functional working knowledge of both. This is an advanced guide; that said, the intent of this guide is to illustrate basic considerations of wrapping a C/C++ library and the logistical issues calling it from a RhinoCommon plugin on both Windows and Mac.
We will be analyzing a sample solution called SampleNativeLibrary. Please clone or download this repository. SampleNativeLibrary builds against the RhinoWIP (on Windows) and Rhino 5 for Mac (on macOS). (On Windows, it is possible to use Rhino 6, but you will have to change the RhinoCommon references).
It is presumed you already have all the necessary tools installed and are ready to go. If you are not there yet, see both Installing Tools (Windows) and Installing Tools (Mac). It is also helpful to have read and understood Your First Plugin (Cross-Platform).
Other methods to create a .NET binding to a C# library exist. A notorious one is based on the compilation of the C++ library with the C++/CLI compiler. To keep things compatible with the Apple macOS, and because the IJW (it just works) technology sometimes does not, we suggest the use of PInvoke.
Let’s begin by taking a look at an absurdly simple C/C++ “library” - SampleLibrary - that does one thing: add two numbers together. We’ll start on Windows, but it really doesn’t matter if you start on a macOS, nearly everything that follows applies on each platform…
Open the SampleLibrary.cpp file and take a look. Nearly all of the code in this file is auto-generated boilerplate. The only important section to pay attention to is:
…even if you are not a C/C++ programmer, this C function should be clear to you. Add
takes a native int
and a native double
, and returns the sum of the inputs as a native double
. Take note of the SAMPLELIBRARY_C_FUNCTION
decoration above the implementation…we will talk about that in a moment.
#defined
sections to this header: one that relates to Windows (#if defined (_WIN32)
) and one that relates to Mac (#if defined(__APPLE__)
). The code in these #defines are basically telling the linker to export functions in a specific way. The reason they are different on each platform is that Dynamic Link Libraries (DLLs) are implemented differently in Windows and macOS - each platform has a unique way of telling the linker what to do with the library. How this works is not really important at this juncture, just know that these MACROs tell the linker to export the functions. More importantly…Take a look at the function declaration at the bottom of the file:
…is decorated with the same SAMPLELIBRARY_C_FUNCTION
.
xcodebuild
from Visual Studio for Mac to build our native library, but we’ll talk about that below.Now that we have examined the simple native SampleLibrary, let’s turn our attention to the .NET portion of our wrapping code…
SampleNativeLibrary is the .NET project that calls into the SampleLibrary. On each platform, we are using the exact same wrapping source code, just using cloned .csproj projects on each platform. (For more information on this cross-platform strategy, see the Your First Plugin (Cross-Platform) guide).
Let’s take a look at SampleNativeLibrary on Windows using Visual Studio first…
Add
function is being called.Open SampleRhinoCommand.cs and find the RunCommand
method. After prompting the user to enter two numbers, we see the following code
Add(first, second)
method is being called on the UnsafeNativeMethods
class. Open UnsafeNativeMethods.cs. Let’s go through this class line-by-line.System.Runtime.InteropServices
namespace, specifically the DllImport
function.UnsafeNativeMethods.cs contains the class:
…which declares a lib
string member. On Windows, it is necessary to explicitly state the name of the native dll being called (on macOS, a .dll.config file is used to point to a .dylib - see below).
The UnsafeNativeMethods
class itself contains a single function…
2004a lcd arduino. …does this look familiar?
The Add
function is decorated with an Attribute:
…this important bit of metadata tells the runtime to look in the native library and call the associated function with a specific language (C) calling convention when the Add
method is called from .NET. This is the point at which the link between the managed .NET code and the unmanaged C/C++ code is established.
In UnsafeNativeMethods change the function declaration of Add
to accept double
s (instead of int
s) as the first argument…
a
back to an int
, rather than a double
and save the file.There is one critical difference between how the Windows and macOS wrapping works and it has little to do with code. As we saw above, UnsafeNativeMethods.cs contains the class:
…which declares a lib
string member. On Windows, it was necessary to explicitly state the name of the native dll being called. On macOS, this works a little differently. In order to understand what is different, let’s look at how SampleLibrary.dll gets built on macOS…
xcodebuild
command line from Visual Studio for Mac. Let’s take a look.xcodebuild
to build the SampleLibrary.xcodeproj, which builds libSampleLibrary.dylib.After Build 1: This after build step copies the SampleLibrary.dll.config file from the /SampleLibrary folder to the target folder. SampleLibrary.dll.config creates a mapping between calls to SampleLibrary.dll and libSampleLibrary.dylib…
On macOS, both the .dll.config file and the .dylib must be in the same folder as the calling .NET dll.
-o --overwrite
command argument if you want to build the native library every time you build the .NET wrapping dll.You have seen how a basic C/C++ library can be wrapped and called from .NET in a Rhino Plugin. You have also seen what can go wrong when a native method’s export declaration is out-of-sync with its .NET counterpart. Now what?
Check out the Using methodgen guide for instructions on programmatically generating UnsafeNativeMethods export declarations.