Search code examples
javac#jna

C# dll method call from Java


Has anyone an idea about what is wrong with my attempt to call a method from a C# dll in my Java code?

Here is my example:

Java code:

public class CsDllHandler {
public interface IKeywordRun extends Library {
    public String KeywordRun(String action, String xpath, String inputData,
            String verifyData);
}

private static IKeywordRun jnaInstance = null;

public void runDllMethod(String action, String xpath, String inputData,
        String verifyData) {
    NativeLibrary.addSearchPath(${projectDllName},
                    "${projectPath}/bin/x64/Debug");

    jnaInstance = (IKeywordRun) Native.loadLibrary(
            ${projectDllName}, IKeywordRun.class);

    String csResult = jnaInstance.KeywordRun(action, xpath, inputData,
            verifyData);
    System.out.println(csResult);
}
} 

And in C#:

    [RGiesecke.DllExport.DllExport]
    public static string KeywordRun(string action, string xpath, string inputData, string verifyData) { 
        return "C# here";
    }

The Unmanaged Exports nuget should be enough for me to call this method (in theory) but I have some strange error:

Exception in thread "main" java.lang.Error: Invalid memory access
at com.sun.jna.Native.invokePointer(Native Method)
at com.sun.jna.Function.invokePointer(Function.java:470)
at com.sun.jna.Function.invokeString(Function.java:651)
at com.sun.jna.Function.invoke(Function.java:395)
at com.sun.jna.Function.invoke(Function.java:315)
at com.sun.jna.Library$Handler.invoke(Library.java:212)
at com.sun.proxy.$Proxy0.KeywordRun(Unknown Source)
at auto.test.keywords.utils.CsDllHandler.runDllMethod(CsDllHandler.java:34)
at auto.test.keywords.runner.MainClass.main(MainClass.java:24)

Solution

  • Well, after another day of research and "trial and error" I have found the cause of my problem and a solution.

    The cause was that my C# dll had a dependency on log4net.dll. For running a static method from a standalone C# dll the code from the question is all you need.

    The solution for using C# dll with dependencies is to create another dll with no dependency and to load the original dll in this adapter with reflection. In Java you should load the adapter dll with jna and call any exported method. I was able not only to execute methods from the adapter but also to configure log4net with reflection and Java

    Here is my code: (C#)

    public class CSharpDllHandler {
    
    private static Logger log = Logger.getLogger(CSharpDllHandler.class);
    
    public interface IFrameworkAdapter extends Library {
    
        public String runKeyword(String action, String xpath, String inputData,
                String verifyData);
    
        public String configureLog4net(String log4netConfigPath);
    
        public String loadAssemblies(String frameworkDllPath,
                String log4netDllPath);
    }
    
    private static IFrameworkAdapter jnaAdapterInstance = null;
    private String jnaSearchPath = null;
    
    public CSharpDllHandler(String searchPath) {
        this.jnaSearchPath = searchPath;
        // add to JNA search path
        System.setProperty("jna.library.path", jnaSearchPath);
        // load attempt
        jnaAdapterInstance = (IFrameworkAdapter) Native.loadLibrary(
                "FrameworkAdapter", IFrameworkAdapter.class);
    }
    
    public String loadAssemblies(String frameworkDllPath, String log4netDllPath) {
        String csResult = jnaAdapterInstance.loadAssemblies(frameworkDllPath,
                log4netDllPath);
        log.debug(csResult);
        return csResult;
    }
    
    public String runKeyword(String action, String xpath, String inputData,
            String verifyData) {
        String csResult = jnaAdapterInstance.runKeyword(action, xpath,
                inputData, verifyData);
        log.debug(csResult);
        return csResult;
    }
    
    public String configureLogging(String log4netConfigPath) {
        String csResult = jnaAdapterInstance
                .configureLog4net(log4netConfigPath);
        log.debug(csResult);
        return csResult;
    }
    
    public String getJnaSearchPath() {
        return jnaSearchPath;
    }
    }
    

    In the main method just use something like this:

        CSharpDllHandler dllHandler = new CSharpDllHandler(
                    ${yourFrameworkAdapterDllLocation});
    
    dllHandler.loadAssemblies(
        ${yourOriginalDllPath},${pathToTheUsedLog4netDllFile});             
    dllHandler.configureLogging(${log4net.config file path});
    dllHandler.runKeyword("JAVA Action", "JAVA Xpath", "JAVA INPUT",
                    "JAVA VERIFY");
    dllHandler.runKeyword("JAVA Action2", "JAVA Xpath2", "JAVA INPUT2",
                    "JAVA VERIFY2");
    

    In C# I have the desired methods on the original dll:

            public static string KeywordRun(string action, string xpath, string inputData, string verifyData) {
            log.Debug("Action = " + action);
            log.Debug("Xpath = " + xpath);
            log.Debug("InputData = " + inputData);
            log.Debug("VerifyData = " + verifyData);
            return "C# UserActions result: "+ action+" "+xpath+" "+inputData+" "+verifyData;
        }
    

    and all the magic is in the DLL Adapter:

    namespace FrameworkAdapter {
    [ComVisible(true)]
    public class FwAdapter {
        private const String OK="OK";
        private const String frameworkEntryClassName = "${nameOfTheDllClass with method to run }";
        private const String log4netConfiguratorClassName = "log4net.Config.XmlConfigurator";
    
        private static Assembly frameworkDll = null;
        private static Type frameworkEntryClass = null;
        private static MethodInfo keywordRunMethod = null;
    
        private static Assembly logDll = null;
        private static Type logEntryClass = null;
        private static MethodInfo logConfigureMethod = null;
    
        private static String errorMessage = "OK";
    
        [RGiesecke.DllExport.DllExport]
        public static string loadAssemblies(string frameworkDllPath, string log4netDllPath) {
            try {
                errorMessage = LoadFrameworkDll(frameworkDllPath, frameworkEntryClassName);
                LoadFrameworkMethods("KeywordRun", "Setup", "TearDown");
    
                errorMessage = LoadLogAssembly(log4netDllPath, log4netConfiguratorClassName);
                if (errorMessage.CompareTo(OK) == 0)
                    errorMessage = LoadLogMethods("Configure");
            }
            catch (Exception e) {
                return e.Message;
            }
            return errorMessage;
        }
    
        [RGiesecke.DllExport.DllExport]
        public static string configureLog4net(string log4netConfigPath) {
            if (errorMessage.CompareTo("OK") == 0) {
                StringBuilder sb = new StringBuilder();
                sb.AppendLine("Try to configure Log4Net");
                try {
                    FileInfo logConfig = new FileInfo(log4netConfigPath);
                    logConfigureMethod.Invoke(null, new object[] { logConfig });
                    sb.AppendLine("Log4Net configured");
                }
                catch (Exception e) {
                    sb.AppendLine(e.InnerException.Message);
                }
    
                return sb.ToString();
            }
            return errorMessage;
        }
    
        [RGiesecke.DllExport.DllExport]
        public static string runKeyword(string action, string xpath, string inputData, string verifyData) {
            StringBuilder sb = new StringBuilder();
            object result = null;
            try {
                result = keywordRunMethod.Invoke(null, new object[] { action, xpath, inputData, verifyData });
                sb.AppendLine(result.ToString());
            }
            catch (Exception e) {
                sb.AppendLine(e.InnerException.Message);
            }
    
            return sb.ToString();
        }
    
        private static String LoadFrameworkDll(String dllFolderPath, String entryClassName) {
            try {
                frameworkDll = Assembly.LoadFrom(dllFolderPath);
                Type[] dllTypes = frameworkDll.GetExportedTypes();
                foreach (Type t in dllTypes)
                    if (t.FullName.Equals(entryClassName)) {
                        frameworkEntryClass = t;
                        break;
                    }
            }
            catch (Exception e) {
                return e.InnerException.Message;
            }
            return OK;
        }
    
        private static String LoadLogAssembly(String dllFolderPath, String entryClassName) {
            try {
                logDll = Assembly.LoadFrom(dllFolderPath);
                Type[] dllTypes = logDll.GetExportedTypes();
    
                foreach (Type t in dllTypes)
                    if (t.FullName.Equals(entryClassName)) {
                        logEntryClass = t;
                        break;
                    }
            }
            catch (Exception e) {
                return e.InnerException.Message;
            }
            return OK;
        }
    
        private static String LoadLogMethods(String logMethodName) {
            try {
                logConfigureMethod = logEntryClass.GetMethod(logMethodName, new Type[] { typeof(FileInfo) });
            }
            catch (Exception e) {
                return e.Message;
            }
            return OK;
        }
    
        private static void LoadFrameworkMethods(String keywordRunName, String scenarioSetupName, String scenarioTearDownName) {
            ///TODO load the rest of the desired methods here
            keywordRunMethod = frameworkEntryClass.GetMethod(keywordRunName);
        }
    }
    

    }

    Running this code will provide all the logged messages from the original C# DLL to the Java console output (and to a file if configured). In a similar way, we can load any other needed dll files for runtime.

    Please forgive my [very probable wrong] way of doing things in C# with reflection, I'm new to this language.