Introduction
The first question is why we call unmanaged code before we discuss how to call unmanaged code.
There are possibly two reasons to call unmanaged code
- You want to reuse your code which is already written in unmanaged environment e.g. VC 6.0
- You want to Perform some low level work i.e. (need in line assembly in your program)
How to call unmanaged code
The first time I saw this topic in Tom Archer's "Inside C#" which explain how to call unmanaged DLL from the C#
Program 1
// 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); } }Then I tried to make my own DLL and call that DLL from my Application
Program 2
// Dll1.cpp // Written by Zeeshan Amjad #includeAnd my C# Program is: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); }
Program 3
// 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 ran program and my program crashed because it can not find DLL1.dll - either in current directory nor in the path. It threw a DllNotFoundException. To handle this I had to catch this exception. So I change my program little bit to:
Program 4
// 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()); } } };And also copied the DLL1.dll to the current folder to avoid this exception.
Now again my program crashed when I tried to run it. This time it threw a EntryPointNotFoundException. To handle this more elegantly I should also catch this and display an error message for this exception rather than crash the program. This is new version of the program:
Program 5
// 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 this 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 this C# program. In fact when you write a function in C++ the compiler decorates the function name to enable function overloading. The function which exports by DLL is not Message. To get the exact name type
dumpbin -exports dll1.dll
at command prompt. The part of output of this utility is
ordinal hint RVA name 1 0 00001005 ?Message@@YGXPAD@ZThere isn't any standard way of decorating function names, so we should tell the compiler not to decorate function name.
This is revised version of DLL code.
Program 6
// Dll1.cpp // Written by Zeeshan Amjad #includeextern "C" is used to tell the compiler not to decorate the function name.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); }
Now if we see the function name from the dumpbin utility its output looks like this.
ordinal hint RVA name 1 0 0000100A _Message@4Here @ shows the function uses the standard calling convention and 4 shows the number of bytes pushed on the stack for parameters. In a 32 bit environment like windows 9x and NT/2000 the address is stored in 32 bits i.e. 4 bytes. It means there is only one parameter in the stack - in other words this function takes only one parameter.
Now the above C# Program works fine without any change and displays a message box with Text "Hello world" with the caption "Message from DLL"
How to call assembly in C#
Let's do an experiment with inline assembly in a DLL. I can not call assembly language from C# but I know I can call unmanaged DLLs from C#. I'll make a DLL which calculates the speed of CPU, vendor name, Family, Model and Stepping of CPU using in line assembly language.
Program 7
// 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 of this.
Program 8
// Client1.cpp // Written by Zeeshan Amjad #includeNow I m going to write the same client in C#.#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; }
Program 9
// 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()); } } };