Exporting .NET DLLs with Visual Studio 2005 to be Consumed by Native Applications

Abstract

This article will explain how to create a wrapper for a .NET DLL and import it in MetaTrader 4. I used Visual Studio 2005 and wrote the example .NET DLL in C# and the wrapper in C++. The wrapper is again a DLL, which implements the Stdcall calling convention (MetaTrader 4 supports only this convention but you can produce a DLL with any calling convention as long as .NET supports it). The .NET DLL can be easily written in any other .NET language. The technique described below can be used to export .NET DLLs to be used by any unmanaged applications.

Motivation

When I decided to create an expert advisor for MetaTrader to test a trading strategy, I was frustrated because of many unsuccessful attempts to find out how to create DLLs and import them in the expert advisor. I am coming from a Java background and a year and a half of programming in C#. I have a slight knowledge of C++ and feel like I am crippled when I have to write something in it. Because of that, my first decision was to implement the trading strategy in C#. I spent days trying to figure out how, but my nerves couldn't stand it, so I gave it all up and switched to Visual C++ 6.0. When I realized how quickly I am in writing C++ code, I began having second thoughts and started trying in .NET again. After days of trial and error, I came up with the following step-by-step guide.

Step-by-Step Guide for Creating a Sample DLL

I. Creating the C# DLL

  1. Create a new C# class library project called CSharpAssembly in a new solution named DotNetDllForMetaTrader4.
  2. Add the following C# class file to the project:
  3. CSharpClass.cs:
    using System;
    using System.Collections.Generic;
    using System.Text;
    
    namespace CSharpAssembly
    {
       public class CSharpClass
       {
          public static byte[] Hello(byte[] name)
          {
             string s = ", hello from .NET!";
             byte[] helloPart = Encoding.ASCII.GetBytes(s);
             byte[] whole =
                new byte[name.Length + helloPart.Length];
             int i = 0;
             foreach (byte b in name)
             {
                whole[i++] = b;
             }
             foreach (byte b in helloPart)
             {
                whole[i++] = b;
             }
          return whole;
          }
       }
    }
    
  4. Change the CSharpAssembly project -> Properties -> Build -> Output path field to "..\debug". You do this to dump all the output to one folder.
    This step should be completed after adding CppStdcallInerfaceWrapper project to the solution because this will automatically change the solution platform from Any CPU to Mixed Platforms.

II. Creating the C++ Wrapper, Implementing the Stdcall Calling Convention

  1. Add to the solution a new C++ Win32 project called CppStdcallInerfaceWrapper with application type DLL.
  2. Change the CppStdcallInerfaceWrapper.cpp file content to:
  3. #include "stdafx.h"
    
    #ifdef _MANAGED
    #pragma managed(push, off)
    #endif
    
    #ifdef _MANAGED
    #pragma managed(pop)
    #endif
    
    #using "CSharpAssembly.dll"
    using namespace CSharpAssembly;
    
    __declspec(dllexport) char* __stdcall Hello(char* name)
    {
       int i = 0;
       while (*name != '\0')
       {
          i++;
          name++;
       }
       array<unsigned char>^ nameManArr =
          gcnew array<unsigned char>(i);
       name -= i;
       i = 0;
       while (*name != '\0')
       {
          nameManArr[i] = *name;
          name++;
          i++;
       }
       array<unsigned char>^ char8ManArr =
          CSharpClass::Hello(nameManArr);
       char*  char8UnmanArr = new char[char8ManArr->Length + 1];
       for (int i = 0; i < char8ManArr->Length; i++)
       {
          char8UnmanArr[i] = char8ManArr[i];
       }
       char8UnmanArr[char8ManArr->Length] = '\0';
       return char8UnmanArr;
    }
    
  4. To make the Hello function visible in the CppStdcallInerfaceWrapper.dll through the Stdcall calling convention, add a new Module Definition file called CppStdcallInerfaceWrapper.def in the project source files. This will automatically set the project properties -> Linker -> Input -> Module Definition file.
  5. To add Hello to the list of exported functions, change the CppStdcallInerfaceWrapper.def content to the following:
  6. LIBRARY "CppStdcallInerfaceWrapper"
    EXPORTS
       Hello
    
  7. To call the CSharpAssembly.dll, go to project properties -> Configuration Properties -> General and add Common Language Runtime Support.
  8. To enable the compiler to locate the CSharpAssembly.dll, go to project properties -> C/C++ -> General -> Resolve #using References and add "$(SolutionDir)\debug".

III. Creating a Debug Entry Point Project

  1. Add to the solution a new C# console application project called DebugEntry.
  2. Add the following C# class file to the project:
  3. DebugEntry.cs:
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Runtime.InteropServices;
    
    namespace DebugEntry
    {
       class DebugEntry
       {
          [DllImport("CppStdcallInerfaceWrapper.dll",
                CharSet = CharSet.Ansi, CallingConvention =
                   CallingConvention.StdCall)]
            public static extern string Hello(string name);
    
            static void Main(string[] args)
            {
                System.Console.WriteLine(Hello("MyName"));
                System.Console.ReadLine();
            }
       }
    }
    
  4. Change the DebugEntry project -> Properties -> Build -> Output path field to "..\debug". You do this to dump all the output to one folder.
  5. In solution properties -> Common Properties -> Startup Project, change the single startup project to DebugEntry.

IV. Setting Project Dependencies and Building the Solution

  1. Go to solution properties -> Common Properties -> Project Dependencies and set the dependencies as follows:
    DebugEntry depends on CppStdcallInterfaceWrapper and CSharpAssembly,
    CppStdcallInterfaceWrapper depends on CSharpAssembly.
  2. Build the solution.

V. Testing the DLLs in MetaTrader 4

  1. Add to the Path environment variable the MetaTrader 4 main directory. Example: "C:\Program Files\MetaTrader 4". You can do it through My Computer -> Properties -> Advanced -> Environment Variables. You do this so MetaTrader 4 can locate your DLLs.
  2. Copy CSharpAssembly.dll and CppStdcallInerfaceWrapper.dll from "$(SolutionDir)\debug" to MetaTrader's main directory.
    The reason you don't put the DLLs in "C:\Program Files\MetaTrader 4\experts\libraries", where external DLLs for MetaTrader usually reside is that when the calling order is MetaTrader 4 Terminal -> CppStdcallInerfaceWrapper.dll -> CSharpAssembly.dll, it will not locate the CSharpAssembly.dll. The Path environment variable can't solve this because the CLR doesn't use it to locate assemblies. That's why I put the DLLs where the MetaTrader terminal executable is. In the Reference section below, there is a link covering the matter on runtime assembly location.
  3. In the MetaTrader platform, create a new expert advisor called DotNetDllTest.
  4. Before the init() function in DotNetDllTest.mq4, insert the following:
  5. #import "user32.dll"
    int MessageBoxA(int hWnd ,string szText,
                    string szCaption, int nType);
    
    #import "CppStdcallInerfaceWrapper.dll"
    string Hello(string name);
    
  6. At the beginning of the init() function, insert:
  7. MessageBoxA(100, Hello("MyName"), "", 0);
    The user32.dll is a DLL that comes with the Windows operating system; you use it just to show a box with the hello message.
  8. Run the DotNetDllTest expert advisor with Allow DLL imports option on.

Explanations about the Step-by-Step Guide

The DebugEntry project is not necessary, but with it you can debug other projects. To create a release version, all the properties setting steps must be replicated. In the Hello function, project CppStdcallInterfaceWrapper, the char8UnmanArr array is unmanaged and you have to deallocate it, using the delete operator, after you are finished using it in MetaTrader. To keep things simple, this is not done in the example above. You can do it by writing another function in the CppStdcallInterfaceWrapper and calling it from MetaTrader.

Another way of passing data to the unmanaged MetaTrader is not by creating an unmanaged copy, but by using the System.InteropeServices.GCHandle to pin the needed managed object while using it in the unmanaged MetaTrader, so the Garbage Collector doesn't move or delete it.

Other thing you sould take care of is the ecoding convention. .NET strings are in Unicode, whereas the calling application can support only ASCII, like with MetaTrader.

Security Issues

Although there are several available ways to protect your .NET DLLs from back engineering, this is not as secure as native byte code. References to other materials about this matter are provided in the next section.

References



About the Author

Boian Petkantchin

I am living in Bulgaria and studding mathematics at the Sofia University. I have been programming in Java for two years and now continuing in C# for the past year and a half. Mostly coding my own project and having participated in the development of event photography management application.

Downloads

Comments

  • 0xe0434352 error

    Posted by Xerxes on 04/21/2016 09:12pm

    Hello I copy your dll files into my "C:\Program Files\MetaTrader 4\experts\libraries" and try to write code in MetaEditor but when I am running the code I receive this message: "2016.04.22 08:41:09.105 Unhandled exception 0xE0434352" can help me to fix this error? Thanks. I am waiting for your replay. Best Regards.

    Reply
  • Very Informative & Helpful

    Posted by overng on 10/23/2014 02:46am

    Hi Boian, Thank you for a very informative and well presented tutorial, which works 'out of the box'. I normally work with Unix, so don't have much experience with Windows code ... this has saved me many hours ;) I wish you well. overng.

    Reply
  • System.AccessViolationException when returning to debug entry

    Posted by Ernie on 08/19/2014 12:36pm

    Hi Boian, I get a System.AccessViolationException in my debug entry. I use VS2013 x86 on 64bit Win7. I have logged my query on code project please can you try to help. code project : http://www.codeproject.com/Messages/4883815/System-AccessViolationException-Returning-from-cpp.aspx#xx1329679xx

    • the same problem

      Posted by marquez on 05/06/2015 07:34am

      I have the same Issue with the same config. To build the project works well, but starting in debug entry causes the same error like above. Trying to import the dll in MT4 breaks with an uncaught error. Would be nice if you can update or help to Fix this.

      Reply
    Reply
  • Unhandled exception 0xE0434F4D

    Posted by Steph on 07/19/2012 08:39pm

    Hi Boian, That's a great article thanks. I've followed the steps, created the dlls and dumped the dlls in metatrader(5) folder. When I've created a script instead of Expert but that's not the issue. When I drop the scipt on the chart I get Unhandled exception 0xE0434F4D. Dll can be found obviously and I'm stuck on this. I've tried everything, uninstalling all .Net frameworks, tweaking compilation parameters, creating a very basic dll with one function with no parameters and no return I keep getting the same exception. I can't figure out whether the issue comes from the code or from the environment. If you would have any thoughts on this that would be much appreciated. Thanks Stephan

    Reply
  • .

    Posted by Sérgio Rodelo on 07/15/2012 03:15pm

    Good article, thanks for detailed information. Is there a newer version of the article? Now we are in 2012 and it was written in 2008.

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • As all sorts of data becomes available for storage, analysis and retrieval - so called 'Big Data' - there are potentially huge benefits, but equally huge challenges...
  • The agile organization needs knowledge to act on, quickly and effectively. Though many organizations are clamouring for "Big Data", not nearly as many know what to do with it...
  • Cloud-based integration solutions can be confusing. Adding to the confusion are the multiple ways IT departments can deliver such integration...

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date