• Advertisement
Sign in to follow this  

C# C# and C++ interop question (structures and pointers)

Recommended Posts

I'm writing a GPU tool with C# for the UI and command line .exes that the user interacts with, and writing the necessary driver code to make it work in C++ (via .dll).  So far, loading an unmanaged .dll file written in C++ is trivial and easy, but there's one part that confuses me (mostly because I am not a C# expert, yet).  How do you handle structures as parameters?  My code crashes when I try to use a structure as a parameter.  Should I use an IntPtr instead and just cast it? 

I'll show you a bit of code to show you what I mean:

C++:
 

typedef struct _GPUDETAILS
{
	CHAR	DeviceDesc[128];
	DWORD	DeviceID;
	DWORD	VendorID;
} GPUDETAILS;

...

GPUMONEXDRIVERNVAPI_API int Drv_GetGpuDetails( int AdapterNumber, GPUDETAILS* pGpuDetails )
{
	_LOG( __FUNCTION__ << "(): TODO: Implement...\n" );

	if( !pGpuDetails )
	{
		_ERROR( "Invalid parameter!" << std::endl );
		return 0;
	}

	_LOG( __FUNCTION__ << "(): Gathering GPU details...\n" );

	strcpy( pGpuDetails->DeviceDesc, "NVIDIA Something..." );
	pGpuDetails->DeviceID = 0xFFFF;	/* TODO */
	pGpuDetails->VendorID = 0x10DE;	/* This is always a given */

	return 1;
}

Something simple for now.  Let's move on to the C# part...

namespace GPUMonEx
{
	/*
	 * GPU Details structure
	 * NOTE: Subject to change
	 */
	public struct GPUDETAILS
    {
        public string DeviceDesc;
        public UInt32 DeviceID;
        public UInt32 VendorID;
    }


	/*
	 * Driver importer classes for the following APIs under Windows
	 * TODO: Get ahold of Intel's SDK as well as implement AMD's equivalent for their hardware.
	 * 
	 * NVAPI - NVIDIA Driver Specific functionality 
	 * D3DKMT - Direct3D internal driver functions.  Should work for all GPUs, but currently needed for Intel.
	 */
	static class DrvD3DKMT
    {
        [DllImport("GPUMonEx.Driver.D3DKMT.dll")]
        public static extern int Drv_Initialize();

        [DllImport("GPUMonEx.Driver.D3DKMT.dll")]
        public static extern void Drv_Uninitialize();

        [DllImport("GPUMonEx.Driver.D3DKMT.dll")]
        public static extern unsafe int Drv_GetGpuDetails(int Adapter, ref GPUDETAILS pGpuDetails);

        [DllImport("GPUMonEx.Driver.D3DKMT.dll")]
        public static extern int Drv_GetOverallGpuLoad();

        [DllImport("GPUMonEx.Driver.D3DKMT.dll")]
        public static extern int Drv_GetGpuTemperature();
    }

    static class DrvNVAPI
    {
        [DllImport("GPUMonEx.Driver.NVAPI.dll")]
        public static extern int Drv_Initialize();

        [DllImport("GPUMonEx.Driver.NVAPI.dll")]
        public static extern void Drv_Uninitialize();

        [DllImport("GPUMonEx.Driver.NVAPI.dll")]
        public static extern unsafe int Drv_GetGpuDetails(int Adapter, ref GPUDETAILS pGpuDetails);

        [DllImport("GPUMonEx.Driver.NVAPI.dll")]
        public static extern int Drv_GetOverallGpuLoad();

        [DllImport("GPUMonEx.Driver.NVAPI.dll")]
        public static extern int Drv_GetGpuTemperature();
    }


	/*
	 * GPU Driver interfacing classes (the ones you actually call in user mode)
	 */
    public abstract class GPUDriverBase
    {
        public abstract int Initialize();
        public abstract void Uninitialize();
        public abstract int GetGpuDetails( int Adapter, ref GPUDETAILS pGpuDetails );
        public abstract int GetOverallGpuLoad();
        public abstract int GetGpuTemperature();
    }

	public class GPUDriverD3DKMT : GPUDriverBase
    {
		public override int Initialize()
        {
            return DrvD3DKMT.Drv_Initialize();
        }

        public override void Uninitialize()
        {
            DrvD3DKMT.Drv_Uninitialize();
        }

        public override int GetGpuDetails(  int Adapter, ref GPUDETAILS pGpuDetails )
        {
            return DrvD3DKMT.Drv_GetGpuDetails( Adapter, ref pGpuDetails );
        }

        public override int GetOverallGpuLoad()
        {
            return DrvD3DKMT.Drv_GetOverallGpuLoad();
        }

        public override int GetGpuTemperature()
        {
            return DrvD3DKMT.Drv_GetGpuTemperature();
        }
    }

	public class GPUDriverNVAPI : GPUDriverBase
    {
        public override int Initialize()
        {
            return DrvNVAPI.Drv_Initialize();
        }

        public override void Uninitialize()
        {
            DrvNVAPI.Drv_Uninitialize();
        }

        public override int GetGpuDetails(int Adapter, ref GPUDETAILS pGpuDetails)
        {
            return DrvNVAPI.Drv_GetGpuDetails(Adapter, ref pGpuDetails);
        }

        public override int GetOverallGpuLoad()
        {
            return DrvNVAPI.Drv_GetOverallGpuLoad();
        }

        public override int GetGpuTemperature()
        {
            return DrvNVAPI.Drv_GetGpuTemperature();
        }
    }
}

So, focusing on Drv_GetGpuDetails(), how do I actually get a valid structure filled in here?  Calling that function just crashes.  I'm sure it's a stupid easy fix, but once again, I'm far too C++ oriented and have yet to get used to C# in the same manner.

Any advice is welcome (on the question at hand or anything else).

Shogun

Share this post


Link to post
Share on other sites
Advertisement

What I did for my engine to C# bridge was to use IntPtr whenever possible. Makes life easier even when you need some overhead code to access some features from your classes.

My (test-) solution for example to transfer arrays from one end to the other is

public class Array<T> : UnmanagedAllocator
{ 
   public readonly UInt32 TypeSize = Marshal.SizeOf(typeof(T));

   /*
    Inherits
    
    protected IAllocator allocator;
    public IAllocator GetAllocator()
    {
       get { return allocator; }
    }
   */
   protected IntPtr handle; 
  
   public int Length
   {
      get 
      {
         if(handle != 0) return (csArrayGetLength(handle) / TypeSize);
         else return 0;  
      }
   }

   public Array(IAllocator allocator = null)
    : base(allocator) //will either take a non-null allocator from unmanaged C++ or use Allocator.Default
   {
      this.handle = 0;
   }

   public void Resize(UInt32 newSize)
   {
      if(handle != 0) csArrayResize(handle, newSize);
      else handle = allocator.Allocate(TypeSize * newSize);
   }

   [DllImport("EngineLayer.dll", CallingConvention = CallingConvention.Cdecl)]
   private static extern bool csArrayResize(IntPtr handle, UInt32 newSize);

   [DllImport("EngineLayer.dll", CallingConvention = CallingConvention.Cdecl)]
   private static extern UInt32 csArrayGetLength(IntPtr handle);
}

I tested this for various other classes and have setup some tools that also access single functions from my engine so you may take this as a proof of concept. On the engine side there is a strict allocator based memory management model and I implemented also IDisposable interface in my C# base class so there might be no memory leaks, otherwise my engine core will tell due to an assert that is routed to an managed C# exception

Share this post


Link to post
Share on other sites
	/*
	 * GPU Details structure
	 * NOTE: Subject to change
	 */
    [StructLayout(LayoutKind.Sequential, Size = 136), Serializable]  
    public struct GPUDETAILS
    {
        [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string DeviceDesc;

        [MarshalAsAttribute(UnmanagedType.U4, SizeConst = 1)]
        public UInt32 DeviceID;

        [MarshalAsAttribute(UnmanagedType.U4, SizeConst = 1)]
        public UInt32 VendorID;
    }

Fixed it, works perfectly.  Thanks.

Shogun

Share this post


Link to post
Share on other sites

Why are you even going through the headache of explicit marshalling and all that. Seeing that you are using C# for your UI I'm assuming that this tool is going to be Windows only for now. With that said Visual Studio supports the CLI/C++ interop in a more straightforward manageable way. Its a minimally restrictive but there is not worry about manual tagging types and others  with Marshal*Attribute. Use ref struct and ref class a forget about wasting time doing marshalling yourself.
https://msdn.microsoft.com/en-us/library/68td296t.aspx

11 hours ago, blueshogun96 said:

I'm far too C++ oriented and have yet to get used to C# in the same manner.

Just had to point out that is just semantics and the usual fallback us devs use for not getting familiar with another language. You can express whatever you desire in both language, its just a matter of syntax...the fundamental language constructs are present in both..

Edited by cgrant

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this  

  • Advertisement
  • Advertisement
  • Popular Tags

  • Advertisement
  • Popular Now

  • Similar Content

    • By Alexander Nazarov
      Hello. I'm newby in Unity and just start learning basics of this engine. I want to create a game like StackJump (links are below). And now I wondering what features do I have to use to create such my game. Should I use Physics engine or I can move objects changing transform manually in Update().
      If I should use Physics can you in several words direct me how can I implement and what I have to use. Just general info, no need for detailed description of developing process.
      Game in PlayMarket
      Video of the game
    • By Dave Haylett
      Hi all. My project is coming along wonderfully, and am starting to consider alpha deployment, and would like your advice.
      My project need access to 10,000 small PNG image files at runtime, each is only a few kilobytes each, which during development I used to load in directly from a fixed path on my HDD whenever one was needed (obviously not a solution for go-live), using something like this:
      img = new WriteableBitmap(new BitmapImage(new Uri(@screenshotsPath + filename)));
      The image would then be blitted onto a buffer screen, etc. etc. At a time, a few dozen would be being used.
      Now I'm thinking about deployment, and also when I produce an update to my app, there could be more images to add to the folders. So I'm considering the best way of a) deploying the images to the user as part of the project, and b) how to most easily handle updates to the app, whereby more images will be added.
      I have just experimented with adding them all as a Resource (!). This inflated the exe from 10mb to 100mb (not a major problem), increased the compile time from 3 secs to 30 secs (annoying), increased RAM usage from 500mb to 1.5gb (not a major problem either), but means that it solves my fixed directory issue, distribution issue, and update issue, simply by having the files all stuck into the executable. Here's the new code I'm using:
      img = BitmapFactory.FromResource("Shots/" + filename);
      The next thing I was going to try was to mark them as Content > Copy if Newer. This would resolve the executable size and RAM usage (and also the directory issue as well), however it seems that I'd need to highlight them all, and move them from Resource to Content. As an up-front job this isn't too bad, but as I add new images to the project, I'll need to go in and do this every time, which gets annoying, as the VS2015 default is Resource. Also, I'm not sure how this would work in terms of updates. Would something like ClickOnce deployment recognise new PNGs and install them to the users?
       
      I also have 3,000 ZIP files (~500kb each) which also need deploying and updating in the same way. These are currently read directly from my HDD until I can find a permanent solution for adding these to the project as well.
      Can anyone thing of a better way of doing what I'm trying to achieve?
      Thanks for any help folks.
       
    • By Felis Nigripes
      I'm doing a test quest.
      The player gets a quest from an NPC to bring him fish.

      Once the player picks up the fish, the original NPC gets replaced by a new one with a new conversation trigger. The NPC tells the Player "Well done" and should give 200xp.

      The script tells the xp counter to go up by making a reference to the gameobject that holds the text component
       
      But it throws this error:
       

       
      I'm aware that the error may hide in plain sight. I just have to sort this out, since I'm writing the AI at the same time, and the time it takes to resolve everyone of these errors is tremendous.
      Plus, I think I'll learn something. I've been having trouble with some basic functionalities recently. There might be something wrong with my understanding on how programming works.
       
      Glad if someone could help (:
       
       
       
      Edit: I'm fully aware that the update function requires an input. I call the function in the editor when the dialogue ends, it still doesn't work.
       
    • By Vu Chi Thien
      Hi fellow game devs,
      With the help of  @CombatWombat in my previous post about clutch modeling, I now have a solid direction for the modeling the clutch. The way I'm doing it is having 2 clutch states: locked and unlocked. EngineRPM and torque will be calculated separately in each state. My problem right now is the logic and code for specifying locking and unlocking.
      The condition for locking is when (engineSpeed - drivetrainSpeed) in previous update cross zero (different sign) with the current update (to determine if engineSpeed = drivetrainSpeed or not in-between updates) and engineTorque <= clutchTorque.
      The condition for unlocking is when engineTorque > clutchTorque.
      The diagram looks roughly like this (taken from matlab website with the similar implementation):

       
      However, the 2 conditions are triggers for switching states, not for determine the current state to be in, so in the end my clutch state just jumped around. I don't have a lot of experience in doing state machine, so can some one give me rough code of how to implement this? Below is my rough code:
      speedError = engineSpeed - drivetrainSpeed; if ((Math.Sign(speedError) != Math.Sign(deltaW) && currentTotalEngineTorque <= clutchReactTorque)) { clutchLocked = true; } else clutchLocked = false; deltaW = speedError; //end of update I think the main struggle is the cross zero. Because cross zero is the "trigger condition" to check if the clutch should lock when it is slipping, not the condition for continuous locking, while the code I have above is the "continuous condition" saying "this condition is true then it is locked/unlocked". Another word, if the clutch is slipping, the condition above would decide if it's locked or not, but once it is locked, the cross zero condition is not true anymore (since speedError and deltaW have same sign as engineSpeed == drivetrainSpeed when clutch is locked). I'm sorry that I cannot explain this better as English is not my first language.
    • By nxrighthere

      BenchmarkNet is a console application for testing the reliable UDP networking solutions.
      Features:
      Asynchronous simulation of a large number of clients Stable under high loads Simple and flexible simulation setup Detailed session information Supported networking libraries:
      ENet (C# Wrapper) UNet LiteNetLib Lidgren MiniUDP Hazel Photon Neutrino DarkRift More information and source code on GitHub.
      You can find the latest benchmark results on the wiki page.
  • Advertisement