Using Unmanaged Code in C#

 

Contents

Introduction

The first thing you should understanind is why you would want to use unmanaged code. There are possibly two reasons to call unmanaged code:

  1. You want to reuse your code which is already written in an unmanaged environment e.g. VC 6.0
  2. You want to perform some low level work. ( i.e. need in line assembly in your program)

Go to Table of Contents

How to call unmanaged code

The first time I saw this topic was in Tom Archer's "Inside C#" which explains how to call an unmanaged DLL from C#:

// Sample program to call unmanaged code
using System;
using System.Runtime.InteropServices;
class PInvoke1App
{
    [DllImport("user32.dll")]
    static extern int MessageBoxA(int hWnd, 
                                  string strMsg, 
                                  string strCaption,
                                  int iType);
    public static void Main() 
    {
        MessageBoxA(0, 
                    "Hello, World!", 
                    "This is called from a C# app!",
                    0);
    }
}

I then tried to make my own DLL and call that DLL from my Application:

// Dll1.cpp
// Written by Zeeshan Amjad
#include 
BOOL __stdcall DllMain(HINSTANCE hInst, 
                       DWORD dwReason, 
                       LPVOID lpReserved) {
   return TRUE;
}
__declspec(dllexport) void __stdcall Message(char* p_szMessage)
{
   MessageBox(NULL, p_szMessage, "Message from DLL", MB_OK);
}

The following is my C# program:

// Native2.cs
// Written by Zeeshan Amjad
using System;
using System.Runtime.InteropServices;
class MainClass {
   [DllImport("Dll1.dll")]
   static extern void Message(string msg);
   public static void Main() {
      Message("Hello world");
   }
};

Now I run my program which crash because it can not find DLL1.dll in either the current directory or in the path. This causes a DllNotFoundException exception to be thrown. To handle this I have to catch this exception too. So I change my program a little bit as shown:

// Native3.cs
// Written by Zeeshan Amjad
using System;
using System.Runtime.InteropServices;
class MainClass {
   [DllImport("Dll1.dll")]
   static extern void Message(string msg);
 
   public static void Main() {
      try {
         Message("Hello world");
      }
      catch(DllNotFoundException e) {
         Console.WriteLine(e.ToString());
      }
   }
};

I also copy the DLL1.dll in the current folder to avoid this exception

Now again my program crash when I try to run it. This time it throws EntryPointNotFoundException. I should also catch this and display the error message of this exception rather than allowing the program to crash. Following is the new version of the program:

// Native4.cs
// Written by Zeeshan Amjad
using System;
using System.Runtime.InteropServices;
class MainClass {
   [DllImport("Dll1.dll")]
   static extern void Message(string msg);
   public static void Main() {
      try {
         Message("Hello world");
      }
      catch(DllNotFoundException e) {
         Console.WriteLine(e.ToString());
      }
      catch(EntryPointNotFoundException e) {
         Console.WriteLine(e.ToString());
      }
   }
};

This program now give the following error message:

System.EntryPointNotFoundException: Unable to find an 
entry point named Message in DLL Dll1.dll.
   at MainClass.Message(String msg)
   at MainClass.Main()
The problem is not in the C# program. In fact when you write the function in C++, the C++ compiler decorates the function name to get function overloading. The function which exports by DLL is not Message. To get the exact name type dumpbin -exports dll1.dll at the command prompt. Part of output of this utility is

 

    ordinal hint RVA      name
          1    0 00001005 ?Message@@YGXPAD@Z

There isn't any standard way of decorating the function name. So you need to tell the C++ compiler to not decorate function name. Following is the revised version of DLL code:

// Dll1.cpp
// Written by Zeeshan Amjad
#include 
   
   
BOOL __stdcall DllMain(HINSTANCE hInst, 
                       DWORD dwReason, 
                       LPVOID lpReserved) {
   return TRUE;
}
extern "C" __declspec(dllexport) void __stdcall Message(char* p_szMessage)
{
   MessageBox(NULL, p_szMessage, "Message from DLL", MB_OK);
}

extern "C" is used to tell the compiler not to decorate the function name. Now when you see the function name from the output of the dumpbin utility, it will look like this:

    ordinal hint RVA      name
          1    0 0000100A _Message@4

Here @ shows that the function uses standard calling conventions and 4 shows the number of bytes push on the stack for parameters. In 32 bit environment like windows 9x and NT/2000 the address is stored in 32 bit i.e. 4 bytes. It means there is only one parameter in the stack. In other words this function take only one parameter.

Now the above C# program works fine without any change and display a message box with the text "Hello world" and the caption of "Message from DLL"

Let's do experiment with inline assembly in a DLL. I cannot call assembly language from C# but I know I can call an unmanaged DLL from C#. I made a DLL that calculates the speed of the CPU as well as determines the vendor name, Family, Model and Stepping of CPU using inline assembly language.

// SysInfo.cpp
// written by Zeeshan Amjad
#include "SysInfo.h"
BOOL __stdcall DllMain(HINSTANCE hInst, 
                       DWORD dwReason, 
                       LPVOID lpReserved) {
  return  TRUE;
}
extern "C" __declspec(dllexport) int __stdcall getCPUSpeed() {
  LARGE_INTEGER ulFreq, ulTicks, ulValue, 
                ulStartCounter, ulEAX_EDX, ulResult;
  // it is number of ticks per seconds
  QueryPerformanceFrequency(&ulFreq);
  // current valueofthe performance counter
  QueryPerformanceCounter(&ulTicks);
  // calculate one second interval
  ulValue.QuadPart = ulTicks.QuadPart + ulFreq.QuadPart;
  // read time stamp counter
  // this asm instruction load the highorder 32 bit of the register into EDX
  // and the lower order 32 bits into EAX
  _asm {
     rdtsc
     mov ulEAX_EDX.LowPart, EAX
     mov ulEAX_EDX.HighPart, EDX
  }
  // start no of ticks
  ulStartCounter.QuadPart = ulEAX_EDX.QuadPart;
  // loop for 1 second
  do {
     QueryPerformanceCounter(&ulTicks);
  } while (ulTicks.QuadPart <= ulValue.QuadPart);
  // get the actual no of ticks
  _asm {
     rdtsc
     mov ulEAX_EDX.LowPart, EAX
     mov ulEAX_EDX.HighPart, EDX
  }
  // calculate result
  ulResult.QuadPart = ulEAX_EDX.QuadPart - ulStartCounter.QuadPart;
  return (int)ulResult.QuadPart / 1000000;
}
extern "C" __declspec(dllexport) char* __stdcall getCPUType() {
  static char pszCPUType[13];
  memset(pszCPUType, 0, 13);
  _asm {
     mov eax, 0
     cpuid
     // getting information from EBX
     mov pszCPUType[0], bl
     mov pszCPUType[1], bh
     ror  ebx, 16
     mov pszCPUType[2], bl
     mov pszCPUType[3], bh
     // getting information from EDX
     mov pszCPUType[4], dl
     mov pszCPUType[5], dh
     ror  edx, 16
     mov pszCPUType[6], dl
     mov pszCPUType[7], dh
     // getting information from ECX
     mov pszCPUType[8], cl
     mov pszCPUType[9], ch
     ror  ecx, 16
     mov pszCPUType[10], cl
     mov pszCPUType[11], ch
  }
  pszCPUType[12] = '/0';
  return pszCPUType;
}
extern "C" __declspec(dllexport) int __stdcall getCPUFamily() {
  int retVal;
  _asm {
     mov eax, 1
     cpuid
     mov retVal, eax
  }
  return (retVal >> 8);
}
extern "C" __declspec(dllexport) int __stdcall getCPUModel() {
  int retVal;
  _asm {
     mov eax, 1
     cpuid
     mov retVal, eax
  }
  return ((retVal >> 4 ) & 0x0000000f);
}
extern "C" __declspec(dllexport) int __stdcall getCPUStepping() {
  int retVal;
  _asm {
     mov eax, 1
     cpuid
     mov retVal, eax
  }
  return (retVal & 0x0000000f);
}

Here is a simple client of this DLL which is written in VC to check the functionality:

// Client1.cpp
// Written by Zeeshan Amjad
#include 
   
   
#include "SysInfo.h"
#pragma comment(lib, "SysInfo.lib")
int main() {
  cout << "CPU Speed = " << getCPUSpeed() << endl;
  cout << "CPU Type = " << getCPUType() << endl;
  cout << "CPU Family = " << getCPUFamily() << endl;
  cout << "CPU Model = " << getCPUModel() << endl;
  cout << "CPU Stepping = " << getCPUStepping() << endl;
  return 0;
}

Now the same client in C#:

// Native5.cs
// Written by Zeeshan Amjad
using System;
using System.Runtime.InteropServices;
class MainClass {
  [DllImport("SysInfo.dll")]
  static extern int getCPUSpeed();
  [DllImport("SysInfo.dll")]
  static extern string getCPUType();
  
  [DllImport("SysInfo.dll")]
  static extern int getCPUFamily();
  
  [DllImport("SysInfo.dll")]
  static extern int getCPUModel();
  [DllImport("SysInfo.dll")]
  static extern int getCPUStepping();
  
  // main program
  public static void Main() {
     // get CPU Speed
     try {
        int iCPUSpeed = getCPUSpeed();
        Console.WriteLine("CPU Speed = {0}", iCPUSpeed.ToString());
     }
     catch (DllNotFoundException e) {
        Console.WriteLine(e.ToString());
     }
     catch (EntryPointNotFoundException e) {
        Console.WriteLine(e.ToString());
     }
     
     // get CPU Type
     try {
        string strType = getCPUType();
        Console.WriteLine("CPU Type = {0}", strType);
     }
     catch (DllNotFoundException e) {
        Console.WriteLine(e.ToString());
     }
     catch (EntryPointNotFoundException e) {
        Console.WriteLine(e.ToString());
     }
 
     // get CPU Family
     try {
        int iFamily = getCPUFamily();
        Console.WriteLine("CPU Family = {0}", iFamily.ToString());
     }
     catch (DllNotFoundException e) {
        Console.WriteLine(e.ToString());
     }
     catch (EntryPointNotFoundException e) {
        Console.WriteLine(e.ToString());
     }
     
     // get CPU Model
     try {
        int iModel = getCPUModel();
        Console.WriteLine("CPU Model = {0}", iModel.ToString());
     }
     catch (DllNotFoundException e) {
        Console.WriteLine(e.ToString());
     }
     catch (EntryPointNotFoundException e) {
        Console.WriteLine(e.ToString());
     }
     // get CPU Stepping
     try {
        int iStepping = getCPUStepping();
        Console.WriteLine("CPU Stepping = {0}", 
                          iStepping.ToString());
     }
     catch (DllNotFoundException e) {
        Console.WriteLine(e.ToString());
     }
     catch (EntryPointNotFoundException e) {
        Console.WriteLine(e.ToString());
     }
  }
};

Top Introduction
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值