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
Hide Copy Code
// 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
Hide Copy Code
// Dll1.cpp // Written by Zeeshan Amjad #include <windows.h> 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); }And my C# Program is:
Program 3
Hide Copy Code
// 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
Hide Copy Code
// 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
Hide Copy Code
// 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
Hide Copy Code
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
Hide Copy Code
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
Hide Copy Code
// Dll1.cpp // Written by Zeeshan Amjad #include <windows.h> 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 if we see the function name from the dumpbin utility its output looks like this.
Hide Copy Code
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
Hide Shrink Copy Code
// 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
Hide Copy Code
// Client1.cpp // Written by Zeeshan Amjad #include <iostream.h> #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 I m going to write the same client in C#.
Program 9
Hide Shrink Copy Code
// 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()); } } };I write GUI for this program. The image below shows the output.
No comments:
Post a Comment