⬆️ ⬇️

We use Unmanaged C ++ code in .NET programs

image



Today, I (like many other programmers, probably), increasingly use the .NET platform and the C # language in my developments, but there are still places where the use of C ++ is justified. This creates the need for their integration.



What for?

These are just the main reasons, the list is far from complete. But if there is a need, then there are solutions.

')

Before habrakat, I will say at once that the article will tell and show one, not quite classical way of such interaction, which can be extremely useful in many real-world applications.



How?

Each of these solutions has its pros and cons. The purpose of this article was not to give an overview of all the advantages and disadvantages of these approaches, however, I briefly touch them.



P / Invoke - very simple, but not flexible. There is no support for OOP, it is not convenient to endure many methods at once.



COM is convenient and quite powerful, but COM libraries require registration in the system registry, and, in addition, administrator rights are required for installation (UPD: as is rightly noted in the comments, not necessarily).



C ++ / CLI is a very tight integration, a pretty good solution. But if you cannot compile your C ++ code with the / CLI key, then you will need to enter an additional intermediate C ++ / CLI library and write wrappers for each call. In addition, CLI often complicates code and has some limitations.



How else?



In this article, I want to talk about a different, interesting and very elegant solution, based on COM Interop + P / Invoke, and allowing you to easily call C ++ classes from C # without needing their COM registration in the system (your programs can be launched portable, for example, from a flash drive). To do this, we will create the most common C ++ classes with virtual methods and transfer them to C # via P / Invoke as COM interfaces.



Let's get straight to the point.



We create two projects. One is C ++ (without using CLR and MFC, including unicode). Let it be called for example Lib.



image

(note, for ease of debugging, and to avoid the need for further copying, the output library is collected in the Windows folder).



After that we create a C # project - Windows Forms. Let's call it net2c. We collect the test form:



image



From the interface, I think it is already clear what our C ++ library will do about.



Now we return to the C ++ project and write a single function that will create our objects:



//

enum EObjectType

{

Hello = 0,

GraphicAlgorithm

};

__declspec(dllexport) void * __stdcall Create(EObjectType AType)

{

if (AType == Hello) return new CHello();

if (AType == GraphicAlgorithm) return new CGraphicAlgorithm();

return NULL;

}






To avoid decorating the names of exported functions, you need to create another .DEF file and write in it:

EXPORTS

Create




As an alternative, to create the necessary objects you can make a separate single class that will be returned by this function, but I will go the simplest way.



Now the code for the CHello class:

//

// {08356CFE-A3DD-43c2-980C-1393E37118B2}

static const GUID IID_IHello =

{ 0x8356cfe, 0xa3dd, 0x43c2, { 0x98, 0xc, 0x13, 0x93, 0xe3, 0x71, 0x18, 0xb2 } };

class IHello : public CBaseUnknown

{

public :

STDHRESULTMETHOD SetName(LPWSTR AName) = 0;

STDHRESULTMETHOD Say(HWND AParent) = 0;

};



class CHello :

public IHello

{

public :

STDHRESULTMETHOD QueryInterface( const CLSID &AId, void ** ARet)

{

__super::QueryInterface(AId, ARet);

if (AId == IID_IHello) *ARet = (IHello*) this ;

return (*ARet != NULL) ? S_OK : E_NOINTERFACE;

}

STDHRESULTMETHOD SetName(LPWSTR AName)

{

mName = AName;

return S_OK;

}

STDHRESULTMETHOD Say(HWND AParent)

{

wstring message = L "Hello, my friend " + mName;

MessageBox(AParent, message.c_str(), L "From C++" , MB_ICONINFORMATION);

return S_OK;

}

private :

wstring mName;

};




As you can see, everything is very simple. The class supports one single IHello interface and implements a pair of elementary methods.



Now the most interesting, go to the C # project and write:

// enum C#

public enum EObjectType : int

{

Hello = 0,

GraphicAlgorithm

}

[InterfaceType(ComInterfaceType.InterfaceIsIUnknown),

Guid ( "08356CFE-A3DD-43c2-980C-1393E37118B2" )]

public interface IHello

{

void SetName([MarshalAs(UnmanagedType.LPWStr)] string AName);

void Say( IntPtr AParent);

}

public class Lib

{

//

[DllImport( "Lib.dll" )]

protected static extern IntPtr Create(EObjectType AType);

//

public static object CreateObject(EObjectType AType)

{

IntPtr ptr = Create(AType);

return Marshal.GetObjectForIUnknown(ptr);

}

}






Again, nothing complicated, except perhaps Marshal.GetObjectForIUnknown . This function accepts an IntPtr pointing to a COM object and returns a System.Object for it. Separately, I would like to draw your attention to how the IHello interface is declared . Here we tell the compiler that this is a COM interface, and we write the same GUID as in the C ++ program.



Everything! Now C ++ methods of the CHello class can be called from C # with absolutely no problems, and not even remember what is behind this evil and terrible C ++. See for yourself:



object hiObject = Lib.CreateObject(EObjectType.Hello);

IHello hello = hiObject as IHello;

hello.SetName(txtName.Text);

hello.Say(Handle);






image

(everything really works)



For dessert



Now the second bonus part is a pair of the simplest graphical algorithms. This is a more realistic example. When processing large images, the code of the algorithm is really advisable to put in the C ++ library.



C ++ file

STDMETHODIMP CGraphicAlgorithm::QueryInterface( const CLSID &AId, void ** ARet)

{

__super::QueryInterface(AId, ARet);

if (AId == IID_IGraphicAlgorithm) *ARet = (CBaseUnknown*) this ;

return (*ARet != NULL) ? S_OK : E_NOINTERFACE;

}

STDMETHODIMP CGraphicAlgorithm::MakeGrayScale( void * APointer, int AWidth, int AHeight)

{

RGBQUAD *p = (RGBQUAD*)APointer;

for ( int y = 0; y < AHeight; y++)

{

for ( int x = 0; x < AWidth; x++)

{

// :)

short mid = (( short )p->rgbBlue + p->rgbGreen + p->rgbRed) / 3;

if (mid > 255) mid = 255;

BYTE v = (BYTE)mid;

memset(p, v, 3);

p++;

}

}

return S_OK;

}

STDMETHODIMP CGraphicAlgorithm::MakeAlpha( void * APointer, int AWidth, int AHeight)

{

RGBQUAD *p = (RGBQUAD*)APointer;

for ( int y = 0; y < AHeight; y++)

{

for ( int x = 0; x < AWidth; x++)

{

// , :)

memset(p, p->rgbReserved, 4);

p++;

}

}

return S_OK;

}




H file

// {65ACBBC0-45D2-4622-A779-E67ED41D2F26}

static const GUID IID_IGraphicAlgorithm =

{ 0x65acbbc0, 0x45d2, 0x4622, { 0xa7, 0x79, 0xe6, 0x7e, 0xd4, 0x1d, 0x2f, 0x26 } };

class CGraphicAlgorithm : CBaseUnknown

{

public :

STDHRESULTMETHOD MakeGrayScale( void * APointer, int AWidth, int AHeight);

STDHRESULTMETHOD MakeAlpha( void * APointer, int AWidth, int AHeight);

STDHRESULTMETHOD QueryInterface( const CLSID &AId, void ** ARet);

};






Separately, I draw your attention to the fact that there is IID_IGraphicAlgorithm , but the interface IGraphicAlgorithm itself , as such, no. How so? This was done specifically to simplify our work with you even more and to write even less code. The only thing that is important to consider here is that all virtual methods that relate to the IGraphicAlgorithm interface must go at the beginning of the class and strictly in order. In addition, then the class will not be able to provide several different interfaces (which we do not need, for this it is more convenient and more logical to make another class).



We return to Sharp and write this code:

IGraphicAlgorithm utils = Lib.CreateObject(EObjectType.GraphicAlgorithm) as IGraphicAlgorithm;



BitmapData data = bmp.LockBits( new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format32bppArgb);



if (optGray.Checked)

utils.MakeGrayScale(data.Scan0, data.Width, data.Height);

else

utils.MakeAlpha(data.Scan0, data.Width, data.Height);



bmp.UnlockBits(data);

picDest.Image = bmp;

picSrc.Refresh();

picDest.Refresh();




Here we first get a pointer to the memory where the bitmap data is stored, and then we give this pointer to the C ++ library for further processing. Immediately I warn you that this is not the most effective and convenient way yet, but if you are interested in the topic, then I can talk about direct work with images in memory in a separate article.



As a result:

image



Results



See what we did:

PS Full source codes can be downloaded here: http://66bit.ru/files/paper/net2c/net2c.zip

PPS The next article will be about the inverse problem - the direct use of C # libraries in C ++



UPD. The second part came out

Source: https://habr.com/ru/post/101589/



All Articles