Using dumpbin.exe as an Aid for Declaring P/Invokes

Chris Tacke
Applied Data System

March 2003

Applies to:
    Microsoft® .NET Compact Framework 1.0
    Microsoft® Visual Studio® .NET 2003

Summary: Learn how to use dumpbin.exe as an aid for declaring P/Invokes in Microsoft .NET Compact Framework-based applications. (6 printed pages)

Contents

Introduction
P/Invoke
dumpbin.exe
Exporting Functions from a DLL
Function Parameters
Conclusion

Introduction

The .NET Compact Framework targets Pocket PC 2000, 2002, and embedded Windows CE .NET 4.1 devices, and has been developed as a subset of the full .NET Framework. The .NET Compact Framework coupled with the Smart Device Project support in VS .NET allows developers to write managed code in Visual Basic or C# that is executed in a common language runtime analogous to that in the full .NET Framework.

However, as a subset of the .NET Framework, the .NET Compact Framework supports approximately 25% of the types across the entire range of namespaces in addition to several .NET Compact Framework-specific types for dealing with user input, messaging, and SQL Server™ 2000 Windows CE Edition 2.0. For developers, this means that there will be some functionality that you can access only by dropping down to the operating system (Windows CE) APIs.

Luckily for you, the .NET Compact Framework (like its desktop cousin) provides developers with a fantastic "swiss army knife" known as Platform Invoke (P/Invoke). This service allows managed code to invoke unmanaged functions residing in DLLs.

P/Invoke

Platform Invoke isn't actually new; it's just a new name, so you're probably already familiar with the concept. If you're a VB developer, you know it as a Declare. If you're a C developer you know it as a dllimport. P/Invoking is simply a methodology for calling functions that are publicly exported from a dynamic link library (DLL).

Because the .NET Compact Framework is so lightweight, you, the developer, may need to P/Invoke external functions from time to time. For VB developers, external functions could often be called without having a strong understanding of how to actually write the Declare statements because you had the API Viewer and pretty exhaustive online archives.

The .NET Compact Framework doesn't come with an API Viewer-like tool, and while the number of examples and resources on the Internet are growing rapidly, there is still no definitive code archive that will provide all the P/Invoke function declarations you'll need. VB and eVB developers, this is your opportunity to get familiar with a tool that C developers have come to rely on: dumpbin.exe.

Dumpbin.exe

Standard Object File Format (COFF) binary files (which include DLLs, LIBs and EXEs) provide a lot of useful header information, including all of the functions that they export. Nicely enough, Microsoft provides a command-line tool with almost all of their development environments, including Visual Studio .NET 2003, that allows you to easily extract this information. This tool is called dumpbin.exe.

As I mentioned, dumpbin.exe is a command-line tool, so to use it you will need to open a command window by clicking Start | Run and typing 'cmd.exe'. Dumpbin.exe may not be in your system path, so to keep the amount of typing to a minimum it's easiest to run all dumpbin.exe commands from the directory in which it resides.

Tip   If you are using Studio 2003, dumpbin.exe is installed by default in C:/Program Files/Microsoft Visual Studio .NET 2003/Vc7/bin/. On my system, simply running it gave an error because it could not find mspdb71.dll. To remedy this and avoid having to change my system path, I simply copied dumpbin.exe to the C:/Program Files/Microsoft Visual Studio .NET 2003.0/Common7/IDE/ directory where mspdb71.dll resides, and ran it from there.

Dumpbin.exe has a lot of command-line switches available, but the one that we are primarily interested in is /EXPORTS, which will give the names of all of the functions the COFF exports. Let's look at an example of its use.

CoreDLL.dll contains the Windows CE kernel and exports a majority of the Win32 functions usable by applications. If you've installed eVC and the Pocket PC 2002 SDK, you can use dumpbin.exe to view these functions by running it on coredll.lib with the following command:

Dumbin.exe /EXPORTS "C:/Windows CE Tools/wce300/Pocket PC 2002/lib/arm/coredll.lib"

As you would expect, this will give a very long dump. Below is a dump that I've trimmed down greatly to show just some common functions we'll be looking at in more depth:

Microsoft (R) COFF/PE Dumper Version 7.10.2292
Copyright (C) Microsoft Corporation.  All rights reserved.
Dump of file C:/Windows CE Tools/wce300/hpc2000/lib/arm/coredll.lib
File Type: LIBRARY
     Exports
       ordinal    name
            23    GetLocalTime
           257    GetWindowTextW
            33    LocalAlloc
            36    LocalFree
Note   The COM standard requires that COM DLLs export DllCanUnloadNow, DllGetClassObject, DllRegisterServer and DllUnregisterServer. Typically they will export nothing else. This means that you cannot get COM object or method information using dumpbin.exe.

Exporting Functions from a DLL

You may have noticed that at the beginning of the dump there are functions with somewhat readable names, but they may start with a question mark or underscore and have additional characters suffixed. These are called decorated names (often referred to as mangled names).

By default, the compiler decorates names and internally this is how they are referenced. Decorated names, however, aren't very readable or convenient for coding, so there are a few ways to get the linker to export the function name undecorated.

Let's assume you have the following function:

int foo(int a) {...}

You then make the function public exported by adding the dllexport storage class attribute like this:

int __declspec(dllexport)
foo(int a) {...}

This will export foo() with the mangled name ?foo@@YAHH@Z. To export it undecorated, you have two options. The first is to use the extern "C" declaration when the function is declared, so the declaration would now look like this:

extern "C" int __declspec(dllexport)
foo(int a) {...}

Alternately, you can add a DEF file to the project that describes the exported function. Simply add a file with a .DEF extension to you project (we'll call it MyDef.DEF) and then add the following text to the file:

LIBRARY      MyDLL
EXPORTS
   Foo

And add the following linker option to the project:

/def:"mydef.def"

More information on decorated names and the rules for their format can be found in the article "Decorated Names" on MSDN.

Function Parameters

As you may have noticed already, dumpbin.exe provides the exported function names but doesn't help with function parameter names or even types. For these you will need a header file for the library.

The header file or files should be available from the library publisher or author. The names don't always coincide with their DLL or LIB file. For example, GetWindowTextW—which we saw in the dump from coredll.lib—is defined in winuser.h.

For most cases, determining what data type to use for parameters in your P/Invoke call is pretty straightforward. In some instances, however, it can be difficult to determine exactly what data type to use. In fact, there are often instances where the call can be made in multiple ways. Let's look at a couple of quick examples.

First, let's look at a pretty straightforward API—SetSystemTime. SetSystemTime takes a single input parameter or a SYSTEMTIME struct. While it is a struct, its members are all fixed length, so the CF can marshal it without any problem. We simply declare a SYSTEMTIME struct in our code:

public struct SYSTEMTIME
{
   public UInt16 Year ;
   public UInt16 Month ;
   public UInt16 DayOfWeek ;
   public UInt16 Day ;
   public UInt16 Hour ;
   public UInt16 Minute ;
   public UInt16 Second ;
   public UInt16 MilliSecond ;
}

We declare our P/Invoke function:

[DllImport("Coredll.dll") ", EntryPoint="SetSystemTime"]
private static extern bool SetSystemTime(ref SystemTime st);

And then calling the function is as simple as this:

SYSTEMTIME st = new SYSTEMTIME(2003, 1, 0, 15, 18, 19, 00, 0);
SetSystemTime(ref st);

In an actual application, it would probably be wise to wrap the actual P/Invoke SetSystemTime call with a public method that can then validate the values in the SYSTEMTIME struct (for example, make sure minutes < 60), but I'll leave this as an exercise for the reader.

Some APIs are a bit more complex, and as I mentioned earlier, there are multiple ways of calling them. Most of these involve having variable-length data parameters (buffer pointers) or structs containing variable-length data elements.

The latter instance (structs containing variable-length data) can get quite complex due to limitations with the .NET Compact Framework marshaller. Handling them using the fixed and unsafe statements is beyond the scope of this article. We can, however, look at a simpler example with the GetWindowText API.

The GetWindowText definition takes a pointer to a string buffer as one of its parameters. It relies on the caller to ensure that the buffer has already been allocated and is large enough to hold the data it will copy to the buffer. This buffer can be handled in three ways: 1) As a StringBuilder class, which is a .NET wrapper around a string buffer, 2) as an array of bytes, or 3) as an IntPtr, which is a .NET wrapper around a memory handle. The choice of which to use is largely a matter of personal preference, though each has inherent benefits and drawbacks.

The StringBuilder is probably the simplest form to use, and it allows the developer to keep in line with simple .NET Compact Framework coding practices. The downside is that under the hood a lot has to happen, so it's not the most efficient way to handle things. Let's look at how the code would look.

First the P/Invoke declaration:

[DllImport("Coredll.dll") ", EntryPoint="GetWindowTextW"]
private static extern int GetWindowText(
                                          int hWnd
                                          StringBuilder lpString
                                          int nMaxCount);

With this declaration, we simply must create a StringBuilder object, making sure to size it properly, and then pass it to the P/Invoke method like this:

StringBuilder sb = new StringBuilder(bufferSize);
GetClassWindowText(hwnd, sb, bufferSize);
MessageBox.Show(sb.ToString());

Using a byte array is a more efficient way to call the API as far as overhead in marshalling, but you end up with an array that then must be processed to get a string. Still, if your goal is to receive a byte array for further processing, it works well.

The P/Invoke declaration changes only in the second input parameter, so it now looks like this:

[DllImport("Coredll.dll") ", EntryPoint="GetWindowTextW"]
private static extern int GetWindowText(
                                          int hWnd
                                          byte[] lpString
                                          int nMaxCount);

And would be called like this:

byte[] buffer = new byte[bufferSize];
int len = GetWindowText(hwnd, buffer, buffersize);
string windowName = Encoding.Unicode.GetString(buffer, 0, len);
MessageBox.Show(windowName);

For GetWindowText, it's unlikely that performance would ever be an issue, but with another API it might. In that case, calling the API with an IntPtr will have significantly less overhead. On the negative side though, you must allocate and release the memory buffer manually. This entirely bypasses the .NET Compact Framework memory manager and opens your code up to memory leaks if you're not very careful.

As with any project, that risk has to be weighed against the performance benefits. Again, the P/Invoke declaration only changes at the second input parameter, so it would look like this:

[DllImport("Coredll.dll") ", EntryPoint="GetWindowTextW"]
private static extern int GetWindowText(
                                          int hWnd
                                          IntPtr lpString
                                          int nMaxCount);

As I said before, memory allocation and deallocation must be handled manually. This is done with the LocalAlloc and LocalFree APIs, which also must be P/Invoked. The declarations for each are as follows:

[ DllImport("coredll.dll")]
public static extern IntPtr LocalAlloc(uint flags, uint cb);
[ DllImport("coredll.dll")]
public static extern IntPtr LocalFree(IntPtr p);

And the implementation would look like this:

IntPtr p = LocalAlloc(0x40, bufferSize);
GetClassName(hwnd, p, bufferSize);
string windowName = Marshal.PtrToStringUni(p);
LocalFree(p);
MessageBox.Show(windowName);

Conclusion

.NET Compact Framework developers will, from time to time, find it necessary to directly call a DLL function from managed code. By leveraging tools like dumpbin.exe and inspecting header files, developers can formulate the required P/Invokes quickly and easily.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值