Introduction
With .NET Assemblies, today it is much easier to get all information about the classes and members inside. You can simply use Reflection and you will get all Meta Data even if it is private
, internal
or public
. But did you ever try to get the content of native libraries such as "advapi32.dll" or "user32.dll". Sure, the best way should be never to use pInvoke because your application will not be CLS compliant. However, in some circumstances, you have to.
Background
Some years ago, I developed an application in Delphi 6, showing me the exported method headers of native (Win32) libraries. However, that application was no longer useful since I started developing with Visual Studio .NET, because I don't like switching between several applications while developing.
The Old Delphi Code
The following Delphi snippet is part of the code that has to be converted into C#. The code was hosted inside a COM Library because I wanted to use it with other languages. The COM Library still works fine today (except, sometimes getting the whole number of methods with Windows 7), but I don't want to use COM Interop in my .NET applications in future.
function ListDLLExports(const FileName: string): TStringList;
type
TDWordArray = array [0..$FFFFF] of DWORD;
var
imageinfo: LoadedImage;
pExportDirectory: PImageExportDirectory;
dirsize: Cardinal;
pDummy: PImageSectionHeader;
i: Cardinal;
pNameRVAs: ^TDWordArray;
Name: string;
begin
{$Warnings Off}
Result := TStringList.Create;
if MapAndLoad(PAnsiChar(FileName), nil, @imageinfo, True, True) then begin
try
pExportDirectory := ImageDirectoryEntryToData
(imageinfo.MappedAddress, False, IMAGE_DIRECTORY_ENTRY_EXPORT, dirsize);
if (pExportDirectory <> nil) then begin
pNameRVAs := ImageRvaToVa(imageinfo.FileHeader,
imageinfo.MappedAddress, DWORD(pExportDirectory^.AddressOfNames), pDummy);
for i := 0 to pExportDirectory^.NumberOfNames - 1 do begin
Name := PChar(ImageRvaToVa(imageinfo.FileHeader,
imageinfo.MappedAddress, pNameRVAs^[i], pDummy));
Result.Add(Name);
end;
end;
finally
UnMapAndLoad(@imageinfo);
end;
end;
{$Warnings ON}
end;
The C# Code
Ok, I'm sorry but I deleted all the code written in C#, after I fixed it in Managed C++, but I did a posting on CodeProject some years ago. So if you are interested, please have a look at this link.
As you can see by the posting time stamp from above, I've tried a long time to migrate the Delphi snippet to C#, but finally I got no results. First of all, it was a lot of code to pInvoke and playing around with marshalling attributes, but finally it didn't work. It was very frustrating but finally I decided to give Managed C++ a chance.
I was very surprised at how easy it is to pInvoke in C++, if you mix native and managed C++. You do not have to pInvoke anything because you can access all Win32 libraries in C++ and on the other side you can also create a .NET Assembly and use it as usual in other .NET Assemblies.
The Hybrid Code (C++ and Managed C++)
#pragma once
using namespace System;
using namespace System::Runtime::InteropServices;
using namespace System::Collections::Generic;
using namespace System::Reflection;
namespace System{namespace Runtime{namespace InteropServices{namespace PlatformInvoke
{
public ref class NativeImage
{
public:
static List<String^>^ OpenImage(String ^imageName, String ^dllPath)
{
List<String^>^ methodNames = gcnew List<String^>();
LOADED_IMAGE* pLoadedImage = new LOADED_IMAGE();
char* pImageName = (char*)Marshal::StringToHGlobalAnsi(imageName).ToPointer();
char* pDllPath = (char*)Marshal::StringToHGlobalAnsi(dllPath).ToPointer();
try
{
if (MapAndLoad(pImageName, pDllPath, pLoadedImage, TRUE, TRUE))
{
ULONG pSize;
PIMAGE_EXPORT_DIRECTORY pImageExportDirectory =
(PIMAGE_EXPORT_DIRECTORY)ImageDirectoryEntryToData
(pLoadedImage->MappedAddress, FALSE, IMAGE_DIRECTORY_ENTRY_EXPORT, &pSize);
PIMAGE_SECTION_HEADER *pImageSectionHeader = new PIMAGE_SECTION_HEADER();
if (pImageExportDirectory != NULL)
{
PULONG pImageImportDescriptor = (PULONG)ImageRvaToVa
(pLoadedImage->FileHeader, pLoadedImage->MappedAddress,
pImageExportDirectory->AddressOfNames, pImageSectionHeader);
for (ULONG i = 0; i < pImageExportDirectory->NumberOfNames; i++)
{
PVOID pVoid = ImageRvaToVa(pLoadedImage->FileHeader,
pLoadedImage->MappedAddress, pImageImportDescriptor[i] , pImageSectionHeader);
methodNames->Add(gcnew String((PSTR)pVoid));
}
}
UnMapAndLoad(pLoadedImage);
}
return methodNames;
}
catch (Exception ^exception)
{
System::Diagnostics::Debug::WriteLine(exception->Message);
return gcnew List<String^>();
}
finally
{
Marshal::FreeHGlobal(IntPtr(pImageName));
Marshal::FreeHGlobal(IntPtr(pDllPath));
}
}
};
}}}}
Installing the AddIn
First of all, download the ZIP file, save and extract it into a temporary folder. The folder contains a .vsi file which you can execute with a double click. If everything is fine, the Visual Studio Installer Assistant should pop up. Please follow the wizard until the AddIn is installed.
After a successful installation, open Visual Studio 2005/2008/2010 and click on MainMenu>View>Object browser (Native). A new ToolWindow called "Native Object Browser should be opened. In the ToolWindow, click on the Open-Button on the Toolbar and select a native library, e.g. "C:\Windows\System32\User32.dll".
Now you can see all the exported methods of the native library. If you select one of the exported methods on the right list view, press F1 for its online help. You can also click on one of the provided links on the dynamic help window.
Points of Interest
Normally, I develop my applications in C#, but this project has taught me that C# is not always the easiest way. I have tried to develop the main functionality (enumerating method headers) for several months in C#, but last but not least I fixed it in about 1 day with a mix of C++ and Managed C++. Well finally I think, it was a great benefit for me, and in future I will use Managed C++ at first.
Some Links
The source code and the installer are also available at www.codeplex.com at http://nativebrowser.codeplex.com/.