Calling Unmanaged Code: Part 1 - simple DLLImport

Environment: [.NET]

The managed world is beautiful. I have all classes I could want in FrameWork. What happens if I want to call some unmanaged code--such as existing C++ code? For instance, I have DLL written in C++, and want use it from C#.

Let's look some code. I have a DLL that exports a function in CDecl convention, that sums two integers:

extern "C" __declspec(dllexport) __cdecl 
                          int sum(int a,int b);

And, of course, I want to reuse this code in C#. There is no "direct" way to call unmanaged code, so you must inform the compiler about it. More specificially, you must tell the compiler about what you want to call, how you want it called, and where its needed code is located:

   [DllImport("TestDll.dll", EntryPoint="sum", 
   ExactSpelling=false,CallingConvention=CallingConvention.Cdecl)]
   static extern int sum(int a,int b);

Once you've done this, you can then call it like any other normal C# function:

   x=5;
   y=7;
   z=sum(x,y);   // x will receive 12 

Here is full C# client code - tested for Beta2:

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Runtime.InteropServices;

namespace WindowsApplication6
{
   /// <summary>
   /// Summary description for Form1.
   /// </summary>
   public class Form1 : System.Windows.Forms.Form
   {
     private System.Windows.Forms.Button button1;
     private System.Windows.Forms.TextBox textBox1;
     private System.Windows.Forms.Label label1;
     private System.Windows.Forms.TextBox textBox2;
     private System.Windows.Forms.Label label2;
    private System.Windows.Forms.TextBox textBox3;
     /// <summary>
     /// Required designer variable.
     /// </summary> 
     private System.ComponentModel.Container components = null;

     public Form1()
     {
       //
       // Required for Windows Form Designer support
       //
       InitializeComponent();

       //
       // TODO: Add any constructor code after 
       //       InitializeComponent call
     }

     /// <summary>
     /// Clean up any resources being used.
     /// </summary>
     protected override void Dispose( bool disposing )
     {
       if( disposing )
       {
         if (components != null) 
         {
           components.Dispose();
         }
       }
       base.Dispose( disposing );
     }

     #region Windows Form Designer generated code
     /// <summary>
     /// Required method for Designer support - do not modify
     /// the contents of this method with the code editor.
     /// </summary>
     private void InitializeComponent()
     {
       this.button1 = new System.Windows.Forms.Button();
       this.textBox1 = new System.Windows.Forms.TextBox();
       this.label1 = new System.Windows.Forms.Label();
       this.textBox2 = new System.Windows.Forms.TextBox();
       this.label2 = new System.Windows.Forms.Label();
       this.textBox3 = new System.Windows.Forms.TextBox();
       this.SuspendLayout();
       // 
       // button1
       // 
       this.button1.Location = new System.Drawing.Point(64, 192);
       this.button1.Name = "button1";
       this.button1.Size = new System.Drawing.Size(144, 64);
       this.button1.TabIndex = 0;
       this.button1.Text = "call sum";
       this.button1.Click 
             += new System.EventHandler(this.button1_Click);
       // 
       // textBox1
       // 
       this.textBox1.Location = new System.Drawing.Point(40, 120);
       this.textBox1.Name = "textBox1";
       this.textBox1.Size = new System.Drawing.Size(72, 22);
       this.textBox1.TabIndex = 1;
       this.textBox1.Text = "2";
       // 
       // label1
       // 
       this.label1.Location = new System.Drawing.Point(128, 128);
       this.label1.Name = "label1";
       this.label1.Size = new System.Drawing.Size(16, 16);
       this.label1.TabIndex = 2;
       this.label1.Text = "+";
       // 
       // textBox2
       // 
       this.textBox2.Location = new System.Drawing.Point(152, 120);
       this.textBox2.Name = "textBox2";
       this.textBox2.Size = new System.Drawing.Size(56, 22);
       this.textBox2.TabIndex = 3;
       this.textBox2.Text = "3";
       // 
       // label2
       // 
       this.label2.Location = new System.Drawing.Point(224, 120);
       this.label2.Name = "label2";
       this.label2.Size = new System.Drawing.Size(24, 23);
       this.label2.TabIndex = 4;
       this.label2.Text = "=";
       // 
       // textBox3
       // 
       this.textBox3.Location = new System.Drawing.Point(248, 120);
       this.textBox3.Name = "textBox3";
       this.textBox3.Size = new System.Drawing.Size(112, 22);
       this.textBox3.TabIndex = 5;
       this.textBox3.Text = "5";
       // 
       // Form1
       // 
       this.AutoScaleBaseSize = new System.Drawing.Size(6, 15);
       this.ClientSize = new System.Drawing.Size(576, 322);
       this.Controls.AddRange(new System.Windows.Forms.Control[] {
                        this.textBox3,
                        this.label2,
                        this.textBox2,
                        this.label1,
                        this.textBox1,
                        this.button1});
       this.Name = "Form1";
       this.Text = "Form1";
       this.ResumeLayout(false);

     }
     #endregion

     /// <summary>
     /// The main entry point for the application.
     /// </summary>
     [STAThread]
     static void Main() 
     {
       Application.Run(new Form1());
     }
     
     #region My Code
     #region Dll Imports
     [DllImport("TestDll.dll", EntryPoint="sum", 
     ExactSpelling=false,CallingConvention=CallingConvention.Cdecl)]
     static extern int sum(int a,int b);
     #endregion
     #region Button Click Events
     private void button1_Click(object sender, System.EventArgs e)
     {
       textBox3.Text=(int.Parse(textBox1.Text)+
                           int.Parse(textBox2.Text)).ToString();
     }
     #endregion
     #endregion
   }
}

I defined 3 textboxes, where textBox1 and textBox2 are the two operands, and where textBox3 is result. The button1 calls sum() and updates the result. The sum() method is defined as "static extern", which means it is an extern linked functions. It can't be put outside of class, becouse in C# there are no "alone" functions--everyone must belong to a class.

The calling convention is CDecl, because the C++ function is compiled with the __cdecl attribute. ExactSpelling=false tells the compiler to try "decore" the function name with "W" for Unicode or "A" for ANSI.

The button1 Click event parses two arguments from string to int, call to sum() and puts result as string.

Pay attention, you can use normal C++ declaration, without <extern "C">:

/*extern "C"*/ __declspec(dllexport) 
                      __cdecl int sum(int a,int b);
But in such case you must inform compiler about the true, or "decorated" function name. This can be done by using the EntryPoint field of DllImport attribute:
    [DllImport("TestDll.dll", EntryPoint="sum", 
    ExactSpelling=false,CallingConvention=CallingConvention.Cdecl)]
    static extern int sum(int a,int b);

It sounds very simple, becouse "int" is an isomorphic type, which means an int in C# and an int C++ are identical. What do you do when you want to operate on non-isomorhic types, such as String? Recall, that .NET string is a Class, while a C++ string is a char*, wchar_t*, or BSTR. String may be embedded in a structure, or pointed by pointer, or even something more exotic. Let's call some string function.

[DllImport("Advapi32.dll", 
                 EntryPoint="GetUserName", 
                 ExactSpelling=false, 
                 SetLastError=true)]
    static extern bool GetUserName(
      [MarshalAs(UnmanagedType.LPArray)] byte[] lpBuffer,
      [MarshalAs(UnmanagedType.LPArray)] Int32[] nSize );

This function receives two parameters: char* and int*. Because you must allocate char* buffer and receive a string by pointer, you can't use the UnmanagedType.LPStr attribute. So, you pass the ANSI string as a byte array. An int* is simpler--it's 1-element Int32 array. Let's call it:

    private void button2_Click(object sender, 
                                    System.EventArgs e)
    {
      byte[] str=new byte[20];
      Int32[] len=new Int32[1];
      len[0]=20;
      GetUserName(str,len);      
      MessageBox.Show(System.Text.Encoding.ASCII.GetString(str));
    }

This allocates 20 bytes for receiving the ANSI string, one element in Int32 array, set 20 as max string length and call it. For receiving the string from the byte array I used Text.Encoding.ASCII class.

That's enough for the first part. The second part will speak about more complex interop.

Downloads

None