forked from deltixlab/DFP
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
API: Initial version of the new API wrappers generator with C/C++ sup…
…port.
- Loading branch information
1 parent
4a5c924
commit 42d386f
Showing
7 changed files
with
972 additions
and
305 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
52 changes: 52 additions & 0 deletions
52
java/nativeWrappers/src/main/java/com/epam/deltix/dfp/ApiEntry.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
168
java/nativeWrappers/src/main/java/com/epam/deltix/dfp/CsWrappers.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | ||
} | ||
} | ||
} |
Oops, something went wrong.