Sử dụng mã C++ trong dự án C# phần 2
In the previous blog of unmanaged dll (here), I created DLL from unmanaged C++ codes and used dllimport/dllexport to import/export unmanaged C++ functions for uses in C#. If you want to use just a few functions from the unmanaged codes, using dllimport/dllexport is just fine. But if there are a large number of unmanaged C++ functions that you want to use in C#, you may have to consider using C++/CLI instead.

CLI stands for Common Language Infrastructure and C++/CLI is a new .NET language that is similar to C++ in some aspects. In Microsoft Visual Studio, C++/CLI is refered to as CLR and you can find CLR installed templates under File > New > Project Visual C++ category.

C++/CLI may be somehow called managed C++ (with many additional syntaxes). Before you decide to learn C++/CLI, make sure that it gonna worth your time as this means learning a new programming language. From my survey, major advantages of C++/CLI are: (below info are from binglongx.wordpress and stackoverflow.com)

  • C++/CLI can mix managed C++ with unmanaged C++.
  • C++/CLI can transform (wrap) native C++ to managed codes (as .NET assemblies) so that you can share it with .NET world, including C#, VB, etc.
  • The other approach (as mentioned in my previous blog) is to use P/Invoke for unmanaged C DLLs. However, you have to wrap your C++ library into flat pure C DLLs; all classes are gone anyway.

My quick conclusion is that go to C++/CLI if your codes strongly need to interop (inter-operate) with unmanaged codes. If your goal is to create console or window-based applications using managed languages, don’t bother learning C++/CLI and go to VB or VC# instead.

Now that you decide to dig deeper in C++/CLI, let’s know it a bit more in this article — A first look at C++/CLI. One main concept is to use handle (^) instead of pointer (*). Handle refers to object created by gcnew keyword and stored in the heap; hence, that object will be managed by Garbage Collection (gc). On the other hand, C++’s old style pointer refers to object created by new keyword; the pointer just does pointing to a memory, hence, it needs our help to do memory management.

// C++ pointer
String *str = new String("Hello world");
...
free(str);

// C++/CLI handle
String ^str = gcnew String("Hello world");

Well, let end our “Intro to C++/CLI” class here and back to the article — how to wrap unmanaged C++ codes with a CLR wrapper and use it in C#. From this point on, the contents are based on the below video tutorial of Marek Kolman, thank a lot.

As an outline, there are three projects involved in this: (these projects do not share the same solution space)

  • VC++ project as the main computing class. This project contains unmanaged C++ codes (i.e., *.h and *.cpp) that do all computations.
  • VC++ project as the CLR wrapper class. This project is a CLR project (managed codes) that creates wrapper for functions or methods in the first VC++ project. The outcome of this project is a DLL file to be used by VC#.
  • VC# project as a testing project that tries using the unmanaged C++ functions via calling their CLR wrappers.

:::::::: STEP 1 : Create a VC++ project of the main computing class ::::::::

1 ) Create a new project by choosing File > New > Project > Visual C++ > Win32 Console Application > Application Settings > Application type: Console Application, Additional options: Empty project > click Finish button.

2 ) Add a header file of your computing class to the project. For example, MathFuncs.h containing:

#pragma once

#include <stdexcept>
using namespace std;

class MyMathFuncs 
{
public:
   double Add(double a, double b);
   double Subtract(double a, double b);
   double Multiply(double a, double b);
   double Divide(double a, double b);
};

3 ) Add a C++ source file of your computing class to the project. For example, MathFuncs.cpp containing:

#pragma once
#include "MathFuncs.h"

double MyMathFuncs::Add(double a, double b)
{ return a+b; }

double MyMathFuncs::Subtract(double a, double b)
{ return a-b; }

double MyMathFuncs::Multiply(double a, double b)
{ return a*b; }

double MyMathFuncs::Divide(double a, double b)
{
    if ( b == 0 )
       throw invalid_argument("b cannot be zero!");
}

Note: the directive #pragma once cannot be omitted here. Otherwise, the error saying error C2084: function ‘double MyMathFuncs::XXX(double,double)’ already has a body will appear during STEP 2.

4 ) Add another C++ source file to the project. This file is just for testing the created class and will not be used in the further steps. For example, main.cpp containing:

#include <stdio.h>
#include "MathFuncs.h"

using namespace std;

int main()
{
    MyMathFuncs *m = new MyMathFuncs();
    double c = m->Add (10.1,20.2) ;

    printf("Result is %f\n", c);
    free(m);
    return 0;
}

5 ) Build and run the project to ensure that no error exists.

:::::::: STEP 2 : Create a VC++ project of the CLR wrapper class ::::::::

1 ) Create a new project by choosing File > New > Project > Visual C++ > CLR > Class Library. And you will find many files including managedDllWrapper.h and managedDllWrapper.cpp in Solution Explorer panel.

Note: managedDllWrapper represents the name of this project you just created.

2 ) In managedDllWrapper.h, add the following codes: (the codes printed in blue and red are those added or edited by me)

// managedDllWrapper.h

#pragma once

#include "C:\Projects\managedDll\managedDll\MathFuncs.h"
#include "C:\Projects\managedDll\managedDll\MathFuncs.cpp"

using namespace System;

namespace CppWrapper {
    public ref class MyMathFuncsWrapper
    {
    public:
       // constructor
       MyMathFuncsWrapper();

       // wrapper methods
       double AddWrapper ( double a, double b);
       double SubtractWrapper ( double a, double b);
       double MultiplyWrapper ( double a, double b);
       double DivideWrapper ( double a, double b);

       // public variable
       double initVal;

    private:
       MyMathFuncs *myCppClass; // an instance of class in C++
    };

}

Note: C:\Projects\managedDll\managedDll\MathFuncs.h and C:\Projects\managedDll\managedDll\MathFuncs.cpp are files created in STEP 1.

3 ) In managedDllWrapper.cpp, add the following codes: (the codes printed in blue are those added or edited by me)

// This is the main DLL file.

#include "stdafx.h"
#include "managedDllWrapper.h"

#include "C:\Projects\managedDll\managedDll\MathFuncs.h"
#include "C:\Projects\managedDll\managedDll\MathFuncs.cpp"

// Constructor implementaion
CppWrapper::MyMathFuncsWrapper::MyMathFuncsWrapper()
{ 
    initVal = 20.0; 
    myCppClass = new MyMathFuncs(); //initiate C++ class's instance
}

double CppWrapper::MyMathFuncsWrapper::AddWrapper ( double a, double b)
{   return myCppClass->Add(a,b);   }

double CppWrapper::MyMathFuncsWrapper::SubtractWrapper (double a, double b)
{   return myCppClass->Subtract(a,b);   }

double CppWrapper::MyMathFuncsWrapper::MultiplyWrapper (double a, double b)
{   return myCppClass->Multiply(a,b);   }

double CppWrapper::MyMathFuncsWrapper::DivideWrapper (double a, double b)
{   return myCppClass->Divide(a,b);   }

4 ) Build either Debug or Release version, or both, of the project and you should get the DLL(s) ready to be used in other .NET environment.

:::::::: STEP 3 : Create a VC# project for testing your CLR wrapper ::::::::

1 ) Create a new VC# project and add your preferred UIs. Or create a simple console application.

2 ) Solution Explorer panel > References > right click and choose Add Reference > in Browse tab, browse for the DLL created in STEP 2 > click OK button. Now the name managedDllWrapper should be listed under References section in Solution Explorer panel.

Note: if your DLL is not a managed one, you cannot do this Add Reference. Otherwise, there will be an error saying

A reference to xxx dll could not be added. Please make sure that the file is accessible and that it is a valid assembly or COM component.

3 ) Add your namespace to the C# project (by using directive) and try calling your wrapper functions. For example: (codes related to this article are written in blue bold characters)

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using CppWrapper;

namespace testCSharpInterface
{
    public partial class Form2 : Form
    {
       MyMathFuncsWrapper mymath = new MyMathFuncsWrapper();
       private double c;

       public Form2()
       {
           InitializeComponent();
           c = mymath.initVal;  // access public variable
       }

       private void button1_Click(object sender, EventArgs e)
       {
           c = mymath.AddWrapper( c, c );
           label1.Text = c.ToString() ;
       }
    }
}

4 ) Your task is done now, without dllimport and public static extern ..blah blah blah.. chunk of codes.

:::::::: CONCLUSION ::::::::

Compare with the solution using pinvoke for unmanaged DLLs, you may see that your C# codes are now cleaner and your wrapped functions are much easier to be distributed and used by any anonymous. Your friend should be happy to use your DLL but it is you, who need to create wrappers for them.

Now it’s your decision, which one to use? Unmanaged or managed DLL? Personally, I prefer using CLR for codes that I plan to use again-and-again in the long run or plan to distribute its DLL to my friends. But for codes that I plan to use just once or twice, I gonna use pinvoke as it is easier, no wrapper and no C++/CLI.

Nguồn: https://drthitirat.wordpress.com/

10/22/2020 10:00:05 PM