Quantcast
Channel: xInterop C++ .NET Bridge
Viewing all articles
Browse latest Browse all 31

AnyOption Class, An Example of C# Wrapper Class of Native C++ Class

$
0
0

I have been blogging about the great amazing features of C# .NET PInvoke Interop SDK Tool, I have also shown the power of the C# .NET Library for the Standard C++ Library. It is time for me to present how easily and quickly a C# wrapper class for a native C++ class can be generated automatically in just a few seconds, you will be able to see how a native C++ class can be wrapped in a C# class instantly and gets integrated with .NET seamlessly, calling C++ class method from C# .NET can never be easier. If you are a developer like me who always wants to write code efficiently and productively using both C# and native C++, you would find the solution of bridging the native C++ and C# .NET can save you so much time of using native C++ DLL.

The class I picked for demonstration is AnyOption, you can find the C++ implementation at GitHub. I am not going to post the code here, you can take a look at the C++ source code there. AnyOption is a C++ class for easy parsing of complex command line options. It was written for Unix platform, but I was able to build the class into a windows native C++ DLL very quickly in less than a minute. Once I opened the C++ DLL from NGenerator, all the exported methods except the private methods were being shown in the following screen shot.

As you can see, all the public and protected functions of AnyOption class are being shown in the screen shot. Simply by looking at the prototypes of the functions, you will find that most of the function parameter types are not complicated, they are int, char, char const *(same as const char *), char **. There are configuration setting you can use to marshal the data type by default, for example, char const* will be marshaled as string by default, char * will be marshaled as StringBuilder by default, char ** will be marshaled as a string array. You can still use the type marshaling designer to change the marshaling for specific method. You do not need to change the type marshaling for the primitive C++ data type since there is only one marshaling for them in the C# world. In the solution explorer, you can choose to show only the functions with possible multiple type marshaling as shown below.

There are still too many of them which I don’t necessary do anything to them because I am happy with marshaling char const * to string, that is exactly what it represents in the native C++ DLL. Then I choose to show the function with possible multiple type marshaling without default type marshaling. After filtering all the functions with default type marshaling, the functions I will need to fine tune by using the type marshaling designer came down to 3 as shown below,

If you have read the source code of the C++ AnyOption class, you know that the type of char** in all three functions is string array, and its preceding parameter of an integer type of these 3 functions is the length of the string array. By using the type marshaling designer, this char ** type can be easily marshaled as string array without writing single line of code, I was able to do it in less than a minute.

The resulting implementation of the C# method looks like it was written by us, developers. Actually, we had iterated all the possible scenario of all the data types NGenerator supports in C++, so the code below was actually the code we usually writes to marshal the C++ data type in C#.

I also presented the source code of the other two methods implemented in C#.

public void processCommandArgs(string[] argv, int max_args)
{
	int argc = (int)argv.Length;
	IntPtr[] __argv = new IntPtr[argv.Length];
	for (int index = 0; index < argv.Length; index++)
	{
		__argv[index] = Marshal.StringToHGlobalAnsi(argv[index]);
	}
	AnyOptionWin32.AnyOption_processCommandArgs(this.ptrObject, argc, __argv, max_args);
	for (int index = 0; index < __argv.Length; index++)
	{
		Marshal.FreeHGlobal(__argv[index]);
	}
}

public void useCommandArgs(string[] argv)
{
	int argc = (int)argv.Length;
	IntPtr[] __argv = new IntPtr[argv.Length];
	for (int index = 0; index < argv.Length; index++)
	{
		__argv[index] = Marshal.StringToHGlobalAnsi(argv[index]);
	}
	AnyOptionWin32.AnyOption_useCommandArgs(this.ptrObject, argc, __argv);
	for (int index = 0; index < __argv.Length; index++)
	{
		Marshal.FreeHGlobal(__argv[index]);
	}
}

After building the C# wrapper project, a .NET assembly containing the single AnyOption class was created, here is the meta data of the C++ class I copied from Visual Studio.

using System;
using System.Runtime.InteropServices;
using System.Text;

namespace NSpeech.Win32.NAnyOption
{
    public class AnyOption : AnyOptionBaseClass
    {
        public AnyOption();
        public AnyOption(IntPtr handle = null);
        public AnyOption(AnyOptionStructX86 objectStruct);
        public AnyOption(int maxoptions);
        public AnyOption(int maxoptions, int maxcharoptions);
        public AnyOption(IntPtr thisObject, IntPtr thatObject, bool initialize);

        public static implicit operator AnyOptionStructX86(AnyOption o);
        public static implicit operator AnyOptionStructX64(AnyOption o);

        protected override int StorageSize { get; }

        public void addUsage(string line);
        public AnyOption assign(AnyOption thatObject);
        public void autoUsagePrint(bool flag);
        protected override void CleanupNativeResource();
        public int getArgc();
        public string getArgv(int index);
        public bool getFlag(char option);
        public bool getFlag(string option);
        public string getValue(char option);
        public string getValue(string option);
        public bool hasOptions();
        public void noPOSIX();
        public void printAutoUsage();
        public void printUsage();
        public void processCommandArgs();
        public void processCommandArgs(int max_args);
        public void processCommandArgs(StringBuilder[] argv);
        public void processCommandArgs(string[] argv, int max_args);
        public bool processFile();
        public bool processFile(string filename);
        public void processOptions();
        public void setCommandFlag(char opt_string);
        public void setCommandFlag(string opt_string);
        public void setCommandFlag(string opt_string, char opt_char);
        public void setCommandLongPrefix(string prefix);
        public void setCommandOption(char opt_string);
        public void setCommandOption(string opt_string);
        public void setCommandOption(string opt_string, char opt_char);
        public void setCommandPrefixChar(char prefix);
        public void setFileCommentChar(char comment);
        public void setFileDelimiterChar(char delimiter);
        public void setFileFlag(char opt_string);
        public void setFileFlag(string opt_string);
        public void setFileFlag(string opt_string, char opt_char);
        public void setFileOption(char opt_string);
        public void setFileOption(string opt_string);
        public void setFileOption(string opt_string, char opt_char);
        public void setFlag(char opt_string);
        public void setFlag(string opt_string);
        public void setFlag(string opt_string, char opt_char);
        public void setOption(char opt_string);
        public void setOption(string opt_string);
        public void setOption(string opt_string, char opt_char);
        public void setVerbose();
        public void useCommandArgs(string[] argv);
        public void useFiileName(string filename);

        public class Marshaller : ICustomMarshaler
        {
            public Marshaller();

            public void CleanUpManagedData(object ManagedObj);
            public void CleanUpNativeData(IntPtr pNativeData);
            public static ICustomMarshaler GetInstance(string pstrCookie);
            public int GetNativeDataSize();
            public IntPtr MarshalManagedToNative(object ManagedObj);
            public object MarshalNativeToManaged(IntPtr pNativeData);
        }
    }
}

It has same neat interface as in the native C++ class. NGenerator allows you to remove the beginning underscore of a parameter name, so you don’t see any parameter starting with ‘_’ in the C# wrapper class.

With the help of C# .NET PInvoke Interop SDK, integrating any C++ native DLL is so easy and quick, you simply grab the native C++ DLL and the header files then feed them to NGenerator, you will get a C# .NET assembly which is ready for use in your C# application right away. Best of all, the maintenance if quite easy as well, all the custom type marshaling is saved, If you change the native C++ DLL and the change still applies to the C++ class function whose interface does not change, the only thing you need to do is to fine tune the newly added function and modified function which does require fine tuning, otherwise you will just need to re-generate the wrapper project and rebuild it.

In the near future, I will continue to demonstrate wrapping more complicated native C++ DLL in C# .NET.

 


Viewing all articles
Browse latest Browse all 31

Trending Articles