C++、C#、Java调用C++ DLL

C++/C#/Java调用C/C++ DLL

[toc]

导出C++ Dll

C++ 编译器为 VS2019

64 位 Debug 模式生成的无法调用(不知道具体原因)

MSVC++ 导出函数接口的方式主要有两种:

_stdcall可以在项目属性 -> C/C++ -> 高级 -> 调用约定处指定,就不用在每个函数前指定了

在函数定义时候加extern "C" _declspec(dllexport)

exportdll.h

extern "C" _declspec(dllexport) int _stdcall Add(int i, int j);
extern "C" _declspec(dllexport) float _stdcall Avg(int i, int j);

exportdll.cpp

#include "exportdll.h"
int Add(int i, int j)
{
    return i + j;
}
float Avg(int i, int j)
{
    return (i + j) / 2.0f;
}

使用模块定义文件(.def)声明

exportdll.h

int _stdcall Add(int i, int j);
float _stdcall Avg(int i, int j);

exportdll.cpp

#include "exportdll.h"
int Add(int i, int j)
{
    return i + j;
}
float Avg(int i, int j)
{
    return (i + j) / 2.0f;
}

右键源文件,选择添加模块定义文件exportdll.def

在项目属性 -> 链接器 -> 输入 -> 模块定义文件处输入exportdll.def

exportdll.def

LIBRARY exportdll
EXPORTS
    Add
    Avg

简单调用C++ DLL

均为基本数据类型的调用,复杂的数据类型会涉及到转换对应关系

C++动态调用

_stdcall也可以在项目属性 -> C/C++ -> 高级 -> 调用约定处指定

第五行可能会出错E0167 "const char *" 类型的实参与 "LPCWSTR" 类型的形参不兼容,项目属性 -> 高级 -> 字符集选择"使用多字节字符集”

int main()
{
    typedef int(_stdcall* AddFun)(int, int);
    typedef float(_stdcall* AvgFun)(int, int);
    HINSTANCE dll = LoadLibrary("exportdll.dll");
    if (dll != NULL) 
    {
        AddFun Add = (AddFun)GetProcAddress(dll, "Add");
        if (Add != NULL) 
        {
            cout << Add(3, 4) << endl;
        }
        AvgFun Avg = (AvgFun)GetProcAddress(dll, "Avg");
        if (Avg != NULL) 
        {
            cout << Avg(3, 4) << endl;
        }
    }
    return 0;
}

C#调用

CallingConvention为StdCall可以不用指定

class Program
{
    [DllImport("exportdll.dll", CallingConvention = CallingConvention.StdCall)]
    extern static public float Avg(int left, int right);
    [DllImport("exportdll.dll"]
    extern static private int Add(int left, int right);
    static void Main(string[] args)
    {
        Console.WriteLine(Avg(11, 2));
        Console.WriteLine(Add(11, 2));
    }
}

Java调用

Java 调用 C++ 比较常用的方法有一下三种:

1. JNI

预先编译好的 C++ dll 还需要进一步使用jni封装,较为麻烦,以后有机会再写

2. JNative

DllImport.java

public class DllImport {
    public static void main(String[] args) {
        JNative add = new JNative("exportdll.dll", "Add");
        // 设置函数返回值为int
        add.setRetVal(Type.INT);
        // add函数的第一个参数是3
        add.setParameter(0, 3);
        // add函数的第二个参数是4
        add.setParameter(1, 4);  
        // 执行
        add.invoke();
        // 获取结果
        int result = Integer.parseInt(add.getRetVal());  //获取返回结果
        System.out.println(result);
    }
}

JNative 已经很久没有更新了,上一次更新是 2013-04-26

3. JNA

pom.xml

<dependency>
    <groupId>com.sun.jna</groupId>
    <artifactId>jna</artifactId>
    <version>3.0.9</version>
</dependency>

DllLibrary.java

public interface DllLibrary extends Library{
    int Add(int i, int j);
    float Avg(int i, int j);
}

DllImport.java

public class DllImport {
    public static void main(String[] args) {
        DllLibrary dll = (DllLibrary)Native.loadLibrary("exportdll.dll", DllLibrary.class);
        System.out.println(dll.Add(3, 4));
        System.out.println(dll.Avg(3, 4));
    }
}

小结

哪有什么岁月静好,不过是有人替你负重前行;以上三种方法语法越简单的就意味着性能越差,毕竟有很多中间商在替你负重前行。

性能:JNI > JNative >> JNA