Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -397,4 +397,9 @@ FodyWeavers.xsd
*.msp

# JetBrains Rider
*.sln.iml
*.sln.iml

# Built binaries
*.dll
*.so
*.dylib
41 changes: 35 additions & 6 deletions TypeTreeGeneratorAPI/NativeAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ unsafe public static int TypeTreeGenerator_generateTypeTreeNodesJson(IntPtr type

[UnmanagedCallersOnly(EntryPoint = "TypeTreeGenerator_generateTreeNodesRaw")]
unsafe public static int TypeTreeGenerator_generateTypeTreeNodesRaw(IntPtr typeTreeGeneratorPtr, IntPtr assemblyNamePtr, IntPtr fullNamePtr, IntPtr arrAddrPtr, IntPtr arrLengthPtr)
{
{
string? assemblyName = Marshal.PtrToStringUTF8(assemblyNamePtr);
string? fullName = Marshal.PtrToStringUTF8(fullNamePtr);

Expand All @@ -154,19 +154,15 @@ unsafe public static int TypeTreeGenerator_generateTypeTreeNodesRaw(IntPtr typeT
}
try
{
var typeTreeGenerator = (TypeTreeGenerator)GCHandle.FromIntPtr(typeTreeGeneratorPtr).Target!;

var typeTreeGenerator = (TypeTreeGenerator)GCHandle.FromIntPtr(typeTreeGeneratorPtr).Target!;
var typeTreeNodes = typeTreeGenerator.GenerateTreeNodes(assemblyName, fullName);
if (typeTreeNodes == null)
{
return -1;
}
var (arrayPtr, arrayLength) = TypeTreeNodeSerializer.ToRaw(typeTreeNodes!);

// Write the JSON pointer to the address specified by `jsonAddr`
Marshal.WriteIntPtr(arrAddrPtr, arrayPtr);

// Write the JSON length to the address specified by `jsonLength`
Marshal.WriteInt32(arrLengthPtr, arrayLength);

return 0;
Expand All @@ -177,6 +173,39 @@ unsafe public static int TypeTreeGenerator_generateTypeTreeNodesRaw(IntPtr typeT
}
}

[UnmanagedCallersOnly(EntryPoint = "TypeTreeGenerator_getMonoBehaviorDefinitions")]
unsafe public static int TypeTreeGenerator_getMonoBehaviorDefinitions(IntPtr typeTreeGeneratorPtr, IntPtr arrAddrPtr, IntPtr arrLengthPtr)
{
if (typeTreeGeneratorPtr == IntPtr.Zero )
{
return -1;
}
try
{
var typeTreeGenerator = (TypeTreeGenerator)GCHandle.FromIntPtr(typeTreeGeneratorPtr).Target!;

var typeNames = typeTreeGenerator.GetMonoBehaviourDefinitions();

var arrayLength = typeNames.Count;
var arrayPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf<IntPtr>() * arrayLength * 2);
for (int i = 0; i < arrayLength; i++)
{
string module = typeNames[i].Module.Name;
string fullName = typeNames[i].FullName;
Marshal.WriteIntPtr(arrayPtr, (i * 2) * Marshal.SizeOf<IntPtr>(), Marshal.StringToCoTaskMemUTF8(module));
Marshal.WriteIntPtr(arrayPtr, (i * 2 + 1) * Marshal.SizeOf<IntPtr>(), Marshal.StringToCoTaskMemUTF8(fullName));
}

Marshal.WriteIntPtr(arrAddrPtr, arrayPtr);
Marshal.WriteInt32(arrLengthPtr, arrayLength);

return 0;
}
catch
{
return -1;
}
}
[UnmanagedCallersOnly(EntryPoint = "FreeCoTaskMem")]
public static void FreeCoTaskMem(IntPtr ptr)
{
Expand Down
25 changes: 24 additions & 1 deletion bindings/python/TypeTreeGeneratorAPI/TypeTreeGenerator.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import os
import platform
import ctypes
from typing import List
from typing import List, Tuple


class TypeTreeNodeNative(ctypes.Structure):
Expand Down Expand Up @@ -78,6 +78,11 @@ def init_dll():
ctypes.POINTER(ctypes.POINTER(TypeTreeNodeNative)),
ctypes.POINTER(ctypes.c_int),
]
dll.TypeTreeGenerator_getMonoBehaviorDefinitions.argtypes = [
ctypes.c_void_p,
ctypes.POINTER(ctypes.POINTER(ctypes.c_char_p)),
ctypes.POINTER(ctypes.c_int),
]
dll.TypeTreeGenerator_del.argtypes = [ctypes.c_void_p]
dll.FreeCoTaskMem.argtypes = [ctypes.c_void_p]
DLL = dll # type: ignore
Expand Down Expand Up @@ -146,5 +151,23 @@ def get_nodes(self, assembly: str, fullname: str) -> List[TypeTreeNode]:
DLL.FreeCoTaskMem(nodes_ptr)
return nodes

def get_monobehavior_definitions(self) -> List[Tuple[str, str]]:
names_ptr = ctypes.POINTER(ctypes.c_char_p)()
names_cnt = ctypes.c_int()
assert not DLL.TypeTreeGenerator_getMonoBehaviorDefinitions(
self.ptr,
ctypes.byref(names_ptr),
ctypes.byref(names_cnt),
), "failed to get module exports"
ptr_array = ctypes.cast(
names_ptr, ctypes.POINTER(ctypes.c_char_p * names_cnt.value)
)
names = [name.decode("utf-8") for name in ptr_array.contents]
for ptr in ptr_array:
ptr = ctypes.cast(ptr, ctypes.c_void_p).value
DLL.FreeCoTaskMem(ptr)
DLL.FreeCoTaskMem(names_ptr)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would leak memory, as the array pointers to the names wouldn't be freed.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which reminds me, that I still have to double check if the raw typetree struct leaks or not.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which reminds me, that I still have to double check if the raw typetree struct leaks or not.

Unfortunately it does according to the docs :(

fDeleteOld
true to call the DestroyStructure(IntPtr, Type) method on the ptr parameter before this method copies the data. The block must contain valid data. Note that passing false when the memory block already contains data can lead to a memory leak.

Back to this one - when I was trying to free the allocated strings with the following snippet Python seem to crash after a few frees. Any ideas why this is happening?

        for ptr in ptr_array:
            ptr = ctypes.cast(ptr, ctypes.c_void_p).value
            DLL.FreeCoTaskMem(ptr)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue is that the iterator doesn't work as intended.
I managed to fix it, but the solution isn't pretty.

So I'm simply going to include free function in the NativeAPI as well, which would also make usage in other languages next to python easier and less hacky.

return [(module, fullname) for module, fullname in zip(names[::2], names[1::2])]


__all__ = ("TypeTreeGenerator", "TypeTreeNode", "TypeTreeNodeNative")