Integrate native C++ project with managed C# projects

Scenario: recently our team need to use a compression algorithm which is implemented in native C++ codes, while our product codes are C#. This article introduces my end-to-end experience of integrating the C++ project with C# projects.

Preliminary

Assume we have 5 projects:

  • A C++ project that implement a compression algorithm: A_Cpp.vcxproj
  • A C# interop project to interop with the C++ codes: B_Interop.csproj
  • A C# project that actually utilized the compression algorithm in local machine: C_Client.csproj
  • A C# WebRole/WorkerRole project that utilize compression in cloud: D_WebRole.csproj
  • A C# test/unittest project that test the functionality: E_Test.csproj

C++ project

  1. Set "Configuration Type" as "Dynamic Library (.dll)" to build a native binary A_Cpp.dll.
  2. Implement a wrapper in C++ to expose APIs that can be called from managed code.
    1. e.g.

                        extern "C"__declspec(dllexport) int Compress(UInt8 * inBuffer, unsignedintinBufferSize, UInt8 * outBuffer, unsignedint* outBufferSize)

C# interop project

  1. Create a C# interop project to interop with native code.
  2. Reference the C++ project to ensure A_Cpp.vcxproj is built when building B_Interop.csproj.
    1. Here is a trick that we directly do reference by editing the B_Interop.csproj, as we cannot reference a native project from C# project in Visual Studio.

    2. e.g. Add the followings:

        <ItemGroup>

          <ProjectReference Include="..\A_Cpp\A_Cpp.vcxproj">

            <Project>{<A GUID>}</Project>

            <Name>A_Cpp</Name>

          </ProjectReference>

        </ItemGroup>

  3. Create a class NativeMethods.cs to call native APIs.
    1. e.g.

      [DllImport("A_Cpp.dll", CallingConvention = CallingConvention.Cdecl)]

      unsafe internal static extern int Compress(byte* inBuffer, uint inBufferSize, byte* outBuffer, uint* outBufferSize);  

  4. Create another class to wrapper the APIs.
    1. e.g.

      unsafe public static byte[] Compress(byte[] inBuffer)

  5. Now the wrapped APIs can be called by any C# codes. This project will build the binary: B_Interop.dll

C# client project

  1. Reference project B_Interop.csproj to call Compress APIs.
  2. After that, when build C_Client.csproj, we can ensure B project is built and thus A project is built. Now a critical issue is that B_Interop.dll will be copied to this project's outDir, but A_Cpp.dll cannot be copied as it is not a "formal" reference, which will lead to DllNotFoundException in run time.
  3. Add a target in C_Client.csproj to ensure A_Cpp.dll is copied.
    1. e.g.

        <Target Name="CopyACppDll" AfterTargets="Build">

          <Copy SourceFiles="$(OutDir)..\A_Cpp\A_Cpp.dll" DestinationFolder="$(OutDir)" />

        </Target>

C# web role project

  1. Reference project B_Interop.csproj to call Compress APIs.
  2. For web role project, copy the cpp dll to this project's outDir won't work, as the codes are actually executed in Cloud. Only copy the dll to the Azure service package will work.
  3. Add an item in D_WebRole.csproj to copy the cpp dll to package.
    1. e.g.

         <None Include="$(OutDir)..\A_Cpp\A_Cpp.dll">

            <Link>A_Cpp.dll</Link>

            <CopyToOutputDirectory>Always</CopyToOutputDirectory>

          </None>

C# test project

  1. Reference project B_Interop.csproj to test the functionality.
  2. For test project, copy the cpp dll to this project's outDir won't work, it seems the codes are actually executed in folder "TestResults".
  3. Add the following attribute for TestMethod/TestClass.
    1. e.g.

      [DeploymentItem(@"..\..\bin\x86\Debug\A_Cpp\A_Cpp.dll")]

References

  1. Walkthrough: Creating and Using a Dynamic Link Library
  2. Platform Invoke Tutorial
  3. Adding Files to your Windows Azure Service Package