All notes
CallingCpp

Intro

SO: possible to call c from csharp.

There are several ways:

PInvoke

  1. Locating and loading the DLL, and locating the address of the function in memory occur only on the first call to the function:
    1. Locates the DLL containing the function.
    2. Loads the DLL into memory.
  2. Locates the address of the function in memory and pushes its arguments onto the stack, marshaling data as required.
  3. Transfers control to the unmanaged function. Platform invoke throws exceptions generated by the unmanaged function to the managed caller.

To consume exported DLL functions:

  1. Identify functions in DLLs. The identity is specified by function name and DLL name.
  2. Create a class to hold DLL functions.
  3. Create prototypes in managed code.
    • [C#] Use the DllImportAttribute to identify the DLL and function. Mark the method with the static and extern modifiers.
    • [C++] Use the DllImportAttribute to identify the DLL and function. Mark the wrapper method or function with extern "C".
  4. Call a DLL function.

Output a message by calling puts from msvcrt.dll:


// PInvokeTest.cs
using System;
using System.Runtime.InteropServices;

class PlatformInvokeTest
{
    [DllImport("msvcrt.dll")]
    public static extern int puts(string c);
    [DllImport("msvcrt.dll")]
    internal static extern int _flushall();

    public static void Main() 
    {
        puts("Test");
        _flushall();
    }
}

To use a different name for the C# method such as putstring, you must use the EntryPoint option in the DllImport attribute, that is:


[DllImport("msvcrt.dll", EntryPoint="puts")]

MarshalAS

When calling an unmanaged function from C# code, the common language runtime must marshal the parameters and return values. For every .NET Framework type there is a default unmanaged type, which the common language runtime will use to marshal data across a managed to unmanaged function call. For example, the default marshaling for C# "string" values is to the type LPTSTR (pointer to TCHAR char buffer). You can override the default marshaling using the "MarshalAs" attribute in the C# declaration of the unmanaged function.

The MarshalAs attribute can be placed on method parameters, method return values, and fields of structs and classes.


// Marshal.cs
using System;
using System.Runtime.InteropServices;

class PlatformInvokeTest
{
    [DllImport("msvcrt.dll")]
    // The default marshaling for the parameter has been overridden from the default of LPTSTR to LPSTR.
    public static extern int puts(
        [MarshalAs(UnmanagedType.LPStr)]
        string m);
    [DllImport("msvcrt.dll")]
    internal static extern int _flushall();


    public static void Main() 
    {
        puts("Hello World!");
        _flushall();
    }
}

// MarshalAs placed on return value:
[DllImport("msvcrt.dll")] 
[return : MarshalAs(UnmanagedType.I4)]
public static extern int puts( 

Callback

For a callback in c:


typedef void (__stdcall *PFN_MYCALLBACK)();
int __stdcall MyFunction(PFN_ MYCALLBACK callback);

To call MyFunction from managed code, declare the delegate, attach DllImport to the function declaration, and optionally marshal any parameters or the return value:


public delegate void MyCallback();
[DllImport("MYDLL.DLL")]
public static extern void MyFunction(MyCallback callback);

Make sure the lifetime of the delegate instance covers the lifetime of the unmanaged code.

StructLayout

Consider the following C structure:


typedef struct tagLOGFONT 
{ 
   LONG lfHeight; 
   LONG lfWidth; 
   LONG lfEscapement; 
   LONG lfOrientation; 
   LONG lfWeight; 
   BYTE lfItalic; 
   BYTE lfUnderline; 
   BYTE lfStrikeOut; 
   BYTE lfCharSet; 
   BYTE lfOutPrecision; 
   BYTE lfClipPrecision; 
   BYTE lfQuality; 
   BYTE lfPitchAndFamily; 
   TCHAR lfFaceName[LF_FACESIZE]; 
} LOGFONT; 

In C#, you can describe the preceding struct by using the StructLayout and MarshalAs attributes as follows:


// logfont.cs
// compile with: /target:module
using System;
using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Sequential)]
public class LOGFONT 
{ 
    public const int LF_FACESIZE = 32;
    public int lfHeight; 
    public int lfWidth; 
    public int lfEscapement; 
    public int lfOrientation; 
    public int lfWeight; 
    public byte lfItalic; 
    public byte lfUnderline; 
    public byte lfStrikeOut; 
    public byte lfCharSet; 
    public byte lfOutPrecision; 
    public byte lfClipPrecision; 
    public byte lfQuality; 
    public byte lfPitchAndFamily;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=LF_FACESIZE)]
    public string lfFaceName; 
}

The structure can then be used in C# code as shown below:


// pinvoke.cs
// compile with: /addmodule:logfont.netmodule
using System;
using System.Runtime.InteropServices;
 
class PlatformInvokeTest
{   
      [DllImport("gdi32.dll", CharSet=CharSet.Auto)]
      public static extern IntPtr CreateFontIndirect(
            [In, MarshalAs(UnmanagedType.LPStruct)]
            LOGFONT lplf   // characteristics
            );
 
      [DllImport("gdi32.dll")]
      public static extern bool DeleteObject(
            IntPtr handle
            );
 
      public static void Main() 
      {
            LOGFONT lf = new LOGFONT();
            lf.lfHeight = 9;
            lf.lfFaceName = "Arial";
            IntPtr handle = CreateFontIndirect(lf);
 
            if (IntPtr.Zero == handle)
            {
                  Console.WriteLine("Can't creates a logical font.");
            }
            else
            {
                  
                  if (IntPtr.Size == 4)
                        Console.WriteLine("{0:X}", handle.ToInt32());
                  else
                        Console.WriteLine("{0:X}", handle.ToInt64());         

                  // Delete the logical font created.
                  if (!DeleteObject(handle))
                       Console.WriteLine("Can't delete the logical font");
            }
      }
}

// Result:
// C30A0AE5

COM interop

MSDN: COM Interop Tutorials.