Click here to Skip to main content
15,885,944 members
Articles / Programming Languages / XML

How to Automate Exporting .NET Function to Unmanaged Programs

Rate me:
Please Sign up or sign in to vote.
4.90/5 (44 votes)
21 Nov 2006MIT3 min read 457K   4.5K   94  
Post-build tool which can automate exporting .NET function to unmanaged programs
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace ExportDll
{

    enum ParserState
    {
        Normal,
        ClassDeclaration,
        Class,
        DeleteExportDependency,
        MethodDeclaration,
        MethodProperties,
        Method,
        DeleteExportAttribute,
    }
    class Program
    {
        static Dictionary<System.Runtime.InteropServices.CallingConvention, string> diccc = new Dictionary<CallingConvention, string>();
        static bool verbose = false;
        static Program()
        {
            diccc[System.Runtime.InteropServices.CallingConvention.Cdecl] = typeof(CallConvCdecl).FullName;
            diccc[System.Runtime.InteropServices.CallingConvention.FastCall] = typeof(CallConvFastcall).FullName;
            diccc[System.Runtime.InteropServices.CallingConvention.StdCall] = typeof(CallConvStdcall).FullName;
            diccc[System.Runtime.InteropServices.CallingConvention.ThisCall] = typeof(CallConvThiscall).FullName;
            diccc[System.Runtime.InteropServices.CallingConvention.Winapi] = typeof(CallConvStdcall).FullName;
        }

        static void Log(bool forced, string message, params object[] param)
        {
            if (forced || verbose)
            {
                Console.WriteLine(message, param);
            }
        }

        static void Log(string message, params object[] param)
        {
            Log(false, message, param);
        }

        static void Load()
        {
            var dic = new Dictionary<string, Dictionary<string, KeyValuePair<string, string>>>();
            Assembly assembly = Assembly.ReflectionOnlyLoadFrom((string)AppDomain.CurrentDomain.GetData("filepath"));
            Type[] types = assembly.GetTypes();
            int exportscount = 0;
            foreach (Type type in types)
            {
                MemberInfo[] mis = type.FindMembers(MemberTypes.All, BindingFlags.Public | BindingFlags.Static, new MemberFilter((mi, obj) => true), null);
                foreach (MemberInfo mi in mis)
                {
                    var attrs = CustomAttributeData.GetCustomAttributes(mi);
                    foreach (var attr in attrs)
                        if (attr.Constructor.ReflectedType.FullName == "ExportDllAttribute")
                        {
                            if (!dic.ContainsKey(type.FullName))
                                dic[type.FullName] = new Dictionary<string, KeyValuePair<string, string>>();
                            switch (attr.ConstructorArguments.Count)
                            {
                                case 0:
                                    dic[type.FullName][mi.Name] = new KeyValuePair<string, string>(mi.Name, typeof(CallConvStdcall).FullName);
                                    break;
                                case 1:
                                    dic[type.FullName][mi.Name] = new KeyValuePair<string, string>((string)attr.ConstructorArguments[0].Value, typeof(CallConvStdcall).FullName);
                                    break;
                                case 2:
                                    dic[type.FullName][mi.Name] = new KeyValuePair<string, string>((string)attr.ConstructorArguments[0].Value, diccc[(CallingConvention)attr.ConstructorArguments[1].Value]);
                                    break;
                                default:
                                    break;
                            }
                            exportscount++;
                        }
                }
            }
            AppDomain.CurrentDomain.SetData("exportscount", exportscount);
            AppDomain.CurrentDomain.SetData("dic", dic);
        }


        static int Main(string[] args)
        {
            try
            {
                if (args.Length < 1)
                {
                    Log(true, "Parameter error!");
                    return 1;
                }
                bool debug = false;
                List<string> listargs = new List<string>(args);
                listargs.RemoveAt(0);
                int deb = listargs.FindIndex(new Predicate<string>(x => x.ToLower().Contains("/debug")));
                if (deb > -1)
                    debug = true;
                else
                {
                    int rel = listargs.FindIndex(new Predicate<string>(x => x.ToLower().Contains("/release")));
                    if (rel > -1)
                    {
                        listargs.RemoveAt(rel);
                        listargs.Add("/optimize");
                    }
                }
                int any = listargs.FindIndex(new Predicate<string>(x => x.ToLower().Contains("/anycpu")));
                if (any > -1)
                {
                    listargs.RemoveAt(any);
                }
                else
                {
                    int x64 = listargs.FindIndex(new Predicate<string>(x => x.ToLower().Contains("/x64")));
                    if (x64 > -1)
                    {
                        listargs.Add("/PE64");
                    }
                }
                int verb = listargs.FindIndex(new Predicate<string>(x => x.ToLower().Contains("/verbose")));
                if (verb > -1)
                {
                    verbose = true;
                    listargs.RemoveAt(verb);
                }
                string filepath = args[0];
                string path = System.IO.Path.GetDirectoryName(filepath);
                if (path == string.Empty)
                {
                    Log(true, "Full path needed!");
                    return 1;
                }
                string ext = System.IO.Path.GetExtension(filepath);
                if (ext != ".dll")
                {
                    Log(true, "Target should be dll!");
                    return 1;
                }

                AppDomain domain = AppDomain.CreateDomain("ReflectionOnly");
                domain.ReflectionOnlyAssemblyResolve += new ResolveEventHandler(CurrentDomain_ReflectionOnlyAssemblyResolve);
                domain.SetData("filepath", filepath);
                domain.DoCallBack(new CrossAppDomainDelegate(Program.Load));
                var dic = (Dictionary<string, Dictionary<string, KeyValuePair<string, string>>>)domain.GetData("dic");
                int exportscount = (int)domain.GetData("exportscount");
                AppDomain.Unload(domain);
                if (exportscount > 0)
                {
                    int exportpos = 1;
                    string filename = System.IO.Path.GetFileNameWithoutExtension(filepath);
                    System.IO.Directory.SetCurrentDirectory(path);
                    Process proc = new Process();
                    string arguments = string.Format("/nobar{1}/out:{0}.il {0}.dll", filename, debug ? " /linenum " : " ");
                    Log("Deassebly file with arguments '{0}'", arguments);
                    System.Diagnostics.ProcessStartInfo info = new ProcessStartInfo(Properties.Settings.Default.ildasmpath, arguments);
                    info.UseShellExecute = false;
                    info.CreateNoWindow = false;
                    info.RedirectStandardOutput = true;
                    proc.StartInfo = info;
                    proc.Start();
                    proc.WaitForExit();
                    Log(proc.ExitCode != 0, proc.StandardOutput.ReadToEnd());
                    if (proc.ExitCode != 0)
                        return proc.ExitCode;
                    List<string> wholeilfile = new List<string>();
                    System.IO.StreamReader sr = System.IO.File.OpenText(System.IO.Path.Combine(path, filename + ".il"));
                    string methoddeclaration = "";
                    string methodname = "";
                    string classdeclaration = "";
                    string methodbefore = "";
                    string methodafter = "";
                    int methodpos = 0;
                    Stack<string> classnames = new Stack<string>();
                    List<string> externassembly = new List<string>();
                    ParserState state = ParserState.Normal;
                    while (!sr.EndOfStream)
                    {
                        string line = sr.ReadLine();
                        string trimedline = line.Trim();
                        bool addilne = true;
                        switch (state)
                        {
                            case ParserState.Normal:
                                if (trimedline.StartsWith(".class"))
                                {
                                    state = ParserState.ClassDeclaration;
                                    addilne = false;
                                    classdeclaration = trimedline;
                                }
                                else if (trimedline.StartsWith(".assembly extern ExportDll"))
                                {
                                    addilne = false;
                                    state = ParserState.DeleteExportDependency;
                                    Log("Deleting ExportDllAttribute dependency.");
                                }
                                break;
                            case ParserState.DeleteExportDependency:
                                if (trimedline.StartsWith("}"))
                                {
                                    state = ParserState.Normal;
                                }
                                addilne = false;
                                break;
                            case ParserState.ClassDeclaration:
                                if (trimedline.StartsWith("{"))
                                {
                                    state = ParserState.Class;
                                    string classname = "";
                                    System.Text.RegularExpressions.Regex reg = new System.Text.RegularExpressions.Regex(@".+\s+([^\s]+) extends \[.*");
                                    System.Text.RegularExpressions.Match m = reg.Match(classdeclaration);
                                    if (m.Groups.Count > 1)
                                        classname = m.Groups[1].Value;
                                    classname = classname.Replace("'", "");
                                    if (classnames.Count > 0)
                                        classname = classnames.Peek() + "+" + classname;
                                    classnames.Push(classname);
                                    Log("Found class: " + classname);
                                    wholeilfile.Add(classdeclaration);
                                }
                                else
                                {
                                    classdeclaration += " " + trimedline;
                                    addilne = false;
                                }
                                break;
                            case ParserState.Class:
                                if (trimedline.StartsWith(".class"))
                                {
                                    state = ParserState.ClassDeclaration;
                                    addilne = false;
                                    classdeclaration = trimedline;
                                }
                                else if (trimedline.StartsWith(".method"))
                                {
                                    if (dic.ContainsKey(classnames.Peek()))
                                    {
                                        methoddeclaration = trimedline;
                                        addilne = false;
                                        state = ParserState.MethodDeclaration;
                                    }
                                }
                                else if (trimedline.StartsWith("} // end of class"))
                                {
                                    classnames.Pop();
                                    if (classnames.Count > 0)
                                        state = ParserState.Class;
                                    else
                                        state = ParserState.Normal;
                                }
                                break;
                            case ParserState.MethodDeclaration:
                                if (trimedline.StartsWith("{"))
                                {
                                    System.Text.RegularExpressions.Regex reg = new System.Text.RegularExpressions.Regex(@"(?<before>[^\(]+(\(\s[^\)]+\))*\s)(?<method>[^\(]+)(?<after>\(.*)");
                                    System.Text.RegularExpressions.Match m = reg.Match(methoddeclaration);
                                    if (m.Groups.Count > 3)
                                    {
                                        methodbefore = m.Groups["before"].Value;
                                        methodafter = m.Groups["after"].Value;
                                        methodname = m.Groups["method"].Value;
                                    }
                                    Log("Found method: " + methodname);
                                    if (dic[classnames.Peek()].ContainsKey(methodname))
                                    {
                                        methodpos = wholeilfile.Count;
                                        state = ParserState.MethodProperties;
                                    }
                                    else
                                    {
                                        wholeilfile.Add(methoddeclaration);
                                        state = ParserState.Method;
                                        methodpos = 0;
                                    }
                                }
                                else
                                {
                                    methoddeclaration += " " + trimedline;
                                    addilne = false;
                                }
                                break;
                            case ParserState.Method:
                                if (trimedline.StartsWith("} // end of method"))
                                {
                                    state = ParserState.Class;
                                }
                                break;
                            case ParserState.MethodProperties:
                                if (trimedline.StartsWith(".custom instance void [ExportDll"))
                                {
                                    addilne = false;
                                    state = ParserState.DeleteExportAttribute;
                                }
                                else if (trimedline.StartsWith("// Code"))
                                {
                                    state = ParserState.Method;
                                    if (methodpos != 0)
                                        wholeilfile.Insert(methodpos, methoddeclaration);
                                }
                                break;
                            case ParserState.DeleteExportAttribute:
                                if (trimedline.StartsWith(".custom") || trimedline.StartsWith("// Code"))
                                {
                                    KeyValuePair<string, string> attr = dic[classnames.Peek()][methodname];
                                    if (methodbefore.Contains("marshal( "))
                                    {
                                        int pos = methodbefore.IndexOf("marshal( ");
                                        methodbefore = methodbefore.Insert(pos, "modopt([mscorlib]" + attr.Value + ") ");
                                        methoddeclaration = methodbefore + methodname + methodafter;
                                    }
                                    else
                                        Log("\tChanging calling convention: " + attr.Value);
                                    if (methodpos != 0)
                                        wholeilfile.Insert(methodpos, methoddeclaration);
                                    if (methodname == "DllMain")
                                        wholeilfile.Add(" .entrypoint");
                                    wholeilfile.Add(string.Format(".export [{0}] as {1}", exportpos, dic[classnames.Peek()][methodname].Key));

                                    Log("\tAdding .vtentry:{0} .export:{1}", exportpos, dic[classnames.Peek()][methodname].Key);
                                    exportpos++;
                                    state = ParserState.Method;
                                }
                                else
                                    addilne = false;
                                break;
                        }
                        if (addilne)
                            wholeilfile.Add(line);
                    }
                    sr.Close();
                    System.IO.StreamWriter sw = System.IO.File.CreateText(System.IO.Path.Combine(path, filename + ".il"));
                    foreach (string line in wholeilfile)
                    {
                        sw.WriteLine(line);
                    }
                    sw.Close();
                    string res = filename + ".res";
                    if (System.IO.File.Exists(filename + ".res"))
                        res = " /resource=" + res;
                    else
                        res = "";
                    proc = new Process();
                    arguments = string.Format("/nologo /quiet /out:{0}.dll {0}.il /DLL{1} {2}", filename, res, string.Join(" ", listargs.ToArray()));
                    Log("Compiling file with arguments '{0}'", arguments);
                    info = new ProcessStartInfo(Properties.Settings.Default.ilasmpath, arguments);
                    info.UseShellExecute = false;
                    info.CreateNoWindow = false;
                    info.RedirectStandardOutput = true;
                    proc.StartInfo = info;
                    proc.Start();
                    proc.WaitForExit();
                    Log(proc.ExitCode != 0, proc.StandardOutput.ReadToEnd());
                    if (proc.ExitCode != 0)
                        return proc.ExitCode;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return -1;
            }
            return 0;
        }

        static Assembly CurrentDomain_ReflectionOnlyAssemblyResolve(object sender, ResolveEventArgs args)
        {
            return Assembly.ReflectionOnlyLoad(args.Name);
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Systems / Hardware Administrator
Poland Poland
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions