C
C#2y ago
rick_o_max

Interoperability issues (PInvoke)

Hi! I'm calling a native CPP library and retrieving a struct by reference which returns a struct containing some pointers.
69 Replies
rick_o_max
rick_o_maxOP2y ago
I first call a CPP method to fill the structure, this is what CPP returns:
rick_o_max
rick_o_maxOP2y ago
rick_o_max
rick_o_maxOP2y ago
This is the struct in C# (returned value)
rick_o_max
rick_o_maxOP2y ago
rick_o_max
rick_o_maxOP2y ago
When using the same struct as a reference to call another CPP method, the values are wrong in the CPP side:
rick_o_max
rick_o_maxOP2y ago
rick_o_max
rick_o_maxOP2y ago
This is my CPP struct:
typedef struct TFBXContext {
int code;
FbxManager* manager;
FbxIOSettings* ios;
FbxScene* scene;
FbxImporter* importer;
MemoryStream* stream;
FbxEmbeddedFileCallback* fileCallback;
FbxSimpleMap<FbxString, FbxPair<char*, size_t>, FbxStringCompare>* embeddedResources;
rapidjson::Document* assetLoaderOptions;
//const char* status;
} FBXContext;
typedef struct TFBXContext {
int code;
FbxManager* manager;
FbxIOSettings* ios;
FbxScene* scene;
FbxImporter* importer;
MemoryStream* stream;
FbxEmbeddedFileCallback* fileCallback;
FbxSimpleMap<FbxString, FbxPair<char*, size_t>, FbxStringCompare>* embeddedResources;
rapidjson::Document* assetLoaderOptions;
//const char* status;
} FBXContext;
And this is my C# code with the method called in the second CPP screenshot and the same interface in C#:
[DllImport(DllPath, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
private static extern IntPtr ConvertScene(
FBXContext context
);

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
private struct FBXContext
{
public int code;
public IntPtr manager;
public IntPtr ios;
public IntPtr scene;
public IntPtr importer;
public IntPtr stream;
public IntPtr fileCallback;
public IntPtr embeddedResources;
public IntPtr assetLoaderOptions;
// public string status;
}
[DllImport(DllPath, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
private static extern IntPtr ConvertScene(
FBXContext context
);

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
private struct FBXContext
{
public int code;
public IntPtr manager;
public IntPtr ios;
public IntPtr scene;
public IntPtr importer;
public IntPtr stream;
public IntPtr fileCallback;
public IntPtr embeddedResources;
public IntPtr assetLoaderOptions;
// public string status;
}
So, both CPP screenshots are meant to have the same FBXContext data, but somehow, it is broken when using the context as a parameter No reason to get 90 in code where the original value was 1 Only if C# is not keeping the original layout order sizeof(void*) and IntPtr.Size are both the same: 8 sizeof(FBXContext) and Marshal.SizeOf<FBXContext>() are both 72
sibber
sibber2y ago
how are you passing the struct to the cpp method and how are you getting it show the signatures of the methods
rick_o_max
rick_o_maxOP2y ago
c:
extern "C" {
FBX_API FBXContext CALL_CONV ReadStream(void* data, long dataLength, const char* assetLoaderOptions, const char* filename, bool async, FbxProgressCallback onProgress = 0);
FBX_API RootModel CALL_CONV ConvertScene(FBXContext context);
}
extern "C" {
FBX_API FBXContext CALL_CONV ReadStream(void* data, long dataLength, const char* assetLoaderOptions, const char* filename, bool async, FbxProgressCallback onProgress = 0);
FBX_API RootModel CALL_CONV ConvertScene(FBXContext context);
}
First method generates the struct, second one receives the struct c#:
[DllImport(DllPath, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
private static extern FBXContext ReadStream(
IntPtr data,
long dataLength,
[MarshalAs(UnmanagedType.LPStr)]
string assetLoaderOptions,
[MarshalAs(UnmanagedType.LPStr)]
string filename,
bool async,
[MarshalAs(UnmanagedType.FunctionPtr)]
ProgressCallback onProgress
);

[DllImport(DllPath, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
private static extern IntPtr ConvertScene(
FBXContext context
);
[DllImport(DllPath, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
private static extern FBXContext ReadStream(
IntPtr data,
long dataLength,
[MarshalAs(UnmanagedType.LPStr)]
string assetLoaderOptions,
[MarshalAs(UnmanagedType.LPStr)]
string filename,
bool async,
[MarshalAs(UnmanagedType.FunctionPtr)]
ProgressCallback onProgress
);

[DllImport(DllPath, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
private static extern IntPtr ConvertScene(
FBXContext context
);
c# call:
var data = stream.ReadBytes();
var gcHandle = LockGc(data);
var context = ReadStream(gcHandle.AddrOfPinnedObject(), data.LongLength, assetLoaderContext.Options.Serialize(), filename, assetLoaderContext.Async, null);// todo
gcHandle.Free();
if (context.code != 1)
{
throw new Exception();
//throw new Exception(context.status);
}
var scene = ConvertScene(context);
var data = stream.ReadBytes();
var gcHandle = LockGc(data);
var context = ReadStream(gcHandle.AddrOfPinnedObject(), data.LongLength, assetLoaderContext.Options.Serialize(), filename, assetLoaderContext.Async, null);// todo
gcHandle.Free();
if (context.code != 1)
{
throw new Exception();
//throw new Exception(context.status);
}
var scene = ConvertScene(context);
ConvertScene seems to be messing up the context contents when sending it back to cpp
sibber
sibber2y ago
i tested interoping with passing a simple struct with a pointer and an int back and forth, and everything worked fine no idea why this isnt working for you its probably a good idea to write a wrapper in cpp to make interoping with c# easier
rick_o_max
rick_o_maxOP2y ago
Tks. Trying to find the issue source The proble is in marshalling struct conversion, when passing the struct as a pointer, it works as expected But I cannot rely on that bc I need to readback some info from the structs (In C#)
sibber
sibber2y ago
if youre on .net 7 you can use LibraryImport and see what marshaling its doing
rick_o_max
rick_o_maxOP2y ago
I'm on Unity :c (Mono)
sibber
sibber2y ago
oh oof
rick_o_max
rick_o_maxOP2y ago
These issues makes 0 sense
sibber
sibber2y ago
but your struct doesnt require any marshalling its just pointers and an int
rick_o_max
rick_o_maxOP2y ago
Indeed hmm This struct is allocated in the stack when it comes from CPP Maybe that is the issue? Afaik, C# should just copy the struct contents to managed memory, so it doesn't make much sense either I mean, it is not a blittable type bc of the pointers, I guess
sibber
sibber2y ago
no it doesnt copy anything to managed mem
rick_o_max
rick_o_maxOP2y ago
hmm!
sibber
sibber2y ago
its still on the stack in c#
rick_o_max
rick_o_maxOP2y ago
Managed or unmanaged stack?
sibber
sibber2y ago
theres no managed stack
rick_o_max
rick_o_maxOP2y ago
Got it, it shares the same stack as C then
sibber
sibber2y ago
yeah and with stdcall cpp cleans the stack
rick_o_max
rick_o_maxOP2y ago
So that might be the issue? Lemme test it with __cdecl
sibber
sibber2y ago
probably not but what do you have to lose
rick_o_max
rick_o_maxOP2y ago
Indeed Maybe mono works differently YEEEESSS The problem is the calling convention cdecl works as expected Tks for the tip @Cyberrex
sibber
sibber2y ago
huh thats weird np i tested with both cdecl and stdcall and both worked for me as expected
rick_o_max
rick_o_maxOP2y ago
But you're not on Mono, right?
sibber
sibber2y ago
no still
rick_o_max
rick_o_maxOP2y ago
It might be a Mono implementation bug Unity uses an outdated Mono implementation afaik
sibber
sibber2y ago
yeah because the new versions dont support something that they want or whatever
rick_o_max
rick_o_maxOP2y ago
Now I just have to retrieve a list of nested structs which will give me way more headache...lol
sibber
sibber2y ago
marshalling arrays shouldnt be a problem in theory lol consider writing a wrapper in cpp
rick_o_max
rick_o_maxOP2y ago
Yep, I'm planning to use the "count, array" format Bc they are varying in size A wrapper is a good idea, but I'm already late with this project
sibber
sibber2y ago
ah
rick_o_max
rick_o_maxOP2y ago
And I plan to do only a few calls to CPP and return the structs I need
sibber
sibber2y ago
yeah dynamically allocate the array and return a pointer you can use pointers in c# btw
rick_o_max
rick_o_maxOP2y ago
I see, but I can't force the Unity user to allow unsafe code So.. :/ I also hate to work with pointers in C# Prone to a lot of bugs
sibber
sibber2y ago
if your lib uses unsafe it doesnt mean that projects that use must be unsafe what youre doing is already prone to bugs and youre interoping with cpp
rick_o_max
rick_o_maxOP2y ago
If I do what you're saying, I'll have to pre-compile two different C# lib versions Bc for some platforms, Mono won't use the DLL name when calling the DLL
sibber
sibber2y ago
avoiding pointers isnt a good idea
rick_o_max
rick_o_maxOP2y ago
And uses __Internal instead
sibber
sibber2y ago
if you do what
rick_o_max
rick_o_maxOP2y ago
If I use unsafe code On C#
sibber
sibber2y ago
but unsafe is just a compiler flag afaik
rick_o_max
rick_o_maxOP2y ago
Bc I can't force users enabling unsafe code in the Unity project, so I have to move the unsafe part to a managed DLL But depending on the platform, the managed DLL has to find the native library using the library name or __Internal __Internal is used in platforms like WebGL, iOS On Unity
sibber
sibber2y ago
wdym move to a managed dll its already managed its c#
rick_o_max
rick_o_maxOP2y ago
In Unity, you can use unsafe code, but you have to change a compiler setting. And since I'm developing a plugin, I can't force users enabling this option So I can compile a managed DLL and add it to the Unity project
sibber
sibber2y ago
you dont have to expose pointers to the public api of your plugin
rick_o_max
rick_o_maxOP2y ago
But then comes the problem that Unity uses different DllPath values depending on the platform And I can't change these values inside a compiled DLL, got it?
#if ((UNITY_WEBGL || UNITY_IOS) && !UNITY_EDITOR)
private const string DllPath = "__Internal";
#else
private const string DllPath = "TriLibCoreFbxNative";
#endif
#if ((UNITY_WEBGL || UNITY_IOS) && !UNITY_EDITOR)
private const string DllPath = "__Internal";
#else
private const string DllPath = "TriLibCoreFbxNative";
#endif
I can't do that on a compiled library And using a compiled library, I can enable unsafe code only for the library itself
sibber
sibber2y ago
i dont get why you need 2 dlls but it doesnt matter do what suits you :) idk how unity works
rick_o_max
rick_o_maxOP2y ago
Bc I can't change #define after the library is already compiled But I can include a different library version depending on the platform the user is compiling to It is complicated
sibber
sibber2y ago
yeah i get that but what does using unsafe in c# change why would you suddenly need to reference a different version for different platforms
rick_o_max
rick_o_maxOP2y ago
If the library is compiled by Unity itself, users have to enable unsafe code in their projects. I can't do that If I put a precompiled managed DLL inside Unity, the DLL itself can use unsafe code
sibber
sibber2y ago
but you dont ship the code you ship the dll dont you?
rick_o_max
rick_o_maxOP2y ago
I can do both
sibber
sibber2y ago
so ship the dll so the users dont have to enable unsafe
rick_o_max
rick_o_maxOP2y ago
But this precompiled DLL would be using the managed code from the lib I was having issues with
sibber
sibber2y ago
and shipping the source doesnt?
rick_o_max
rick_o_maxOP2y ago
But depending on the platform, the name of the called DLL changes, because Mono can't use dynamic C libraries in WebGL and iOS, so it links to static The __Internal library name makes Mono search for the function in static libraries when linking But there comes the problem, I can't change the __Internal to LibraryName stuff to work on all platforms inside an already compiled managed DLL
sibber
sibber2y ago
ohhh so you letting the user compile i dont know what took me so long to get that
rick_o_max
rick_o_maxOP2y ago
Yes, that is why I'm putting all the managed source inside my plugin Np But I swear I found something once that could actually change a #define inside a precompiled managed DLL Or maybe I was just on a bad trip
sibber
sibber2y ago
#define is preprocessor how would something change it after compilation
rick_o_max
rick_o_maxOP2y ago
Yes, but I found something once that seem to do that!? Maybe I just made some confusion back in time
sibber
sibber2y ago
yeah that doesnt make sense
rick_o_max
rick_o_maxOP2y ago
When it gets compiled to ILCode, it loses all info that wasn't precompiled, right?
sibber
sibber2y ago
yeah thats the point
rick_o_max
rick_o_maxOP2y ago
I guess I made some confusion then Anyway. I appreciate a lot bro
Want results from more Discord servers?
Add your server