19
votes

Here's my problem. I am wrapping a C dll in C#. To do this, I am first writing a C++/CLI wrapper. The native C library is linked to the C++/CLI wrapper. (Linker properties in C++/cli project).

Here's how it is all organised now: - The native C .lib: both x86 and 64bit.

  • 1 solution containing 2 projects:
    • C++/CLI wrapper project to which is linked native C .lib
    • C# project referencing C++/CLI project

My problem comes from the fact that I need C# to target "Any CPU". But this option is not available in C++/CLI since it compiles directly to native code.

My idea to solve this is: - Compile C++/CLI wrapper in x86 and then change the config and compile to 64 bit. When it compiles, I would like to tell it which dll to take based on the platform. ie: if compiling in 64bit, link 64 bit native C dll, else if x86, link x86 native C. - With this done, I should then be able to have Any CPU target in my C# platform. Here again, instead of referencing my C++/CLI wrapper project, I would reference the required dll based on the target platform.

My questions are:

  • How to I tell the C++/CLI project which .lib to link to based on the target platform?
  • How to I tell the C# project which C++/CLI dll to reference based on the target platform?

Let me add that the C# project a CLASS LIBRARY to be used by an x86 or x64 client.

I hope my question is clear enough. Any helps would be appreciated !

UPDATE based on:Conditional references in .NET project, possible to get rid of warning?...

So now I've edited my .csproj file using a condition to reference the dll as follows:

<ItemGroup>
    <Reference Include="AlibCppWrapper, Version=1.0.4303.21410, Culture=neutral, PublicKeyToken=c0c17a53adc44091, processorArchitecture=AMD64"
               Condition="$(Platform) == 'x64'">
      <SpecificVersion>False</SpecificVersion>
      <HintPath>..\x64\Debug\AlibCppWrapper.dll</HintPath>
    </Reference>
    <Reference Include="AlibCppWrapper, Version=1.0.4303.21410, Culture=neutral, PublicKeyToken=c0c17a53adc44091, processorArchitecture=x86"
               Condition="$(Platform) == 'x86'">
      <SpecificVersion>False</SpecificVersion>
      <HintPath>..\Debug\AlibCppWrapper.dll</HintPath>
    </Reference>
  </ItemGroup>

Unfortunately this doesn't work as the $(Platform) is set to AnyCPU...

2
I don't see a question. If you want to target x64 and x86 you need to compile seperate files for each platform (C/C++) and reference the correct library within your C# application depending on which platform it is. You could also decide you just want to force a certain platform by using the reference to a x86 or x64 compiled library. I don't see a '?' feel free to edit your question and make your question clear. - Security Hound
Is the "Any CPU" a hard requirement? The lazy programmer's solution to this is just to compile for x86 since it runs in x64. - Ilian
yes it is a hard requirement. The C# project is actually a library to be used by a client and server that can be either installed on a 64 bit or on x86 - nche
This is a deployment problem. The native DLLs are not a problem, you can give them different names and specify them in the linker's Additional Dependencies setting. The rub is the C++/CLI assembly. The CLR can automatically pick the right assembly based on the processorArchitecture attribute but that requires deploying them to the GAC. Which requires two installers. Since you need them anyway, the simple solution is to just provide two different installers, one for a 32-bit machine and another for a 64-bit machine. Each deploying the correct flavor of the DLLs. - Hans Passant
@IlianPinzon - If I have my C# library as x86 then it cannot be called by a client program compiled on a x64 architecture with Any CPU... And changing this client to x86 is not possible. - nche

2 Answers

22
votes

What you describe is known as "side-by-side assembly" (two versions of the same assembly, one 32 and the other 64 bit)... I think you will find these helpful:

EDIT - as per comment:

Here you can find a walkthrough for exactly your scenario: .NET DLL wrapping C++/CLI DLL referencing a native DLL

1
votes

For me solution was following:

  1. Build x64 bit version of C++ library to output file xxx.x64.dll

  2. Build x86 bit (Win32 platform) version of C++ library to output file xxx.x86.dll

  3. Add them as content files to my wrapper C# project. Ex.:

    <ItemGroup>
      <Content Include="..\..\$(Configuration)\xxx.x86.dll" Link="xxx.x86.dll">
        <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      </Content>
      <Content Include="..\..\$(Configuration)\xxx.x64.dll" Link="xxx.x64.dll">
        <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      </Content>
    </ItemGroup>
    
  4. In C# Import functions from both x86 and x64 library versions. Ex.:

        [DllImport("xxx.x86.dll", EntryPoint = "FunctionName", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
        public static extern void FunctionNamex86();
        [DllImport("xxx.x64.dll", EntryPoint = "FunctionName", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
        public static extern void FunctionNamex64();
    
  5. In C# implement function that calls correct overload based on current platform. Ex.:

        public static void FunctionName()
        {
            if (Environment.Is64BitProcess)
                FunctionNamex64();
            else
                FunctionNamex86();
        }
    
  6. Now C# project can be built as "Any CPU" and used by other multiplatform projects.

  7. To distribute it as NuGet package, I use following NuSpec configuration:

    <?xml version="1.0"?>
    <package>
        <metadata>
            <contentFiles>
                <files include="any/any/xxx.x64.dll" buildAction="None" copyToOutput="true" />
                <files include="any/any/xxx.x86.dll" buildAction="None" copyToOutput="true" />
            </contentFiles>
        </metadata>
        <files>
            <file src="Release/C#Wrapper.dll" target="lib" /> 
            <file src="Release/xxx.x64.dll" target="content" /> 
            <file src="Release/xxx.x86.dll" target="content" /> 
            <file src="Release/xxx.x64.dll" target="contentFiles/any/any" /> 
            <file src="Release/xxx.x86.dll" target="contentFiles/any/any" /> 
        </files>
    </package>
    

Answer is mostly based on: Using a 32bit or 64bit dll in C# DllImport