feat: add csharp interop code generation but no test.

- add csharp interop code generation without testing.
- add annotation for bmap binding project.
- fix README.
This commit is contained in:
2024-04-23 16:01:26 +08:00
parent 8a1fc03965
commit a2fb376231
6 changed files with 421 additions and 25 deletions

View File

@ -1,12 +1,283 @@
import java.io.OutputStreamWriter;
import java.util.Vector;
import java.util.stream.Collectors;
public class CSharpWriter {
/**
* The class represent the C# type corresponding to extracted variable type.
*/
private static class CsInteropType {
public CsInteropType() {
mMarshalAs = null;
mCsType = null;
}
/**
* The argument of MarshalAsAttribute constructor. In generation, this field
* should be used like this: "[MarshalAs(THIS)]" (for parameter) or "[return:
* MarshalAs(THIS)]" (for return value).
*/
public String mMarshalAs;
/**
* The C# type used in interop function declaration for corresponding parameter.
*/
public String mCsType;
}
/**
* C# specified function which get C# used interop MarshalAs constructor
* arguments and C# type used in interop function declaration.
*
* @param vt The instance of {@linkplain VariableType} for fetching interop
* type.
* @return The corresponding interop type of given variable type.
*/
private static CsInteropType getCsInteropType(ExpFctParamDecl paramdecl) {
// get essential variable type properties first
VariableType vt = paramdecl.mVarType;
String vt_base_type = vt.getBaseType();
int vt_pointer_level = vt.getPointerLevel();
// create return value
CsInteropType ret = new CsInteropType();
// use "switch" to check variable type
switch (vt_base_type) {
case "CKSTRING":
// only allow 0 and 1 pointer level for string.
switch (vt_pointer_level) {
case 0:
ret.mMarshalAs = "UnmanagedType.LPUTF8Str";
ret.mCsType = "string";
break;
case 1:
ret.mMarshalAs = "UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(BMStringArrayMashaler)";
ret.mCsType = "string[]";
break;
}
break;
case "CKDWORD":
if (vt_pointer_level == 0) {
ret.mMarshalAs = "UnmanagedType.U4";
ret.mCsType = "uint";
} else {
ret.mMarshalAs = "UnmanagedType.SysInt";
ret.mCsType = "IntPtr";
}
break;
case "CKWORD":
if (vt_pointer_level == 0) {
ret.mMarshalAs = "UnmanagedType.U2";
ret.mCsType = "ushort";
} else {
ret.mMarshalAs = "UnmanagedType.SysInt";
ret.mCsType = "IntPtr";
}
break;
case "CKINT":
if (vt_pointer_level == 0) {
ret.mMarshalAs = "UnmanagedType.I4";
ret.mCsType = "int";
} else {
ret.mMarshalAs = "UnmanagedType.SysInt";
ret.mCsType = "IntPtr";
}
break;
case "bool":
if (vt_pointer_level == 0) {
ret.mMarshalAs = "UnmanagedType.U1";
ret.mCsType = "bool";
} else {
ret.mMarshalAs = "UnmanagedType.SysInt";
ret.mCsType = "IntPtr";
}
break;
case "CKFLOAT":
if (vt_pointer_level == 0) {
ret.mMarshalAs = "UnmanagedType.R4";
ret.mCsType = "float";
} else {
ret.mMarshalAs = "UnmanagedType.SysInt";
ret.mCsType = "IntPtr";
}
break;
case "CKBYTE":
if (vt_pointer_level == 0) {
ret.mMarshalAs = "UnmanagedType.U1";
ret.mCsType = "byte";
} else {
ret.mMarshalAs = "UnmanagedType.SysInt";
ret.mCsType = "IntPtr";
}
case "CK_ID":
if (vt_pointer_level == 0) {
ret.mMarshalAs = "UnmanagedType.U4";
ret.mCsType = "uint";
} else {
ret.mMarshalAs = "UnmanagedType.SysInt";
ret.mCsType = "IntPtr";
}
break;
case "NakedOutputCallback":
// callback actually is a function pointer
// so it only allow base type without any pointer level.
if (vt_pointer_level == 0) {
ret.mMarshalAs = "UnmanagedType.FunctionPtr";
ret.mCsType = "OutputCallback";
}
break;
case "BMFile":
// In any case, BMFile only should be raw pointer
if (vt_pointer_level != 0) {
ret.mMarshalAs = "UnmanagedType.SysInt";
ret.mCsType = "IntPtr";
}
break;
case "BMMeshTransition":
// In any case, BMMeshTransition only should be raw pointer
if (vt_pointer_level != 0) {
ret.mMarshalAs = "UnmanagedType.SysInt";
ret.mCsType = "IntPtr";
}
break;
case "VxVector3":
if (vt_pointer_level == 0) {
ret.mMarshalAs = "UnmanagedType.Struct";
ret.mCsType = "VxVector3";
} else {
ret.mMarshalAs = "UnmanagedType.SysInt";
ret.mCsType = "IntPtr";
}
break;
case "VxVector2":
if (vt_pointer_level == 0) {
ret.mMarshalAs = "UnmanagedType.Struct";
ret.mCsType = "VxVector2";
} else {
ret.mMarshalAs = "UnmanagedType.SysInt";
ret.mCsType = "IntPtr";
}
break;
case "VxColor":
if (vt_pointer_level == 0) {
ret.mMarshalAs = "UnmanagedType.Struct";
ret.mCsType = "VxColor";
} else {
ret.mMarshalAs = "UnmanagedType.SysInt";
ret.mCsType = "IntPtr";
}
break;
case "VxMatrix":
if (vt_pointer_level == 0) {
ret.mMarshalAs = "UnmanagedType.Struct";
ret.mCsType = "VxMatrix";
} else {
ret.mMarshalAs = "UnmanagedType.SysInt";
ret.mCsType = "IntPtr";
}
break;
case "CK_TEXTURE_SAVEOPTIONS":
case "VX_PIXELFORMAT":
case "VXTEXTURE_BLENDMODE":
case "VXTEXTURE_FILTERMODE":
case "VXTEXTURE_ADDRESSMODE":
case "VXBLEND_MODE":
case "VXFILL_MODE":
case "VXSHADE_MODE":
case "VXCMPFUNC":
case "VXMESH_LITMODE":
// all enum share the same underlying type.
if (vt_pointer_level == 0) {
ret.mMarshalAs = "UnmanagedType.U4";
ret.mCsType = "uint";
} else {
ret.mMarshalAs = "UnmanagedType.SysInt";
ret.mCsType = "IntPtr";
}
break;
}
// check whether we successfully get result
if (ret.mMarshalAs == null || ret.mCsType == null) {
throw new IllegalArgumentException("Unexpected type: " + vt.toCType());
}
// return value
return ret;
}
public static void writeCSharpCode(Vector<ExpFctDecl> data) throws Exception {
OutputStreamWriter writer = CommonHelper.openWriter("dest/BMExports.cs");
writer.write("// WIP");
IndentHelper helper = new IndentHelper(writer);
// write function decls
helper.puts("");
helper.puts("#region Function Defines");
helper.puts("");
for (ExpFctDecl fctdecl : data) {
// write annotation
// summary (just plain function name)
helper.puts("/// <summary>");
helper.printf("/// %s", fctdecl.mFctName);
helper.puts("/// </summary>");
// parameter list
for (ExpFctParamDecl paramdecl : fctdecl.mFctParams) {
helper.printf("/// <param name=\"%s\">Type: %s. %s%s</param>", paramdecl.mVarName,
paramdecl.mVarType.toCType(), (paramdecl.mIsInput ? "" : "This is OUT parameter. "),
paramdecl.mVarDesc);
}
// return value
helper.puts("/// <returns>True if no error, otherwise False.</returns>");
// write real declaration
// first, write DllImportAttribute
helper.printf(
"[DllImport(g_DllName, EntryPoint = \"%s\", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]",
fctdecl.mFctName);
// second, write return value MarshalAsAttribute
helper.printf("[return: MarshalAs(UnmanagedType.U1)]");
// then, before we write function body, we need origanize its parameter list
// first
Vector<String> cs_param_list = new Vector<String>();
for (ExpFctParamDecl paramdecl : fctdecl.mFctParams) {
// create string builder
StringBuilder sb = new StringBuilder();
// push in out symbol
if (paramdecl.mIsInput) {
sb.append("[In, ");
} else {
sb.append("[Out, ");
}
// get interop type now
CsInteropType interop_type = getCsInteropType(paramdecl);
// push MarshalAsAttribute
sb.append("MarshalAs(");
sb.append(interop_type.mMarshalAs);
sb.append(") ");
// push out keyword if parameter is out parameter
if (!paramdecl.mIsInput) {
sb.append("out ");
}
// push parameter cs type
sb.append(interop_type.mCsType);
sb.append(" ");
// push parameter name
sb.append(paramdecl.mVarName);
// insert built string into list
cs_param_list.add(sb.toString());
}
// join built parameter list and output real function declaration
helper.printf("internal static extern bool %s(%s);", fctdecl.mFctName,
cs_param_list.stream().collect(Collectors.joining(", ")));
}
helper.puts("");
helper.puts("#endregion");
helper.puts("");
writer.close();
}
}

View File

@ -1,16 +1,29 @@
import java.util.Vector;
/**
* The class represent an export BMap function.
*/
public class ExpFctDecl {
/**
* The name of this function.
*/
public String mFctName;
/**
* The return value type of this function.
*/
public VariableType mFctRetType;
/**
* The parameters (arguments) list of this function. Each items are
* {@linkplain ExpFctParamDecl} and represent parameter one by one from left to
* right.
*/
public Vector<ExpFctParamDecl> mFctParams;
public ExpFctDecl() {
mFctName = "";
mFctRetType = new VariableType();
mFctParams = new Vector<ExpFctParamDecl>();
}
}
}

View File

@ -1,16 +1,48 @@
/**
* The class represent a single parameter (argument) of function. This class
* usually is the member of {@linkplain ExpFctDecl}.
*/
public class ExpFctParamDecl {
/**
* The type of this parameter.
*/
public VariableType mVarType;
/**
* The name of this parameter.
*/
public String mVarName;
/**
* True if this paramter is marked as input parameter, otherwise false.
* <p>
* Input parameter and output paramter is commonly used in C/C++ code. By using
* this feature, each function can receive multiple arguments and return
* multiple arguments without defining a struct to hold it.
* <p>
* The type of input parameter is itself. However, the type of output parameter
* is the pointer of itself. So you may need get its pointer type when
* processing output paramter, especially for the scenario that the target
* language do not support explicit output parameter keyword.
*/
public boolean mIsInput;
/**
* The description of this parameter.
* <p>
* This description is generated by this program. It will indicate the
* underlying C++ type to tell end user how to treat this paramter because some
* target languages' native calling style can not represent these detail.
* <p>
* In this program, this field must be written as a annotation of corresponding
* function.
*/
public String mVarDesc;
public ExpFctParamDecl() {
mVarType = new VariableType();
mVarName = "";
mVarDesc = "";
mIsInput = true;
}
}

View File

@ -39,19 +39,29 @@ public class PythonWriter {
return Collections.unmodifiableMap(cache);
}
public static String pythonTypeGetter(ExpFctParamDecl paramdecl) {
private static String pythonTypeGetter(ExpFctParamDecl paramdecl) throws IllegalArgumentException {
VariableType vt = paramdecl.mVarType;
if (!paramdecl.mIsInput) {
vt = vt.getPointerOfThis();
}
// create string builder for build final type string
StringBuilder sb = new StringBuilder();
// add type prefix
sb.append("bm_");
sb.append(g_CppTypeMap.get(vt.getBaseType()));
// try getting cpp type from base type
String cpp_type = g_CppTypeMap.get(vt.getBaseType());
if (cpp_type == null) {
throw new IllegalArgumentException("Unexpected type: " + vt.toCType());
}
// assign cpp type
sb.append(cpp_type);
// add pointer suffix
if (vt.isPointer()) {
sb.append("_");
sb.append(String.join("", Collections.nCopies(vt.getPointerLevel(), "p")));
}
// return built type string.
return sb.toString();
}
@ -63,11 +73,11 @@ public class PythonWriter {
CommonHelper.writeSnippet(writer, "snippets/header.py");
// write function decls
helper.puts("");
helper.puts("#region Function Defines");
helper.puts("");
for (ExpFctDecl fctdecl : data) {
// write annotation
// function name
@ -90,7 +100,7 @@ public class PythonWriter {
helper.puts("");
helper.puts("#endregion");
helper.puts("");
writer.close();
}

View File

@ -2,28 +2,51 @@ import java.util.Collections;
import java.util.Vector;
import java.util.stream.Collectors;
/**
* The class represent the type of each parameters and function return value.
*/
public class VariableType {
/**
* The base type of this variable which remove all ending stars. Each item is a
* part of namespace string. If no namespace, this Vector will only have one
* item.
* The base type of this variable removing all ending stars (remove all pointer
* levels) Each item in this a part of namespace and the last one must be the
* type itself (without any namespace restriction). If no namespace restriction
* for this type, this Vector will only have one item.
* <p>
* For end user, it is enough that knowing the last item is type itself.
*/
private Vector<String> mBaseType;
/**
* The pointer level of this type. It is equal with the count of stars.
* The pointer level of this type. It is equal to the count of trailing star of
* this field in C style representation.
*/
private int mPointerLevel;
/**
* Construct an empty varible type. This is commonly used constructor.
*/
public VariableType() {
mBaseType = new Vector<String>();
mPointerLevel = 0;
}
/**
* The constructor used for cloning self. This constructor is only can be used
* by self.
*
* @param base_type The hierarchy of the variable type.
* @param pointer_level The pointer level of new created variable type.
*/
private VariableType(Vector<String> base_type, int pointer_level) {
mBaseType = (Vector<String>) base_type.clone();
mPointerLevel = pointer_level;
}
/**
* Set this variable type with a type string in C/C++ style. For example
* "NSTest::NSTest2::MyType**".
*
* @param ctype The type string in C/C++ style.
*/
public void fromCType(String ctype) {
if (ctype.isEmpty())
throw new IllegalArgumentException("empty string can not be parsed.");
@ -43,7 +66,7 @@ public class VariableType {
namepart = ctype.substring(0, star_pos);
mPointerLevel = len - star_pos;
}
// resolve name part
mBaseType.clear();
for (String item : namepart.split("::")) {
@ -51,31 +74,78 @@ public class VariableType {
}
}
/**
* Build a type string represented by this variable type in C/C++ style.
*
* @return The type string in C/C++ style.
*/
public String toCType() {
return mBaseType.stream().collect(Collectors.joining("::"))
+ String.join("", Collections.nCopies(mPointerLevel, "*"));
}
/**
* Get the base type of this variable type without any namespace. It just simply
* get the last entry in type hierarchy.
*
* @return The base type string without namespace prefix.
*/
public String getBaseType() {
return mBaseType.lastElement();
}
/**
* Check whether this variable type is a pointer. This function just check
* whether the pointer level of this variavle type is zero.
*
* @return True if it is pointer, otherwise false.
*/
public boolean isPointer() {
return mPointerLevel != 0;
}
/**
* Return the pointer level of this variable type. You can simply assume the
* pointer level is equal to the count of trailing star.
*
* @return The pointer level integer. Zero means that this type is not a
* pointer.
*/
public int getPointerLevel() {
return mPointerLevel;
}
/**
* Return the clone of the type hierarchy of this variable type.
* <p>
* It is rarely used. This only should be used when you need the namespace
* hierarchy of this variable type.
*
* @return The clone of current variable type hierarchy.
*/
public Vector<String> getBaseTypeHierarchy() {
return (Vector<String>) mBaseType.clone();
}
/**
* Check whether this type is a valid one. It actually check whether type
* hierarchy include at least one entry.
*
* @return True if no problem of this type, otherwise false.
*/
public boolean isValid() {
return mBaseType.size() != 0;
}
/**
* Return a new created variable type which is the pointer of this variable
* type.
* <p>
* In internal implementation, it just create a clone of current variable type
* with the increase of pointer level by 1.
*
* @return The new created pointer type of this variable type.
*/
public VariableType getPointerOfThis() {
return new VariableType(mBaseType, mPointerLevel + 1);
}