Jump to content
  • Advertisement
Sign in to follow this  
blueshogun96

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
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!