Using PInvoke To Call An Unmanaged DLL From Managed C++

From Kate Gregory's Codeguru column, "Using Visual C++ .NET".

-->

If you've been maintaining a large and complex C++ application for several years, it probably serves as a mini-museum of technology all by itself. There will be code that interacts with the user, business logic, and data access logic - and I hope it's not all together in one giant source file. Chances are that you've used several different techniques to split that code into modules.

One popular technique is COM, and creating and using COM components in Visual C++ 6 is relatively easy. But it's not the only way to reuse code: DLLs are relatively easy to write and use from Visual C++ 6, and many developers found them easier than COM for code that was only to be called from one place. Chances are your system has a few COM components and also a few DLLs.

The COM Interop story is vital to the acceptance of .NET by today's programmers. You need access to the huge body of working tested code that is in production today, deployed as COM components. Your .NET code can call old COM code after someone (either the COM developer, or you if no-one else is available) creates a Runtime Callable Wrapper for the component. If you write a new component in .NET, it can be used by old COM code after someone (ok, probably you again) creates a COM Callable Wrapper and registers the interop assembly. These techniques are the same across the .NET languages, because they involve typing commands like tlbexp or tlbimp at a command prompt. Once the appropriate wrapper has been created, you're in It Just Works territory: the .NET code acts as though the COM component is really a .NET object, or the COM code calls the new exciting .NET object as though it were a traditional COM component.

So let's go on past the COM Interop story to the PInvoke story. The P stands for platform and it's a reference to the underlying platform on which the Common Language Runtime is running. Today that will be some flavor of Windows, but in the future, who knows? There are projects underway to recreate the CLR on non-Windows platforms.

Calling a function in a DLL

I'm going to use examples of calling functions from the Win32 API, even though you would never actually call these functions from a managed C++ application - they are encapsulated very conveniently in the Base Class Library. It's just that these examples are sure to be on your machine, so using them spares you the trouble of writing a DLL just to call it.

For my first example, I'm going to use GetSystemTime(). The documentation tells you that it takes an LPSYSTEMTIME, which it fills with the current UTC time. The documentation tells you the name of the function, the parameters it takes, and the fact that it's in kernel32.lib. (You can't use the version in the .lib file, but there's a corresponding kernel32.dll that you can use.) This is everything you need to declare the function prototype in an unmanaged application, like this:

using namespace System::Runtime::InteropServices;
[DllImport("kernel32.dll")]
   extern "C" void GetSystemTime(SYSTEMTIME* pSystemTime);

The DllImport attribute tells the runtime that this function is in a DLL, and names the DLL. The extern C takes care of the rest. Now you need to teach the compiler what a SYSTEMTIME pointer is. By clicking on LPSYSTEMTIME on the help page for GetSystemTime(), you can find a definition of the SYSTEMTIME struct:

typedef struct _SYSTEMTIME { 
    WORD wYear; 
    WORD wMonth; 
    WORD wDayOfWeek; 
    WORD wDay; 
    WORD wHour; 
    WORD wMinute; 
    WORD wSecond; 
    WORD wMilliseconds; 
} SYSTEMTIME, *PSYSTEMTIME; 

If you copy and paste this struct definition into a Managed C++ application, it won't compile - WORD is a typedef that isn't automatically usable in .NET applications. It just means a 16-bit integer, anyway, and that's a short in C++. So paste it in (before the prototype for GetSystemTime()) and change all the WORD entries to short.

Now here's some code that calls this function:

SYSTEMTIME* pSysTime = new SYSTEMTIME();
GetSystemTime(pSysTime);
Console::WriteLine("Current Month is {0}", 
                   __box(pSysTime->wMonth));

What could be simpler? When this code runs, it prints out:

Current Month is 9

The documention is clear that months are 1-based in the SYSTEMTIME structure, so yes indeed September is month 9.

Remember, this example is here to show you how to declare the function type and the structures it uses, not how to get the current time and date. A .NET application gets the current time and date with DateTime::Now():

System::DateTime t = DateTime::Now;
Console::WriteLine( "Current Month is {0}", 
                    __box(t.get_Month()));

(The trick here is to remember, or to be reminded by Intellisense, that DateTime is a value class, which means that C++ applications don't have to work with a pointer to a DateTime, but can just use a DateTime object as you see here. Also, Now is a property, not a function, of the DateTime class. Let Intellisense help you, because these things are non-trivial to keep track of yourself. It shows different symbols for properties and functions.)

Dealing with Strings

The GetSystemTime() function involved a struct, and once you made a managed-code version of the struct definition, it was simple enough to work with. Things are little trickier when the DLL function you want to call works with a string. That's because string manipulation is a bit of a technology museum itself.

Strings come in two basic flavors: ANSI, or single-byte, and wide, or double-byte. Strings in .NET are all Unicode which are compatible with wide strings in the Win32 world. Many of the Win32 functions that use strings actually come in two versions: one for ANSI strings and one for wide strings. For example there is no MessageBox function really: there is a MessageBoxA and a MessageBoxW. This is hidden from you when you program in Visual C++ 6 by a #define that changes function names based on your Unicode settings. It's hidden from you in .NET by the Interop framework.

If you wish, you can declare MessageBox with a DllImport attribute that only specifies the DLL. The declaration from the online help is:

int MessageBox( HWND hWnd, 
                LPCTSTR lpText, 
                LPCTSTR lpCaption, 
                UINT uType);

This translates into the following .NET signature:

MessageBox( int hWnd, 
            String* lpText, 
            String* lpCaption, 
            unsigned int uType);

How do I know? Handles and the like generally map to integers. Pointers to various kinds of strings become String*. UINT is an unsigned int. If you're stuck, do a search in the online help for one of the many tables of data types that are scattered about. One of them will have the mapping you need.

Here's the prototype for MessageBox:

[DllImport("user32.dll")]
   extern "C" void MessageBox( int hWnd, 
                               String* lpText, 
                               String* lpCaption, 
                               unsigned int uType);

And here's a simple way to use it:

MessageBox(0,"Hi there!","",0);

This is, however, a little wasteful. You know (or you should know) that "Hi There" is a Unicode string, and that MessageBoxW is the function you really want. Here's a revamp of the prototype that makes it clear:

[DllImport( "user32.dll", 
            EntryPoint="MessageBoxW", 
            CharSet=CharSet::Unicode)]
   extern "C" void MessageBox( int hWnd, 
                               String* lpText, 
                               String* lpCaption, 
                               unsigned int uType);

call the function the same way, but the DllImport attribute is now mapping directly to the right entry point, and using the right marshaling technique, without having to make these decisions on the fly every time you call this function from the DLL.

Don't forget that MessageBox() is just an example of a function call that takes a string and that most developers are familiar with. If you really want to display a message box from within your code, use the MessageBox class in System::Windows::Forms (remember to add a #using statement to the top of your source code mentioning System.Windows.Forms.dll, where this class is implemented. Here's an example:

System::Windows::Forms::MessageBox::Show(NULL, 
   "Hi from the library!","",
    System::Windows::Forms::MessageBoxButtons::OK,
    System::Windows::Forms::MessageBoxIcon::Information);

Conclusion

Do you have useful, even vital functionality wrapped up in a DLL? Would you like to be able to access that DLL from your new .NET applications? Well, go ahead. All you have to do is declare a prototype of the function with a DllImport attribute and an extern C linkage. Declare any structures the function uses, and just call it as though it were managed code. (Remember that it isn't, though, and that means your application must have permission to execute unmanaged code.) Give the framework a hand and tell it you're using wide strings, and away you go. Old technologies can never die as long as they can be called from new ones.

About the Author

Kate Gregory is a founding partner of Gregory Consulting Limited (www.gregcons.com). In January 2002, she was appointed MSDN Regional Director for Toronto, Canada. Her experience with C++ stretches back to before Visual C++ existed. She is a well-known speaker and lecturer at colleges and Microsoft events on subjects such as .NET, Visual Studio, XML, UML, C++, Java, and the Internet. Kate and her colleagues at Gregory Consulting specialize in combining software develoment with Web site development to create active sites. They build quality custom and off-the-shelf software components for Web pages and other applications. Kate is the author of numerous books for Que, including Special Edition Using Visual C++ .NET.




Comments

  • Three or more Significant Realities Your Mortgage Officer Hopes You Noticed

    Posted by aredierce on 11/12/2012 08:49am

    Second, a casus belli for a plus loan stay in individuals who have is ready to proffer quotes in free of cost [url=http://www.merrychristmaspaydayloans.co.uk/]christmas loans[/url] A tax preparer estimates ones refund according to you made receive an unsecured will be the absence of assets in the process

    Reply
  • What about using unmanaged DLLs exporting classes?

    Posted by controlcraft on 11/25/2006 10:31pm

    I have a regular DLL, written in VC++ exporting a class, rather than individual methods. Can I use this in C#?

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Learn How A Global Entertainment Company Saw a 448% ROI Every business today uses software to manage systems, deliver products, and empower employees to do their jobs. But software inevitably breaks, and when it does, businesses lose money -- in the form of dissatisfied customers, missed SLAs or lost productivity. PagerDuty, an operations performance platform, solves this problem by helping operations engineers and developers more effectively manage and resolve incidents across a company's global operations. …

  • Today's agile organizations pose operations teams with a tremendous challenge: to deploy new releases to production immediately after development and testing is completed. To ensure that applications are deployed successfully, an automatic and transparent process is required. We refer to this process as Zero Touch Deployment™. This white paper reviews two approaches to Zero Touch Deployment--a script-based solution and a release automation platform. The article discusses how each can solve the key …

Most Popular Programming Stories

More for Developers

RSS Feeds