缘起
项目中调用了第三方一个 Matlab 实现的数据清洗和机组状态评价的算法,但是对方不会除 Matlab 外的其他语言,最后只用 Matlab 生成了一个 dll/lib 文件。由于对方提供的接口质量真心不好,而且对方也无力修改。最终方案只好由我方在其基础上用 C++ 重新包装一下。在给我方 Client 调用时,需要把从多数据源查询数据的细节封装掉,最终就形成了 Java Client-> Java Interface->C++ Interface->Matlab/C++ Interface (第三方) 这样一个诡异的调用链。
因只是 Java 单向调用 C++ 接口,故通过 [JNA][jna] 实现。主要设计到 JNA 的结构体封装,指针声明及取值,结构体指针定义及取值。
实现
C++ 的封装接口逻辑非常简单,根据业务提供数据清洗和机组评价的两个接口。
由于 Matlab 导出的 dll 效率是真心差,尤其时加载的时候,各种抛异常,每次加载 dll 大约需耗时 20~30s。所以不能每次加载,故提供 init/terminator
实现按需加载及停用。
C/C++ 头文件定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
| #ifndef __CALCULATOR_API_H__ #define __CALCULATOR_API_H__ #include <cstdint>
#ifdef WIN32 # define EXPORT_API __declspec(dllexport) #else # define EXPORT_API #endif
#define CALCULATOR_ENOBASE 112345678
enum { CALCULATOR_EXCEPTION_ILLEGAL_FUNCTION = 0x01, CALCULATOR_EXCEPTION_ILLEGAL_PARA };
#define EMBXILFUN (CALCULATOR_ENOBASE + CALCULATOR_EXCEPTION_ILLEGAL_FUNCTION) #define EMBXILPARA (CALCULATOR_ENOBASE + CALCULATOR_EXCEPTION_ILLEGAL_PARA)
typedef enum { H2 = 0X01, CH4, C2H2, C2H4, TOTAL_HYDROCARBON, CO, CO2 } OilChromatographyType;
typedef struct sImproveResult { int32_t valid; float validPercent; float* abnormalData; int32_t abnormal_size; float* optimizeData; int32_t optimize_size; } sImproveResult, *ImproveResult;
typedef struct sTransformer { float h2Value; float totalHydrocarbon; float c2h2Value; float coValue; float co2Value; float dielectricLoss; float dcResistance; float absorptance; float polarizeFactor; float electricCapacity; float moisture; float breakdownVoltage; float oilDielectricLoss; float interfacialTension; float electricResistivity; int16_t runningLife; float ambientTemperature; float jointTemperature; float oilTankTemperature; } sTransformer, *Transformer;
typedef struct sFusionResult { float *h1; int16_t h1_size; float *h2; int16_t h2_size; float *h3; int16_t h3_size; float *h4; int16_t h4_size; float qz1; float qz2; float qz3; float qz4; float m; float n; } sFusionResult, *FusionResult;
typedef struct sFactorResult { float* state; int16_t state_size; int8_t* comment; int16_t comment_len; } sFactorResult, *FactorResult;
typedef struct { int64_t time; float value; } sImproveInput, *ImproveInput;
typedef struct sReviewResult { Transformer indexScore; int16_t indexScore_size; FusionResult indexFusion; FactorResult factorFusion; } sReviewResult, *ReviewResult;
#ifdef __cplusplus extern "C" { #endif
EXPORT_API int16_t improve_data(const ImproveInput original, const int32_t originalLen, ImproveResult result);
EXPORT_API int16_t review_transformer(const Transformer transformer, ReviewResult result);
EXPORT_API int16_t init();
EXPORT_API const int8_t* calculator_strerror(int errnum);
EXPORT_API void terminator();
#ifdef __cplusplus } #endif #endif
|
Java 接口定义
1 2 3 4 5 6 7 8 9 10 11 12
| import com.sun.jna.Library;
public interface CalculatorApi extends Library {
int improve_data(ImproveInputWrapper.ByReference original, int originalLen, ImproveResultWrapper result);
int init();
int review_transformer(TransformerWrapper transformer, ReviewResult result);
void terminator(); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import com.sun.jna.Pointer; import com.sun.jna.Structure;
import java.util.List;
public class FactorResultWrapper extends Structure { private static final List<String> FIELDS_ORDER = createFieldsOrder("state", "state_size", "comment", "comment_len"); public Pointer state; public int state_size; public String comment; public int comment_len;
public static class ByReference extends FactorResultWrapper implements Structure.ByReference { }
public static class ByValue extends FactorResultWrapper implements Structure.ByValue { }
public FactorResultWrapper() { super(); }
@Override protected List<String> getFieldOrder() { return FIELDS_ORDER; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import com.sun.jna.Pointer; import com.sun.jna.Structure;
import java.util.List;
public class FusionResultWrapper extends Structure { private static final List<String> FIELDS_ORDER = createFieldsOrder("h1", "h1_size", "h2", "h2_size", "h3", "h3_size", "h4", "h4_size", "qz1", "qz2", "qz3", "qz4", "m", "n"); public Pointer h1; public int h1_size; public Pointer h2; public int h2_size; public Pointer h3; public int h3_size; public Pointer h4; public int h4_size; public float qz1; public float qz2; public float qz3; public float qz4; public float m; public float n;
public static class ByReference extends FusionResultWrapper implements Structure.ByReference { }
public static class ByValue extends FusionResultWrapper implements Structure.ByValue { }
@Override protected List<String> getFieldOrder() { return FIELDS_ORDER; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import com.sun.jna.Structure;
import java.util.List;
public class ImproveInputWrapper extends Structure { private static final List<String> FIELDS_ORDER = createFieldsOrder("time", "value"); public long time; public float value;
public ImproveInputWrapper() { }
public static class ByReference extends ImproveInputWrapper implements Structure.ByReference { }
@Override protected List<String> getFieldOrder() { return FIELDS_ORDER; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import com.sun.jna.Pointer; import com.sun.jna.Structure;
import java.util.List;
public class ImproveResultWrapper extends Structure { private static final List<String> FIELDS_ORDER = createFieldsOrder("valid", "validPercent", "abnormalData", "abnormal_size", "optimizeData", "optimize_size");
public int valid; public float validPercent; public Pointer abnormalData; public int abnormal_size; public Pointer optimizeData; public int optimize_size;
public static class ByReference extends ImproveResultWrapper implements Structure.ByReference { }
@Override protected List<String> getFieldOrder() { return FIELDS_ORDER; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| import com.sun.jna.Memory; import com.sun.jna.Native; import com.sun.jna.Structure;
import java.util.List;
public class ReviewResult extends Structure { private final int STATE_SIZE = 5; private static final List<String> FIELDS_ORDER = createFieldsOrder("indexScore", "indexScore_size", "indexFusion", "factorFusion"); public TransformerWrapper.ByReference indexScore; public int indexScore_size; public FusionResultWrapper.ByReference indexFusion; public FactorResultWrapper.ByReference factorFusion;
public ReviewResult() { super(); if (indexScore_size == 0) indexScore_size = STATE_SIZE; indexScore = new TransformerWrapper.ByReference(); TransformerWrapper[] wrappers = (TransformerWrapper[]) indexScore.toArray(indexScore_size); for (TransformerWrapper wrapper : wrappers) { wrapper.h2Value = 0f; wrapper.totalHydrocarbon = 0f; wrapper.c2h2Value = 0f; wrapper.coValue = 0f; wrapper.co2Value = 0f; wrapper.dielectricLoss = 0f; wrapper.dcResistance = 0f; wrapper.absorptance = 0f; wrapper.polarizeFactor = 0f; wrapper.electricCapacity = 0f; wrapper.moisture = 0f; wrapper.breakdownVoltage = 0f; wrapper.oilDielectricLoss = 0f; wrapper.interfacialTension = 0f; wrapper.electricResistivity = 0f; wrapper.runningLife = 0; wrapper.ambientTemperature = 0f; wrapper.jointTemperature = 0f; wrapper.oilTankTemperature = 0f; } indexFusion = new FusionResultWrapper.ByReference(); indexFusion.h1 = new Memory(STATE_SIZE * Native.getNativeSize(Float.TYPE)); indexFusion.h1_size = STATE_SIZE; indexFusion.h2 = new Memory(STATE_SIZE * Native.getNativeSize(Float.TYPE)); indexFusion.h2_size = STATE_SIZE; indexFusion.h3 = new Memory(STATE_SIZE * Native.getNativeSize(Float.TYPE)); indexFusion.h3_size = STATE_SIZE; indexFusion.h4 = new Memory(STATE_SIZE * Native.getNativeSize(Float.TYPE)); indexFusion.h4_size = STATE_SIZE; factorFusion = new FactorResultWrapper.ByReference(); factorFusion.state = new Memory(STATE_SIZE * Native.getNativeSize(Float.TYPE)); factorFusion.state_size = STATE_SIZE; factorFusion.comment = ""; factorFusion.comment_len = 0; }
public static class ByReference extends ReviewResult implements Structure.ByReference { }
@Override protected List<String> getFieldOrder() { return FIELDS_ORDER; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| import com.sun.jna.Structure;
import java.util.List;
public class TransformerWrapper extends Structure { private static final List<String> FIELDS_ORDER = createFieldsOrder("h2Value", "totalHydrocarbon", "c2h2Value", "coValue", "co2Value", "dielectricLoss", "dcResistance", "absorptance", "polarizeFactor", "electricCapacity", "moisture", "breakdownVoltage", "oilDielectricLoss", "interfacialTension", "electricResistivity", "runningLife", "ambientTemperature", "jointTemperature", "oilTankTemperature");
public float h2Value; public float totalHydrocarbon; public float c2h2Value; public float coValue; public float co2Value; public float dielectricLoss; public float dcResistance; public float absorptance; public float polarizeFactor; public float electricCapacity; public float moisture; public float breakdownVoltage; public float oilDielectricLoss; public float interfacialTension; public float electricResistivity; public short runningLife; public float ambientTemperature; public float jointTemperature; public float oilTankTemperature;
public static class ByReference extends TransformerWrapper implements Structure.ByReference { } public static class ByValue extends TransformerWrapper implements Structure.ByValue { }
public TransformerWrapper() { super(); }
@Override protected List<String> getFieldOrder() { return FIELDS_ORDER; } }
|
CalculatorImpl
Java 接口的实现类,首先需要从 Jar 中解压 dll 到指定目录,然后通过此目录加载 dll。依赖关系为 Java 接口通过 JNA 加载 calculator.dll
,而 calculator.dll
依赖 pingjia.dll
和另外一个 dll。
三个 dll 必须在同一目录下, JNA 只需要加载 calculator.dll
。因为此处只是在 WIN32 平台执行,所以加载时,通过 Native.loadLibrary
加载的时候,在文件名前加了 /
,否则 JNA 会在文件前增加平台相关的 perfix 导致加载失败。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class CalculatorImpl implements Calculator { private static final Log LOGGER = Logs.getLog(CalculatorImpl.class); private static CalculatorApi CALCULATOR_API; static { try { String current = System.getProperty("user.dir"); File matlab = new File(current, "matlab"); System.setProperty("java.library.path", matlab.getPath()); System.setProperty("jna.library.path", matlab.getPath()); CALCULATOR_API = Native.loadLibrary("/calculator.dll", CalculatorApi.class); int ret = CALCULATOR_API.init(); LOGGER.debugf("init calculator (%d)", ret); } catch (Exception e) { LOGGER.error(e.getMessage(), e); } } }
|
使用
improveData
数据清洗,需要根据 Java Wrapper 的接口,组织数据,内存都在 Java 端分配,由 Java 端负责回收。Pointer
的内存分配,通过 new Memory(size)
来分配。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| private TwoTuple<String, CalculationResult> improveData(String label, List<TwoTuple<String, Float>> values) throws CalculatorException { ImproveInputWrapper.ByReference inputsRef = new ImproveInputWrapper.ByReference(); final int valueSize = values.size(); ImproveInputWrapper[] inputs = (ImproveInputWrapper[]) inputsRef.toArray(valueSize); LOGGER.debug(label + " inputs: "); for (int i = 0; i < valueSize; i++) { try { TwoTuple<String, Float> entry = values.get(i); Date date = FORMATTER.parse(entry.getKey()); inputs[i].time = date.getTime(); inputs[i].value = entry.getValue(); LOGGER.debugf("%d: %s(%d) -> %f", i, entry.getKey(), inputs[i].time, inputs[i].value); } catch (ParseException e) { LOGGER.error(e.getMessage(), e); } }
ImproveResultWrapper.ByReference impResultRef = new ImproveResultWrapper.ByReference(); impResultRef.abnormalData = new Memory(valueSize * Native.getNativeSize(Float.TYPE)); impResultRef.abnormalData.setFloat(0, 0); impResultRef.optimizeData = new Memory(valueSize * Native.getNativeSize(Float.TYPE)); impResultRef.optimizeData.setFloat(0, 0); impResultRef.abnormal_size = 0; impResultRef.validPercent = 0; impResultRef.optimize_size = 0;
int flag = CALCULATOR_API.improve_data(inputsRef, inputs.length, impResultRef); LOGGER.debug("improve_data flag: " + flag); CalculationResult.CalculationResultBuilder builder = CalculationResult.CalculationResultBuilder .aCalculationResult().withOriginalData(values); if (flag == 0) { builder.withValidPercent(impResultRef.validPercent).withValidSize(impResultRef.valid); int abnormalSize = impResultRef.abnormal_size; LOGGER.debug("abnormalSize: " + abnormalSize); if (abnormalSize > 0) { float[] abnormalValues = impResultRef.abnormalData.getFloatArray(0, abnormalSize); builder.withAbnormalData(Collections.unmodifiableList(Lang.array2list(abnormalValues, Float.class))); } else { builder.withAbnormalData(Collections.<Float>emptyList()); } int optimizeSize = impResultRef.optimize_size; LOGGER.debug("optimizeSize: " + optimizeSize); if (optimizeSize > 0) { float[] optimizeValues = impResultRef.optimizeData.getFloatArray(0, optimizeSize); builder.withOptimizeData(Collections.unmodifiableList(Lang.array2list(optimizeValues, Float.class))); } else { builder.withOptimizeData(Collections.<Float>emptyList()); } }
CalculationResult result = builder.build(); LOGGER.debug(result);
return new TwoTuple<>(label, result); }
|
reviewTransformer
机组状态评价,获取 float *
的数据时候的,需要通过 getFloatArray
获取数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| private TransformerResult reviewTransformer(Map<String, Float> values) throws CalculatorException { TransformerWrapper.ByReference transformerRef = new TransformerWrapper.ByReference(); setTransformer(transformerRef, values);
ReviewResult.ByReference reviewResultRef = new ReviewResult.ByReference(); int flag = CALCULATOR_API.review_transformer(transformerRef, reviewResultRef); LOGGER.debugf("review_transformer flag: %d", flag);
TransformerResult result = new TransformerResult(); if (flag == 0) { float[] states = reviewResultRef.factorFusion.state.getFloatArray(0, reviewResultRef.factorFusion.state_size); result.setState(states).setComment(reviewResultRef.factorFusion.comment);
float[] h1 = reviewResultRef.indexFusion.h1.getFloatArray(0, reviewResultRef.indexFusion.h1_size); result.setH1(h1); float[] h2 = reviewResultRef.indexFusion.h2.getFloatArray(0, reviewResultRef.indexFusion.h2_size); result.setH2(h2); float[] h3 = reviewResultRef.indexFusion.h3.getFloatArray(0, reviewResultRef.indexFusion.h3_size); result.setH3(h3); float[] h4 = reviewResultRef.indexFusion.h4.getFloatArray(0, reviewResultRef.indexFusion.h4_size); result.setH4(h4); result.setQz1(reviewResultRef.indexFusion.qz1).setQz2(reviewResultRef.indexFusion.qz2) .setQz3(reviewResultRef.indexFusion.qz3).setQz4(reviewResultRef.indexFusion.qz4) .setM(reviewResultRef.indexFusion.m).setN(reviewResultRef.indexFusion.n);
TransformerWrapper[] wrappers = (TransformerWrapper[]) reviewResultRef.indexScore.toArray(reviewResultRef.indexScore_size); Transformer[] transformers = transformerWrapperToTransformer(wrappers); result.setTransformers(transformers); LOGGER.debug("transfer review result."); }
return result; }
|
小结
- 优点
- 缺点
- 需要编写与 C/C++ 对应的结构体映射,碰到复杂的结构体工作量不小
- 结构体指针 / 数据通过
toArray
获取数据时,效率较低,尤其时数据量比较大的时候
- 如果时 C/C++ 端分配的内存,Java 端管理不了,如果 C/C++ 不提供显式回收接口,会导致内存泄露
- 代码不规范,破坏了 OO 封装性,比如 field 必须要 Public
- 需要实现
Structure.ByReference
接口,这些明显都可以通过注解来解决
[jna]:https://github.com/java-native-access/jna “Java Native Access”
—EOF—