Skip to content

Commit

Permalink
API: Initial version of the new API wrappers generator with C/C++ sup…
Browse files Browse the repository at this point in the history
…port.
  • Loading branch information
agdavydov81 committed Oct 6, 2023
1 parent 4a5c924 commit 42d386f
Show file tree
Hide file tree
Showing 7 changed files with 972 additions and 305 deletions.
12 changes: 9 additions & 3 deletions java/nativeWrappers/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@ task makeNativeWrappersDfp(dependsOn: compileJava, type: JavaExec) {
"com_epam_deltix_dfp_NativeImpl_",
"$rootDir/native/NativeImpl.c",
"$rootDir/java/dfp/build/generated/sources/nativeWrappers/com/epam/deltix/dfp/NativeImpl.java",
"$rootDir/csharp/EPAM.Deltix.DFP/NativeImpl.cs"
"$rootDir/csharp/EPAM.Deltix.DFP/NativeImpl.cs",
"$rootDir/native/DecimalNative.h",
"$rootDir/native/DecimalNative.hpp"
]
onlyIf {
!file("$rootDir/java/dfp/build/generated/sources/nativeWrappers/com/epam/deltix/dfp/NativeImpl.java").exists() ||
!file("$rootDir/native/DecimalNative.h").exists() ||
!file("$rootDir/native/DecimalNative.hpp").exists() ||
!file("$rootDir/java/dfp/build/generated/sources/nativeWrappers/com/epam/deltix/dfp/NativeImpl.java").exists() ||
!file("$rootDir/csharp/EPAM.Deltix.DFP/NativeImpl.cs").exists()
}
}
Expand All @@ -36,7 +40,9 @@ task makeNativeWrappersDfpMath(dependsOn: compileJava, type: JavaExec) {
"com_epam_deltix_dfpmath_NativeMathImpl_",
"$rootDir/native/NativeMathImpl.c",
"$rootDir/java/dfp-math/build/generated/sources/nativeWrappers/com/epam/deltix/dfpmath/NativeMathImpl.java",
"$rootDir/csharp/EPAM.Deltix.DFPMath/NativeMathImpl.cs"
"$rootDir/csharp/EPAM.Deltix.DFPMath/NativeMathImpl.cs",
"",
""
]
onlyIf {
!file("$rootDir/java/dfp-math/build/generated/sources/nativeWrappers/com/epam/deltix/dfpmath/NativeMathImpl.java").exists() ||
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.epam.deltix.dfp;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ApiEntry {
public final String returnType;
public final String name;
public final String arguments;

public ApiEntry(final String returnType, final String name, final String arguments) {
this.returnType = returnType;
this.name = name;
this.arguments = arguments;
}

public static List<ApiEntry> collectApi(String body, final String apiPrefix) {
body = body
.replaceAll("\\b__declspec\\s*\\(\\s*dllexport\\s*\\)", "")
.replaceAll("\\b__cdecl\\b", "")
.replaceAll("\\b__stdcall\\b", "");

final Matcher matcher = Pattern.compile("(?<=^|[;}])\\s*([^;}]*?)\\s+(" + apiPrefix + "\\w+)\\s*\\((.*?)\\)\\s*"
// https://stackoverflow.com/questions/47162098/is-it-possible-to-match-nested-brackets-with-a-regex-without-using-recursion-or/47162099#47162099
// + "(?=\\{)(?:(?=.*?\\{(?!.*?\\1)(.*\\}(?!.*\\2).*))(?=.*?\\}(?!.*?\\2)(.*)).)+?.*?(?=\\1)[^{]*(?=\\2$)"
// https://stackoverflow.com/questions/17759004/how-to-match-string-within-parentheses-nested-in-java
+ "\\{([^{}]*|\\{([^{}]*|\\{[^{}]*\\})*\\})*\\}"
).matcher(body);

final List<ApiEntry> api = new ArrayList<>();
while (matcher.find())
api.add(new ApiEntry(matcher.group(1).trim(), matcher.group(2).trim(), matcher.group(3).trim()));

return api;
}

public static final Pattern cppArgRegEx = Pattern.compile("^(.*?)(\\w+)$");

static final String gccAttributePattern = "\\b__attribute__\\s*" +
// https://stackoverflow.com/questions/17759004/how-to-match-string-within-parentheses-nested-in-java
"\\(([^()]*|\\(([^()]*|\\([^()]*\\))*\\))*\\)";

public static String getCppType(String type) {
type = type.replaceAll(gccAttributePattern, "")
.replaceAll("\\bconst\\b", "")
.replace("\\bextern\\b", "");

return type.trim();
}
}
168 changes: 168 additions & 0 deletions java/nativeWrappers/src/main/java/com/epam/deltix/dfp/CsWrappers.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package com.epam.deltix.dfp;

import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.epam.deltix.dfp.ApiEntry.cppArgRegEx;
import static com.epam.deltix.dfp.ApiEntry.getCppType;

public class CsWrappers {
public static void make(final String outputFile, final List<ApiEntry> api, final String apiPrefix) throws IOException {
final int apiPrefixLength = apiPrefix.length();

final Path outputPath = Paths.get(outputFile);

String outputClass = outputPath.getFileName().toString();
if (!outputClass.endsWith(".cs"))
throw new RuntimeException("Can't determine C# the output class name for the outputFile(=" + outputFile + ").");
else
outputClass = outputClass.substring(0, outputClass.length() - 3);

final String outputNamespace = outputPath.getParent().getFileName().toString();

try (final BufferedWriter writer = Files.newBufferedWriter(outputPath, StandardCharsets.UTF_8)) {
writer.write("using System;\n" +
"using System.Runtime.InteropServices;\n" +
"\n" +
"namespace " + outputNamespace + "\n" +
"{\n" +
"\t//Just entries\n" +
"\tinternal static class " + outputClass + "Import\n" +
"\t{\n" +
"\t\tinternal const string libName = \"" + apiPrefix.substring(0, apiPrefixLength - 1) + "\";\n" +
"\t\tinternal const CallingConvention callType = CallingConvention.Cdecl;\n"
//+ "\t\tinternal const CharSet stringCharset = CharSet.Ansi;\n"
);

final StringBuilder objClassBody = new StringBuilder();
objClassBody.append("\t\tstatic " + outputClass + "Obj()\n" +
"\t\t{\n" +
"\t\t\t" + outputClass + "Loader.Load();\n" +
"\t\t}\n");

final StringBuilder nativeClassBody = new StringBuilder();
nativeClassBody.append("\t\tinternal static readonly " + outputClass + "Obj impl = new " + outputClass + "Obj();\n");

for (final ApiEntry entry : api) {
writer.write("\n\t\t[DllImport(libName, CallingConvention = callType)]\n");

final String csRetType = cppTypeToCs(entry.returnType);
writer.write("\t\tinternal static extern " + csRetType + " " + entry.name + "(");

objClassBody.append("\n\t\tinternal ").append(csRetType).append(" ").append(entry.name).append("(");

nativeClassBody.append("\n\t\tinternal static ").append(csRetType).append(" ").append(entry.name.replace(apiPrefix, "")).append("(");

final String[] args = entry.arguments.split(",");
final StringBuilder csArgs = new StringBuilder();
final StringBuilder csCall = new StringBuilder();
for (int ai = 0; ai < args.length; ++ai) {
if (ai > 0) {
csArgs.append(", ");
csCall.append(", ");
}
final String cppArg = args[ai].trim();
final Matcher cppArgMatcher = cppArgRegEx.matcher(cppArg);
if (!cppArgMatcher.matches())
throw new RuntimeException("Can't parse c++ argument(=" + cppArg + ").");
csArgs.append("[In] ").append(cppTypeToCs(cppArgMatcher.group(1))).append(" ").append(cppArgMatcher.group(2).trim());
csCall.append(cppArgMatcher.group(2).trim());
}
final String csArgsStr = csArgs.toString();
final String csCallStr = csCall.toString();

writer.write(csArgsStr);
writer.write(");\n");

objClassBody.append(csArgsStr).append(") =>\n\t\t\t" + outputClass + "Import.")
.append(entry.name).append("(").append(csCallStr).append(");\n");

nativeClassBody.append(csArgsStr).append(") =>\n\t\t\timpl.")
.append(entry.name).append("(").append(csCallStr).append(");\n");
}

writer.write("\t}\n\n");

writer.write("\t//Mono problem workaround\n");
writer.write("\tinternal class " + outputClass + "Obj\n" +
"\t{\n");
writer.write(objClassBody.toString());
writer.write("\t}\n\n");

writer.write("\t//Actual API class\n");
writer.write("\tinternal static class " + outputClass + "\n" +
"\t{\n");
writer.write(nativeClassBody.toString());
writer.write("\t}\n");

writer.write("}\n");
}
}

private static String cppTypeToCs(String type) {
type = getCppType(type);
switch (type) {
case "_Decimal64":
case "decimal64":
case "D64Bits":
case "BID_UINT64":
return "UInt64";
case "int8":
case "Int8":
return "SByte";
case "uint8":
case "UInt8":
return "Byte";
case "int16":
case "Int16":
return "Int16";
case "uint16":
case "UInt16":
return "UInt16";
case "int32":
case "Int32":
return "Int32";
case "uint32":
case "UInt32":
return "UInt32";
case "int64":
case "Int64":
return "Int64";
case "uint64":
case "UInt64":
return "UInt64";
case "float":
case "Float32":
return "float";
case "double":
case "Float64":
return "double";
case "intBool":
return "bool";
default:
throw new RuntimeException("Can't convert C++ type (='" + type + "') to Cs type.");
}
}

public static void makeVersion(final String outputFile, final String versionThreeDigits, final String versionSuffix, final String versionSha) throws IOException {
try (final BufferedWriter writer =
Files.newBufferedWriter(Paths.get(outputFile).getParent().resolve("Version.targets"),
StandardCharsets.UTF_8)) {
writer.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
"<Project>\n" +
"\t<PropertyGroup>\n" +
"\t\t<Version>" + versionThreeDigits + ".0</Version>\n" +
"\t\t<VersionSuffix>" + versionSuffix + "</VersionSuffix>\n" +
"\t\t<VersionSha>" + versionSha + "</VersionSha>\n" +
"\t</PropertyGroup>\n" +
"</Project>\n");
}
}
}
Loading

0 comments on commit 42d386f

Please sign in to comment.