ACS
ACS

Reputation: 39

failed to run unmanaged cpp unit test for managed code

I have the following C# code(sample.dll).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Sample
 {  
     public class Worker
     {
  /// <summary>
  /// Callback method signature to send array of values.
  /// </summary>
  /// <param name="values">The values.</param>
  public delegate void FloatValuesReady(float[] values);

  /// <summary>
  /// Occurs when [float values are ready].
  /// </summary>
  public event FloatValuesReady ReadFloatValues;

  /// <summary>
  /// Sums the specified i.
  /// </summary>
  /// <param name="i">The i.</param>
  /// <param name="j">The j.</param>
  /// <returns></returns>
  public int Sum(int i, int j)
  {
     return i + j;
  }

  /// <summary>
  /// Gets the Student object.
  /// </summary>
  /// <returns></returns>
  public ManagedStudent GetStudent()
  {
     Console.WriteLine("Enter a Name:");
     string text = Console.ReadLine();
     if (text != string.Empty)
     {
        return new ManagedStudent { Name = text };
     }
     return new ManagedStudent { Name = "NoName" };
  }

  /// <summary>
  /// Read some float values from Console and give it back to the caller using ReadFloatValues callback.
  /// So Caller should add event handler to ReadFloatValues event.
  /// </summary>
  public void GetSomeFloatValues()
  {
     List<float> values = new List<float>();
     Console.WriteLine("Enter 4 valid float values for the Native App");
     while (values.Count < 4)
     {
        string valueText = Console.ReadLine();
        float value;
        if(float.TryParse(valueText, out value))
        {
           values.Add(value);
        }
     }
     if (this.ReadFloatValues != null)
     {
        this.ReadFloatValues(values.ToArray());
     }
  }
  }
  /// <summary>
  /// A Managed Class
  /// </summary>
 public class ManagedStudent
 {
    public string Name { get; set; }
 }
}

I have created C++ wrapper for the above sample.dll to acess the managed code in unmanaged code(wrapper.dll). Below is the code for SampleWrapper.h,SampleWrapper.cpp,NativeInterface.h.

/*Sample Wrapper.h*/
using namespace System;
using namespace Sample;
namespace Wrapper
{
  public ref class SampleWrapper
  {
  /* Private Constructor to achieve signle ton*/
    SampleWrapper(void)
    {
        workerObj = gcnew Worker();
     workerObj->ReadFloatValues += gcnew Worker::FloatValuesReady(this, &Wrapper::SampleWrapper::FloatArrayReadyMethod);
    }
public:
    Worker ^ workerObj;
  /* Static reference to single ton instace.
     In order to use applications built in C.*/
    static SampleWrapper ^ Instance = gcnew SampleWrapper();
  void FloatArrayReadyMethod(array<float> ^ values);
    };
  }


  /*NativeInterface.cpp/*
 #ifdef __cplusplus
 extern "C"
{
   #endif
/* Un managed type definition of student equalent to the Managed*/
 typedef struct
 {
  char* name;
 }UnManagedStudent;

   /* A simple interface using the premitive types. Accepts 2 paramters      and retun*/
  __declspec(dllexport) int SumFromCSharp(int i, int j);

  /* An interface to get the Student Information in a Structure.
  This function calls the C# class method and gets the managed Student Object
  and converts to unmanged student*/
__declspec(dllexport) UnManagedStudent GetStudent();

   /* Function pointer to a native function to achieve call back*/
   typedef void (*GetFloatArrayCallback) (float values[], int length);

     /* An interface that makes call to C# to read some float values.
  The C# worker respond back in event call back.
  In order to pass the information back to the native app 
  it should give the callback pointer*/
  __declspec(dllexport) void   GetFloatArrayFromCSharp(GetFloatArrayCallback cb);

  #ifdef __cplusplus
      }
      #endif

/*sampleWrapper.cpp*/

#include "SampleWrapper.h"
#include "NativeInterface.h"
using namespace Sample;

   namespace Wrapper
    {
    #ifdef __cplusplus
    extern "C"
    {
     #endif
  void copyManagedStringToCharPointer(char target[], System::String ^  inputString)
    {
    int maxSize = inputString->Length;
    if ( maxSize > 0) 
   {
     for (int index = 0; index < maxSize; ++index ) 
     {
        target[index] = (char)inputString->default[index];
     }
     target[maxSize] = '\0';
  }
  }

  void copyManagedFloatToUnfloatArray(float target[], array<float> ^ values)
  {
  int maxSize = values->Length;
  if ( maxSize > 0) 
  {
     for (int index = 0; index < maxSize; index++ ) 
     {
        target[index] = (float)values[index];
     }
  }
   }

 __declspec(dllexport) int SumFromCSharp(int i, int j)
  {
    Worker ^ worker = SampleWrapper::Instance->workerObj;
    return worker->Sum(i,j);
   }

  __declspec(dllexport) UnManagedStudent GetStudent()
    {
    Worker ^ worker = SampleWrapper::Instance->workerObj;
    ManagedStudent ^ studentObj = worker->GetStudent();
  String ^ mName = studentObj->Name;
  UnManagedStudent studentStruct;
  studentStruct.name = new char[mName->Length];
  copyManagedStringToCharPointer(studentStruct.name,mName);
  return studentStruct;
  }

    GetFloatArrayCallback floatArrayCallback;
  __declspec(dllexport) void GetFloatArrayFromCSharp(GetFloatArrayCallback cb)
  {
   floatArrayCallback = cb;
  Worker ^ worker = SampleWrapper::Instance->workerObj;
  worker->GetSomeFloatValues();
  }

  void SampleWrapper::FloatArrayReadyMethod(array<float> ^ values)
  {
  float *nativeValues = new float[values->Length];
  copyManagedFloatToUnfloatArray(nativeValues, values);
  floatArrayCallback(nativeValues, values->Length);
  }

  #ifdef __cplusplus
   }
 #endif
   }

then created small application which will access managed code through unamanaged code like below(native.dll).

 #include "DemoNativeDll.h"
  #include "NativeInterface.h"

 using namespace nsNativeDll;

CDemoNativeDll::CDemoNativeDll(void)

{ }

   CDemoNativeDll::~CDemoNativeDll(void)
   {
  }

   int CDemoNativeDll::GetSum(int value1, int value2)
  {
  return SumFromCSharp(value1,value2);
  }

Then i have created unit case for Native.dll as below

#include "stdafx.h"
#include "CppUnitTest.h"
#include "..\NativeDll\DemoNativeDll.h"
#include "..\Wrapper\NativeInterface.h"

using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace nsNativeDll;

namespace UnitTest1
{       
   TEST_CLASS(UnitTest1)
  {
   public:

    TEST_METHOD(TestMethod1)
    {
        CDemoNativeDll* obj = new CDemoNativeDll();
        int sum = obj->GetSum(10,15);

        Assert::AreEqual(sum,15);
        // TODO: Your test code here
     }

  };
   }

I am getting the EEFileLoadException exception. How to reslove this exception. I am using Visual Studio 2012 cppunit.

Upvotes: 0

Views: 330

Answers (1)

Hans Passant
Hans Passant

Reputation: 941317

  __declspec(dllexport) void   GetFloatArrayFromCSharp(GetFloatArrayCallback cb);

Using __declspec(dllexport) is a simple way to expose managed code to native code. But it is too simple, there is no reasonable way to detect any mishap in the managed code. Which likes throwing exceptions when anything goes wrong. All you can see is an "it didn't work" exception without any way to find out anything about the managed exception. No exception message, no access to the Holy Stack Trace.

You are getting a preview of how hard it is going to be to support your customer. Who will call you and tell you "it did not work". Nothing you can do to help unless you can get a debugger running on his machine. That tends to be hard to come by.

Well, you have one on yours, you can at least use it to diagnose the unit test failure. Project + Properties, Debugging, change the Debugger Type setting from "Auto" to "Mixed". That enables the managed debugging engine, you can now look at the exception details. Usually a bog-standard one, "File not found" is a very common mishap. Especially since the CLR has no good reason to look for the managed assembly in the directory where it is stored right now, configuring the primary appdomain is something else you can't do. You probably have to put it in the GAC or move the cppunit test runner so it is in the same directory as your DLLs. Fuslogvw.exe is a good utility to diagnose assembly resolution problems, it shows you where the CLR looked for the assembly and what config it uses.

But feel free to put this unit test result into the "major fail" category. Consider getting ahead with COM interop or CLR hosting, they both give you an HRESULT error code and support IErrorInfo to obtain the exception message.

Upvotes: 1

Related Questions