Search Unity

how to use debug in c++ dll?

Discussion in 'Scripting' started by ulao, Oct 5, 2011.

  1. ulao

    ulao

    Joined:
    Oct 23, 2009
    Posts:
    37
    I'm using a c++ dll from my c# code

    [DllImport("FaceAPIPlugin")]
    private static extern bool initialize();

    I want to expose Debug to initialize so that I can write out some errors. Is there away to pass it in?

    i.e private static extern bool initialize( Debug * debug );

    problem is I dont know how to declare this in my c++ function since it knows nothing of unity.

    bool FaceAPIService::initialize( ???? * debug )
     
  2. Ntero

    Ntero

    Joined:
    Apr 29, 2010
    Posts:
    1,436
    If it's not on iOS, then you can do some neat tricks.

    using:
    http://msdn.microsoft.com/en-us/lib...es.marshal.getfunctionpointerfordelegate.aspx

    You can pass in initialize(Marshal.GetFunctionPointerForDelegate(Debug.Log)); And then use it as a Function Pointer taking a char* on the C++ end.

    This doesn't work on iOS or any other build using -aot compilation, but it should be fine on Desktop/Webplayer.

    Edit: That being said your callstack is always gonna be empty, and double clicking it could cause some very strange issues.
     
  3. ulao

    ulao

    Joined:
    Oct 23, 2009
    Posts:
    37
    yeah I'm using a non iOS, but I can not find an include or namespace that supports Marshal ?

    using namespace System; is not found.
     
  4. Ntero

    Ntero

    Joined:
    Apr 29, 2010
    Posts:
    1,436
    The Namespace is listed in the MSDN link, and the word namespace is not used in a using statement.

    Code (csharp):
    1.  
    2. using System.Runtime.InteropServices;
    3.  
    If the System namespace is not found, is this a separate DLL, or is it part of your Unity project? If it's a separate DLL you need to reference mscorlib.dll, though that should always be there as it includes things like string, int, bool, etc...
     
  5. ulao

    ulao

    Joined:
    Oct 23, 2009
    Posts:
    37
    actually I was confused. I got it to work but I'm missing something here. My main in C++ calls the initialize function. Worst case I can make a setter. Though, is there a way to do this?

    int main( )
    {
    initialize();

    so: what to put in for the augment? Or would I need to pass it to main first.
     
  6. Ntero

    Ntero

    Joined:
    Apr 29, 2010
    Posts:
    1,436
    You would need to store a function pointer of type void (*foo)(char*) Then using the a DLLImport call, you pass Debug.Log in as listed above. Once it's passed in, you can do whatever you would normally do with function pointers (store it locally or globally, call it whenever) and call it using any char* argument you want.
     
  7. Ntero

    Ntero

    Joined:
    Apr 29, 2010
    Posts:
    1,436
    Essentially you have these steps:
    1) In C# Pass in function you want to call using Marshal.FunctionPointerToDelegate code above
    2) in C API, have function that retrieves function pointer that takes a char* and returns void
    3) in C/C++ store that function pointer wherever you want
    4) in C/C++ invoke that function pointer with the specified string
     
  8. ulao

    ulao

    Joined:
    Oct 23, 2009
    Posts:
    37
    yeah Ntero , I totally follow you here. For some reason I'm not able to assign the pointer to my local?

    For example I plan to store it in
    void (*debuger)(char*)

    So in my set function I have

    void FaceAPIService::setDebug( char * debug )
    {

    debuger = &debug ;// value of char ** can not be assigned.
    debuger = debug ;// value of char * can not be assigned.

    }

    I found some examples on the net and can not even get that to work. I seem to be missing something basic at the moment.
     
  9. Ntero

    Ntero

    Joined:
    Apr 29, 2010
    Posts:
    1,436
    You are not using Function pointers properly:
    http://www.cprogramming.com/tutorial/function-pointers.html

    You can't assign a char* to a function pointer, you have to call the function pointer with the char*

    To be more clear char* and the void (*debuger)(char*) are in no way interchangeable or castable. They are two very different things, and #2 actually takes #1 as a parameter.
     
  10. ulao

    ulao

    Joined:
    Oct 23, 2009
    Posts:
    37
    Yup that cleared things up a bit. Seems I can not define my c# extern as unity gives: Pointers and fixed size buffers may only be used in an unsafe context

    [DllImport("FaceAPIPlugin")]
    private static extern void setDebug( char * debug );

    I tried adding unsafe to the type but that makes unity ask that I add unsafe to the command line, and doing this does nothing.
     
    Last edited: Oct 5, 2011
  11. Ntero

    Ntero

    Joined:
    Apr 29, 2010
    Posts:
    1,436
    Why are you passing a string to C?

    You pass the Debug.Log delegate to C. If you are in C#, you just call Debug.Log().

    Don't pass a string of any kind from C# to C, that's a waste of time and resources. Pass the function that takes a string from C# to C, and then use the strings you created in C.
     
  12. Dreamora

    Dreamora

    Joined:
    Apr 5, 2008
    Posts:
    26,601
    The most crucial question was not asked yet: Are you on pro? Otherwise you can not use plugins on the desktop at all. Also the webplayer can never interactive with native code.
     
  13. ulao

    ulao

    Joined:
    Oct 23, 2009
    Posts:
    37
    I figured char would hold the address to the pointer that GetFunctionPointerForDelegate returns. I'm passing it right I just dont know how to declare the the function header I'm passing it to.

    private static extern void setDebug( ???? debug ); //tried char debug, tried Delegate debug, tried char * debug and, and a few more.
    setDebug(Marshal.GetFunctionPointerForDelegate(Debug.Log));

    Forgive me for missing something simple but I have never used Marshaling before. Looks to me like GetFunctionPointerForDelegate returns a pointer ( System.IntPtr ) but setDebug( System.IntPtr debug ); cause errors.

    Sorry for the confusion.


    dreamora: Yes, I am, the plug in works, I just need to pass in a pointer so I can use the debug console.
     
  14. Ntero

    Ntero

    Joined:
    Apr 29, 2010
    Posts:
    1,436
    IntPtr is the C# safe wrapper for a pointer.

    You'll need to explain what language each call is in, and what the errors are. If I don't know the error, I can't suggest any remedies.
    i.e. System.IntPtr is not a C/C++ class.

    C#:
    [DllImport("FaceAPIPlugin")]
    private static extern bool setDebug(System.IntPtr debugCall);

    C++
    extern "C"
    {
    typedef void (*DebugPointer) (char*) ;

    static myDebugPointer;
    void setDebug(DebugPointer ptr);
    }

    This is syntatically incorrect I'm sure, but it shows the signatures on C and C# that you should be able to use to inter-operate.
    When passing IntPtr objects from C# to C, they can be assigned as any pointer type you want, an are essentially void* objects.

    Edit: There are a few other Plugin related things (DECLSPEC in windows iirc), and you may want to check the plugin documentation on how to actually link a call. You can also use the MSDN to get more information on the Marshal class, as a key player in calling between C# and C.
    http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.marshal.aspx
     
    Last edited: Oct 5, 2011
  15. ulao

    ulao

    Joined:
    Oct 23, 2009
    Posts:
    37
    Ok, I'm obviously complicating things here, let me try to show the code I have.

    c# code.
    Code (csharp):
    1.  
    2.  
    3. public class FaceAPIBehavior : MonoBehaviour
    4. {
    5.     ...
    6.     [DllImport("FaceAPIPlugin")]
    7.    
    8. // If I do this I get, The best overloaded method match for `System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(System.Delegate)' has some invalid arguments
    9.     private static extern void setDebug( System.IntPtr debug );  
    10.    
    11. //if I do this I get,  Pointers and fixed size buffers may only be used in an unsafe context
    12.     private static extern void setDebug( char * debug );   
    13.    
    14.     ...
    15.     void Start ()
    16.     {
    17.         Debug.Log("send my pointer to c++ );
    18.         setDebug(Marshal.GetFunctionPointerForDelegate(Debug.Log));
    19.         ...
    20.     }
    21. ...
    22. }
    23.  
    24.  
    c++ code.

    header:
    void setDebug ( char(*debugger)(char) );

    cpp:
    void FaceAPIService::setDebug( char(*debugger)(char) )
    {
    char r='a';
    debugger(r);
    (*debugger)(r);
    }
     
    Last edited: Oct 5, 2011
  16. Ntero

    Ntero

    Joined:
    Apr 29, 2010
    Posts:
    1,436
    Ah ok, my bad, sorry, it was a syntax issue on my instruction end.
    Playing around with it, it looks like the GetFunctionPtr function cannot be passed a function directly, but requires a delegate to call (which makes sense for it's name).

    Try this:
    Code (csharp):
    1.  
    2. System.Action<string> callback = Debug.Log;
    3. setDebug(Marshal.GetFunctionPointerForDelegate(callback));
    4.  
    Action<string> is just a general delegate that can take a string as input and returns null. If it doesn't work you may need to make your own non-generic delegate. Also you will need to keep callback around as a reference somewhere while it could be called in C, so that the Garbage Collector doesn't clean it up.
     
  17. ulao

    ulao

    Joined:
    Oct 23, 2009
    Posts:
    37
    Yes that does make it compile but there is one mystery on my end left.


    If I can do this in c#
    [DllImport("FaceAPIPlugin")]
    private static extern void setDebug( System.IntPtr debug );

    and just like you suggested I can do this to call my c++
    System.Action<string> callback = Debug.Log;
    setDebug(Marshal.GetFunctionPointerForDelegate( callback ));

    then what does the c++ define look like? If I leave it as I have it
    void setDebug ( char(*debugger)(char) );
    then I get EntryPointNotFoundException: setDebug errors.

    void setDebug ( System.IntPtr debug ); wont work as I dont have a way to use the system namespace? Or is therw a way to cast callback to a pointer, or one of its members as a pointer?
     
  18. Ntero

    Ntero

    Joined:
    Apr 29, 2010
    Posts:
    1,436
    You don't have to use the System namespace.

    C/C++ just uses a pointer, and what's actually passed on the C/C++ function is void*, but you can cast it as whatever you want (albeit unsafely).

    The reason you get EntryPointNotFound is because C++ mangles names, i.e. the function name is not setDebug, but something like _SomeClass__SetDebug_*char or some other mangling. You can use extern "C" {} as a block to tell the compiler to use standard naming, or you can decipher what setDebug's name is within the DLL. It's not just a simple setDebug, because it needs to also contain the class name, info on whether it's static or not, and info on parameters.
     
  19. bdev

    bdev

    Joined:
    Jan 4, 2011
    Posts:
    656
    As long as your using pro, remember you can debug c++ side of the dll in msvc. Just set the project to debug, have the project build to the plugin folder, set debug target to unity.exe or whatever the editor .exe or even standalone .exe is, slip a breakpoint in any function.

    Once unity loads the dll and calls a function you should have no problems debugging.

    I know this might not be what your looking for, but it sounds like a workaround
     
  20. ulao

    ulao

    Joined:
    Oct 23, 2009
    Posts:
    37
    BDev, as of now I cant get the debug to compile, but I may look in to that more if I cant get this to work.

    Turns out I was missing this

    EXPORT void setDebug( char(*debugger)(char) )
    {
    FaceAPIService* pFaceAPI = FaceAPIService::getInstance();
    return pFaceAPI->setDebug(*debugger);
    }

    but now I get this.
    MarshalDirectiveException: The type `System.Object' layout needs to be Sequential or Explicit
    (wrapper native-to-managed) UnityEngine.Debug:Log (string)
    FaceAPIBehavior.Start () (at Assets/Scripts/FaceAPIBehavior.cs:113)
     
    Last edited: Oct 6, 2011
  21. Ntero

    Ntero

    Joined:
    Apr 29, 2010
    Posts:
    1,436
    What is line 113 in FaceAPIBehaviour.cs?
     
  22. ulao

    ulao

    Joined:
    Oct 23, 2009
    Posts:
    37
    sorry, that would help

    System.Action< string > callback = Debug.Log;
    setDebug(Marshal.GetFunctionPointerForDelegate( callback ) );//line 113

    I think it means the < string > of line 112, I read about using [StructLayout(LayoutKind.Explicit)] and using a struct but not having much luck.
     
    Last edited: Oct 6, 2011
  23. smb02dunnal

    smb02dunnal

    Joined:
    May 5, 2012
    Posts:
    439
    I'm sorry about reviving an old thread, but for anyone who is looking to do this here is how I have done it.

    C#
    Code (csharp):
    1.  
    2. [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    3. private delegate void DebugLog(string log);
    4.  
    5. private static readonly DebugLog debugLog = DebugWrapper;
    6. private static readonly IntPtr functionPointer = Marshal.GetFunctionPointerForDelegate(debugLog);
    7.  
    8. private static void DebugWrapper(string log)
    9. {
    10.     Debug.Log(log);
    11. }
    12.  
    13. [DllImport("MeshSplitter")]
    14. private static extern void LinkDebug([MarshalAs(UnmanagedType.FunctionPtr)]IntPtr debugCal);
    15.  
    16.  
    C++
    Code (csharp):
    1.  
    2. extern "C"
    3. {
    4.     void (_stdcall*debugLog)(char*) = NULL;
    5.  
    6.     __declspec(dllexport) void LinkDebug( void(_stdcall*d)(char *))
    7.     {
    8.         debugLog = d;
    9.         d("Debug Link Successful!");
    10.     }
    11. }
    12.  

    Then I simply call it in C++ like this:
    Code (csharp):
    1.  
    2. if(debugLog != NULL)
    3. {      
    4.     debugLog("test");
    5. }
    6.  

    This works! :D
     
    Last edited: May 25, 2013
    dl_studios likes this.
  24. dl_studios

    dl_studios

    Joined:
    Oct 14, 2012
    Posts:
    76
    @smb02dunnal

    Awesome thanks this worked for me. I'd also add that you need to store your delegate in and Intptr like so.
    Code (CSharp):
    1.  public static void SetUpDebug() {
    2.  
    3.  
    4.                 DebugLog debug;
    5.                 debug = DebugWrapper;
    6.                 IntPtr ptr = Marshal.GetFunctionPointerForDelegate(debug);
    7.                 LinkDebug(ptr);
    8.  
    9.            
    10.             }
    11.