Use PInvoke interoperability to complement the pleasant interactive benefits of C#and C++.

One: Background

1. Storytelling

If you often look at the source code of the FCL, you will find that there are many ways to make C# faster and more powerful by using the power of C/C++, as follows:

	[DllImport("QCall", CharSet = CharSet.Unicode)]
	[SecurityCritical]
	[SuppressUnmanagedCodeSecurity]
	private static extern bool InternalUseRandomizedHashing();

	[DllImport("mscoree.dll", EntryPoint = "ND_RU1")]
	[SuppressUnmanagedCodeSecurity]
	[SecurityCritical]
	public static extern byte ReadByte([In] [MarshalAs(UnmanagedType.AsAny)] object ptr, int ofs);

Associated with the previous Ali SMS netsdk is also implemented in C++, and then use C# as a shell, both of which complement each other to show greater power, and many friends who are doing the Internet of Things are too familiar with this.Net interoperability technology. Many hardware and video device drivers are implemented in C/C++, and then use winform/WPF as the management interface. C++ has also been university, manyI haven't touched them in years. In order to practice this article, I use P/Invoke to connect them.

2: PInvoke Interoperability Technology

1. Some Pre-Foundation

Here I use vs2019 to create C++ C onsole App and modify two configurations: exporting the program to dll and compile to Compile as C++ Code (/TP).

2. Basic types of interoperability

Simple type is the best treatment, basically int,long, double are one-to-one correspondence, here I use C++ to implement simple Sum operation, drawing a sketch is like this:

Create a new cpp file and an h header file, as follows.

--- Person.cpp

extern "C"
{
	_declspec(dllexport) int Sum(int a, int b);
}


--- Person.h

#include "Person.h"
#include "iostream"
using namespace std;

int Sum(int a, int b)
{
	return a + b;
}

One thing to note is that extern "C" must be exported in C mode. If you follow C++ mode, the name of Sum will be automatically modified by the compiler. Don't trust you to remove extern "C". I'll open it with ida to show you. It has been modified to? Sum@@YAHHH@Z, embarrassing.

Next, copy the ConsoleApplication1.dll generated by the C++ project into the bin directory of C#as follows:

    class Program
    {
        [DllImport("ConsoleApplication1.dll", CallingConvention = CallingConvention.Cdecl)]
        extern static int Sum(int a, int b);

        static void Main(string[] args)
        {
            var result = Sum(10, 20);

            Console.WriteLine($"10+20={result}");

            Console.ReadLine();
        }
    }

---- output -----

10+20=30

2. Interoperability of strings

We know that managed and unmanaged code are two worlds, which involve type mapping between the two worlds. Where can we find the mapping relationship?Microsoft's msdn does have an introductory marshaling generic type comparison table: https://docs.microsoft.com/zh-cn/dotnet/standard/native-interop/type-marshaling And you're interested in seeing it.

As you can see from the diagram, string s in C# correspond to char*in C++, so this is where you go.

--- Person.cpp

extern "C"
{
	//Character string
	_declspec(dllexport) int GetLength(char* chs);
}


--- Person.h

#include "Person.h"
#include "iostream"
using namespace std;

int GetLength(char* chs)
{
	return strlen(chs);
}

Then let's see how C#is written. string usually uses asc code in C++ and C#is Unicode, so add a CharSet to DllImport to specify it.

    class Program
    {
        [DllImport("ConsoleApplication1.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        extern static int GetLength([MarshalAs(UnmanagedType.LPStr)] string str);

        static void Main(string[] args)
        {
            var str = "hello world";
            Console.WriteLine($"length={GetLength(str)}");

            Console.ReadLine();
        }
    }

---- output -----

length=11

3. Processing of complex types

Complex type configuration mappings are difficult, error prone, error prone, and memory leaks, but thanks to Microsoft providing a gadget, P/Invoke Interop Assistant, which helps us automatically match mappings, I'll show you an example of marshaling the Person class.

As you can see from the diagram, it is very convenient to write C++ on the left and C# mapping type on the right automatically.

--- Person.cpp

extern "C"
{
	class Person
	{
	public:
		char* username;
		char* password;
	};

	_declspec(dllexport) char* AddPerson(Person person);
}

--- Person.h

#include "Person.h"
#include "iostream"
using namespace std;

char* AddPerson(Person person)
{
	return person.username;
}

You can see that AddPerson in C++ returns char*, in C#we connect with IntPtr, then convert the pointer to string using Marshal, and then copy the C#code generated by the tool into the project as follows:

    [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
    public struct Person
    {
        /// char*
        [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.LPStr)]
        public string username;

        /// char*
        [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.LPStr)]
        public string password;
    }   

    class Program
    {
        [DllImport("ConsoleApplication1.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        extern static IntPtr AddPerson(Person person);

        static void Main(string[] args)
        {
            var person = new Person() { username = "dotnetfly", password = "123456" };

            var ptr = AddPerson(person);
            var str = Marshal.PtrToStringAnsi(ptr);

            Console.WriteLine($"username={str}");

            Console.ReadLine();
        }
    }

---------- output ------------

username=dotnetfly

4. Handling of callback functions (asynchronous)

The three situations described above are all one-way, that is, C# transfers data to C++, and sometimes C++ needs to actively call C# functions. We know that C# is a callback function, which is also a delegate wrapper. I don't want to mention that I'm glad C++ can directly accept your delegation and see how to implement it.

--- Person.cpp

extern "C"
{
	//Function Pointer
	typedef void(_stdcall* PCALLBACK) (int result);
	_declspec(dllexport) void AsyncProcess(PCALLBACK ptr);
}

--- Person.h

#include "Person.h"
#include "iostream"
using namespace std;

void AsyncProcess(PCALLBACK ptr)
{
	ptr(10);  //Callback delegation for C#
}

As you can see from the code, PCALLBACK is the function pointer I defined, accepting the int parameter.

    class Program
    {
        delegate void Callback(int a);

        [DllImport("ConsoleApplication1.dll", CallingConvention = CallingConvention.Cdecl)]
        extern static void AsyncProcess(Callback callback);

        static void Main(string[] args)
        {
            AsyncProcess((i) =>
            {
                //Callback function here oh...

                Console.WriteLine($"This is a callback function: {i}");
            });

            Console.ReadLine();
        }
    }

------- output -------  

//This is a callback function oh: 10

I've made a custom delegate here because I don't accept generic throws () using Action <T>.

Fourth: Summary

This reminds me of the linear regression implemented by python some time ago. For simplicity I used http to interact with C#, this time I'm going to rewrite it with C++ and then PInvoke to interact directly. Okay, with the help of the C++ ecosystem, let C# add more wings to it ~~

If you have more questions to interact with me, scan below to enter ~

Tags: C# shell Python

Posted on Thu, 28 May 2020 17:54:55 -0700 by rIkLuNd