question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Question about IntPtr to managed object

See original GitHub issue

Hi, we have a “working” solution that allows native code to use managed logging with ILogger.

We are storing lots of function pointers and DNNE looks like a beautiful way to greatly simplify our code.

From the screenshots it looks like we pass the same IntPtr for the loggerHandle but when running the call with DNNE we get:

System.InvalidCastException: ‘Unable to cast object of type ‘Microsoft.Extensions.Logging.Logger’ to type ‘Microsoft.Extensions.Logging.ILogger’.’

Manually putting various casts into the watch window seem to work.

Examples: (ILogger?)handle.Target handle.Target as ILogger

Wondering if there are any ideas of what could be going on?

Here is the calling code with both the old and the new way with DNNE.

   void LoggerProxy::Log( Level level, std::wstring logMessage )
   {
      // This call seems to work for casting purposes?
      _loggerFunction( _logger->Handle(), (int) level, logMessage.c_str() );

      // Call use DNNE - from InfrastructureNE.h
      ::Log( (intptr_t) _logger->Handle(), (int32_t) level, (intptr_t) logMessage.c_str() );
   }

Here is the method we are calling into:

   [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
   public static void Log(IntPtr loggerHandle, int loglevel, IntPtr message)
   {
      try
      {
         var handle = GCHandle.FromIntPtr(loggerHandle);

         var logmessagetemp = Marshal.PtrToStringUni(message); // move a copy of this up here, to verify it works before exception

         var logger = (ILogger?)handle.Target; // exception when using DNNE
         if (logger == null)
         {
            throw new ArgumentException("Unable to convert loggerHandle to ILogger");
         }
         var logmessage = Marshal.PtrToStringUni(message);
         logger.Log((LogLevel)loglevel, logmessage);
      }
      catch (Exception ex)
      {
         var logger = StaticLogging.CreateLogger(typeof(LoggerFactoryProxyService));
         logger.LogError($"Unable to write log from native code.  The log message will be lost.  Reason: {ex.Message}");
      }
   }

Screenshot of the “working” call:

image

Screenshot of the call with DNNE

image

Issue Analytics

  • State:closed
  • Created a year ago
  • Comments:13 (6 by maintainers)

github_iconTop GitHub Comments

1reaction
roboz0rcommented, Nov 16, 2022

My project isn’t open source but I can share the relevant parts for RPC. Code is in F# but it shouldn’t be too difficult to turn back into C# if required.

type IServer = // Define your interface

module Transport = 
    let serverPipe pipeName = 
        new NamedPipeServerStream(
            pipeName, 
            PipeDirection.InOut, 
            NamedPipeServerStream.MaxAllowedServerInstances, 
            PipeTransmissionMode.Byte, 
            PipeOptions.Asynchronous)
      
    let clientPipe pipeName = 
        new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous)

    let formatter () : IJsonRpcMessageFormatter = new JsonMessageFormatter()

module RPCServer =

    open System
    open StreamJsonRpc
    
    /// Creates a background thread to handle incoming connections to the server. 
    /// 'T should be the interface type that specifies the server data contract.
    let createRPCServer<'T when 'T: not struct> (server:'T) pipeName = 
        
        let rec loop () = async {
            // A new pipe must be created for each request
            let pipe = Transport.serverPipe pipeName
            do! pipe.WaitForConnectionAsync() |> Async.AwaitTask
            let handler = new HeaderDelimitedMessageHandler(pipe, pipe, Transport.formatter())
            
            use rpc = new JsonRpc(handler)
            rpc.ExceptionStrategy <- ExceptionProcessing.ISerializable
            rpc.AddLocalRpcTarget<'T>(server, JsonRpcTargetOptions())
            rpc.StartListening() 
            // No need to await completion, just loop and prepare new pipe for next request
            let _ = rpc.Completion
            return! loop()
        }
        loop ()
        |> Async.Start

module RPCClient =

    open System
    open StreamJsonRpc

    /// Returns a proxy interface to the server once the connection has been established.
    /// 'T should be the interface type that specifies the server data contract.
    let getClientProxy<'T when 'T: not struct> pipeName = 
        async {
            let! tok = Async.CancellationToken
            let pipe = Transport.clientPipe pipeName
            let handler = new HeaderDelimitedMessageHandler(pipe, pipe, Transport.formatter())
            // Client pipe must be connected before returning interface
            do! pipe.ConnectAsync(tok) |> Async.AwaitTask
            return JsonRpc.Attach<'T>(handler)
        }    
1reaction
roboz0rcommented, Nov 16, 2022

I had a similar issue where my assemblies are loaded twice: Once by the native exports and once by COM into the same process. My solution to communication between the two was to set it up as RPC using StreamJsonRpc. That way client and server use normal .NET interfaces to communicate no need to worry about AssemblyLoadContext.

Read more comments on GitHub >

github_iconTop Results From Across the Web

C# - How To Convert Object To IntPtr And Back?
I want to pass an object from managed code to a WinApi function as IntPtr . It will pass this object back to...
Read more >
GCHandle.FromIntPtr(IntPtr) Method (System.Runtime. ...
Returns a new GCHandle object created from a handle to a managed object.
Read more >
Convert Object to IntPtr
Is there any way to convert System.Object to IntPtr or char* in cpp code. Thanks. Posted 15-Nov-10 19:59pm.
Read more >
Misunderstanding IntPtr and System.Object. - limbioliong
1.9 This blog discusses the advantage of using the IDispatch interface for event firing as well as determining the real problem that hindered ......
Read more >
Thread: Get IntPtr of Object?
An IntPtr is our .NET way of representing a void* (pointer to an unmanaged type). As such, you generally don't ever need to...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found