CApiGenerator.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. using System.Runtime.InteropServices;
  2. using System.Text;
  3. using Mono.Cecil;
  4. using Mono.Collections.Generic;
  5. namespace PixiEditor.Api.CGlueMSBuild;
  6. public class CApiGenerator
  7. {
  8. private static readonly string[] excluded = new[] { "get_encryption_key", "get_encryption_iv" };
  9. private string InteropCContent { get; }
  10. private Action<string> Log { get; }
  11. public string? ResourcesEncryptionKey { get; set; }
  12. public string? ResourcesEncryptionIv { get; set; }
  13. public CApiGenerator(string interopCContent, string? resourcesEncryptionKey, string? resourcesEncryptionIv, Action<string> log)
  14. {
  15. InteropCContent = interopCContent;
  16. Log = log;
  17. ResourcesEncryptionKey = resourcesEncryptionKey;
  18. ResourcesEncryptionIv = resourcesEncryptionIv;
  19. }
  20. public string Generate(AssemblyDefinition assembly, string directory)
  21. {
  22. Log($"Reference assembly: {assembly.FullName}");
  23. var assemblies = LoadAssemblies(assembly, directory);
  24. var types = assemblies.SelectMany(a => a.MainModule.Types).ToArray();
  25. var importedMethods = GetImportedMethods(types);
  26. var exportedMethods = GetExportedMethods(types);
  27. StringBuilder sb = new StringBuilder();
  28. sb.AppendLine();
  29. sb.AppendLine("/* ----AUTOGENERATED---- */");
  30. sb.AppendLine();
  31. sb.Append(GenerateImports(importedMethods));
  32. sb.AppendLine(GenerateExports(exportedMethods));
  33. sb.AppendLine(GenerateAttachImportedFunctions(importedMethods));
  34. string final = InteropCContent;
  35. if (!string.IsNullOrEmpty(ResourcesEncryptionKey) && !string.IsNullOrEmpty(ResourcesEncryptionIv))
  36. {
  37. byte[] keyBytes = Convert.FromBase64String(ResourcesEncryptionKey);
  38. byte[] ivBytes = Convert.FromBase64String(ResourcesEncryptionIv);
  39. final = InteropCContent.Replace("static const uint8_t key[16] = { };",
  40. $"static const uint8_t key[16] = {{ {string.Join(", ", keyBytes.Select(b => b.ToString()))} }};");
  41. final = final.Replace("static const uint8_t iv[16] = { };",
  42. $"static const uint8_t iv[16] = {{ {string.Join(", ", ivBytes.Select(b => b.ToString()))} }};");
  43. }
  44. return final.Replace("void attach_imported_functions(){}", sb.ToString());
  45. }
  46. public static MethodDefinition[] GetExportedMethods(TypeDefinition[] types)
  47. {
  48. var exportedMethods = types
  49. .SelectMany(t => t.Methods)
  50. .Where(m => m.IsStatic && m.CustomAttributes.Any(a => a.AttributeType.FullName == "PixiEditor.Extensions.Sdk.ApiExportAttribute"))
  51. .ToArray();
  52. return exportedMethods;
  53. }
  54. public static MethodDefinition[] GetImportedMethods(TypeDefinition[] types)
  55. {
  56. var importedMethods = types
  57. .SelectMany(t => t.Methods)
  58. .Where(m => m.IsStatic && m.ImplAttributes == MethodImplAttributes.InternalCall && !IsExcluded(m))
  59. .ToArray();
  60. return importedMethods;
  61. }
  62. private static bool IsExcluded(MethodDefinition method)
  63. {
  64. return excluded.Any(ex => method.Name.StartsWith(ex, StringComparison.OrdinalIgnoreCase) ||
  65. method.DeclaringType.FullName.StartsWith(ex, StringComparison.OrdinalIgnoreCase));
  66. }
  67. public List<AssemblyDefinition> LoadAssemblies(AssemblyDefinition assembly, string directory)
  68. {
  69. var assemblies = assembly.MainModule.AssemblyReferences
  70. .Where(r => !r.Name.StartsWith("System") && !r.Name.StartsWith("Microsoft"))
  71. .Select(x =>
  72. {
  73. Log($"Loading assembly from {directory}/{x.Name}.dll");
  74. return AssemblyDefinition.ReadAssembly(Path.Combine(directory, x.Name + ".dll"));
  75. })
  76. .ToList();
  77. assemblies.Add(assembly);
  78. return assemblies;
  79. }
  80. public string GenerateImports(MethodDefinition[] importedMethods)
  81. {
  82. StringBuilder sb = new StringBuilder();
  83. foreach (var method in importedMethods)
  84. {
  85. sb.AppendLine(BuildImportFunction(method));
  86. sb.AppendLine(BuildAttachedFunction(method));
  87. }
  88. return sb.ToString();
  89. }
  90. public string GenerateExports(MethodDefinition[] exportedMethods)
  91. {
  92. StringBuilder sb = new StringBuilder();
  93. foreach (var method in exportedMethods)
  94. {
  95. sb.AppendLine(BuildExportFunction(method));
  96. }
  97. return sb.ToString();
  98. }
  99. public string GenerateAttachImportedFunctions(MethodDefinition[] importedMethods)
  100. {
  101. StringBuilder sb = new StringBuilder();
  102. sb.Append("void attach_imported_functions()");
  103. sb.AppendLine("{");
  104. foreach (var method in importedMethods)
  105. {
  106. sb.AppendLine(GenerateAttachImportedFunction(method));
  107. }
  108. sb.AppendLine("}");
  109. return sb.ToString();
  110. }
  111. private string BuildExportFunction(MethodDefinition method)
  112. {
  113. string exportName = method.CustomAttributes.First(a => a.AttributeType.FullName == "PixiEditor.Extensions.Sdk.ApiExportAttribute").ConstructorArguments[0].Value.ToString();
  114. StringBuilder sb = new StringBuilder();
  115. sb.Append($"__attribute__((export_name(\"{exportName}\")))");
  116. sb.AppendLine();
  117. BuildMethodDeclaration(method.ReturnType, sb, exportName, method.Parameters, false);
  118. sb.AppendLine("{");
  119. sb.AppendLine($"MonoMethod* method = lookup_interop_method(\"{method.Name}\");");
  120. string[] paramsToPass = CParamsToMonoVars(method.Parameters, sb);
  121. if (paramsToPass.Length > 0)
  122. {
  123. sb.AppendLine($"void* args[] = {{{string.Join(", ", paramsToPass)}}};");
  124. sb.AppendLine("invoke_interop_method(method, args);");
  125. }
  126. else
  127. {
  128. sb.AppendLine("invoke_interop_method(method, NULL);");
  129. }
  130. sb.AppendLine("free(method);");
  131. sb.AppendLine("}");
  132. return sb.ToString();
  133. }
  134. private string BuildImportFunction(MethodDefinition method)
  135. {
  136. string functionName = method.Name;
  137. var returnType = method.ReturnType;
  138. var parameters = method.Parameters;
  139. StringBuilder sb = new StringBuilder();
  140. sb.AppendLine($@"__attribute__((import_name(""{functionName}"")))");
  141. BuildMethodDeclaration(returnType, sb, functionName, parameters, true);
  142. sb.Append(";");
  143. return sb.ToString();
  144. }
  145. private static void BuildMethodDeclaration(TypeReference returnType, StringBuilder sb, string functionName,
  146. Collection<ParameterDefinition> parameters, bool extractLength)
  147. {
  148. string returnTypeMapped = TypeMapper.MapToCType(returnType);
  149. sb.Append($"{returnTypeMapped} {functionName}(");
  150. for (int i = 0; i < parameters.Count; i++)
  151. {
  152. var parameter = parameters[i];
  153. string parameterTypeMapped = string.Join(", ", TypeMapper.MapToCTypeParam(parameter.ParameterType, parameter.Name, extractLength));
  154. sb.Append(parameterTypeMapped);
  155. if (i < parameters.Count - 1)
  156. {
  157. sb.Append(", ");
  158. }
  159. }
  160. sb.Append(")");
  161. }
  162. private string BuildAttachedFunction(MethodDefinition method)
  163. {
  164. StringBuilder sb = new StringBuilder();
  165. string functionName = method.Name;
  166. var returnType = method.ReturnType;
  167. var parameters = method.Parameters;
  168. string returnTypeMapped = TypeMapper.MapToMonoType(returnType);
  169. sb.Append($"{returnTypeMapped} internal_{functionName}(");
  170. BuildMonoParams(parameters, sb);
  171. sb.AppendLine(")");
  172. sb.AppendLine("{");
  173. BuildCBody(method, sb);
  174. sb.AppendLine("}");
  175. return sb.ToString();
  176. }
  177. private static void BuildMonoParams(Collection<ParameterDefinition> parameters, StringBuilder sb)
  178. {
  179. for (int i = 0; i < parameters.Count; i++)
  180. {
  181. var parameter = parameters[i];
  182. string parameterTypeMapped = TypeMapper.MapToMonoTypeParam(parameter.ParameterType, parameter.Name);
  183. sb.Append(parameterTypeMapped);
  184. if (i < parameters.Count - 1)
  185. {
  186. sb.Append(", ");
  187. }
  188. }
  189. }
  190. private static void BuildCBody(MethodDefinition method, StringBuilder sb)
  191. {
  192. string[] paramsToPass = MonoParamsToCVars(method.Parameters, sb);
  193. BuildInvokeImport(method, sb, paramsToPass);
  194. }
  195. private static string[] MonoParamsToCVars(Collection<ParameterDefinition> methodParameters, StringBuilder sb)
  196. {
  197. List<string> cVars = new List<string>();
  198. for (int i = 0; i < methodParameters.Count; i++)
  199. {
  200. var parameter = methodParameters[i];
  201. if(TypeMapper.RequiresConversion(parameter.ParameterType))
  202. {
  203. string varName = $"c_{parameter.Name}";
  204. ConvertedParam[] convertedParams = TypeMapper.ConvertMonoToCType(parameter.ParameterType, parameter.Name, varName);
  205. foreach (var convertedParam in convertedParams)
  206. {
  207. sb.AppendLine(convertedParam.FullExpression);
  208. cVars.Add(convertedParam.VarName);
  209. }
  210. }
  211. else
  212. {
  213. cVars.Add(parameter.Name);
  214. }
  215. }
  216. return cVars.ToArray();
  217. }
  218. private string[] CParamsToMonoVars(Collection<ParameterDefinition> methodParameters, StringBuilder sb)
  219. {
  220. List<string> monoVars = new List<string>();
  221. for (int i = 0; i < methodParameters.Count; i++)
  222. {
  223. var parameter = methodParameters[i];
  224. if(TypeMapper.RequiresConversion(parameter.ParameterType))
  225. {
  226. string varName = $"mono_{parameter.Name}";
  227. ConvertedParam[] convertedParams = TypeMapper.ConvertCToMonoType(parameter.ParameterType, parameter.Name, varName);
  228. foreach (var convertedParam in convertedParams)
  229. {
  230. sb.AppendLine(convertedParam.FullExpression);
  231. string varToPass = !convertedParam.IsPointer ? $"&{convertedParam.VarName}" : convertedParam.VarName;
  232. monoVars.Add(varToPass);
  233. }
  234. }
  235. else
  236. {
  237. monoVars.Add($"&{parameter.Name}"); // TODO: make sure appending pointer is always correct
  238. }
  239. }
  240. return monoVars.ToArray();
  241. }
  242. private static void BuildInvokeImport(MethodDefinition method, StringBuilder sb, string[] paramsToPass)
  243. {
  244. string functionName = method.Name;
  245. string? returnVar = null;
  246. if (method.ReturnType.FullName != "System.Void")
  247. {
  248. string returnTypeMapped = TypeMapper.MapToCType(method.ReturnType);
  249. returnVar = $"{returnTypeMapped} result = ";
  250. sb.Append(returnVar);
  251. }
  252. sb.Append($"{functionName}(");
  253. sb.Append(string.Join(", ", paramsToPass));
  254. sb.AppendLine(");");
  255. if (returnVar != null)
  256. {
  257. if (TypeMapper.RequiresConversion(method.ReturnType))
  258. {
  259. string varName = "mono_result";
  260. ConvertedParam[] convertedParams = TypeMapper.ConvertCToMonoType(method.ReturnType, "result", varName);
  261. sb.AppendLine(convertedParams[0].FullExpression);
  262. returnVar = convertedParams[0].VarName;
  263. sb.AppendLine($"return {returnVar};");
  264. }
  265. else
  266. {
  267. sb.AppendLine($"return result;");
  268. }
  269. }
  270. }
  271. private string GenerateAttachImportedFunction(MethodDefinition method)
  272. {
  273. StringBuilder sb = new StringBuilder();
  274. string functionName = method.Name;
  275. string funcNamespace = method.DeclaringType.FullName;
  276. sb.Append($"mono_add_internal_call(\"{funcNamespace}::{functionName}\", internal_{functionName});");
  277. return sb.ToString();
  278. }
  279. }
  280. public class ConvertedParam
  281. {
  282. public string VarName { get; set; }
  283. public string VarType { get; set; }
  284. public string ConversionString { get; set; }
  285. public string FullExpression => $"{VarType} {VarName} = {ConversionString};";
  286. public bool IsPointer => VarType.EndsWith("*");
  287. public ConvertedParam(string varName, string varType, string conversionString)
  288. {
  289. VarName = varName;
  290. VarType = varType;
  291. ConversionString = conversionString;
  292. }
  293. }