//
you're reading...
Programming Issues/Tips

Accessing Exported Data From a DLL in Managed Code.

1. Introduction.

1.1 As we all know, exported functions from a DLL can be imported into managed code and invoked at runtime.

1.2 The System.Runtime.InteropServices.DllImportAttribute is used to declare such APIs.

1.3 But how about data exported from DLLs ?  In a C++ application, these global expirted data are usually imported by using an import library. Can these be imported into managed code ?

1.4 As it turns out, yes they can be “imported” into managed code. This article will demonstrate how this can be done.

2. Accessing Exported Data from a DLL in Managed Code – the Basic Principles.

2.1 Basically, an exported global data from a DLL will have an entry point just like an exported function.

2.2 The key is to determine the entry point of the exported data.

2.3 To do this, we treat the exported data just as if it was an exported function and use LoadLibrary() to load the DLL, then call GetProcAddress() to obtain a pointer to exported data.

2.4 The return value from GetProcAddress() is actually the address of the exported data which has already been adjusted according to the load address of the DLL.

2.5 After that, we can convert the exported data into its managed equivalent.

2.6 Of course, do not forget to call FreeLibrary() to unload the DLL when all is done.

3. Sample C++ DLL that Exports Sumbols.

3.1 Let’s say we have a DLL written in C++ named TestDLL.dll with the following code :

// TestDLL.cpp : Defines the exported functions for the DLL application.
//

#include "stdafx.h"
#define INITGUID
#include 

__declspec(dllexport) int iCPPExportedInteger = 100;
extern "C" __declspec(dllexport) int iCExportedInteger = 200;

char szExportedString[] = "Exported string.";
double dblExportedDouble = 0.123456;

// {727F4B7F-8753-4bf3-B6C7-136881A5EAAA}
DEFINE_GUID(GUID_EXPORTED,
0x727f4b7f, 0x8753, 0x4bf3, 0xb6, 0xc7, 0x13, 0x68, 0x81, 0xa5, 0xea, 0xaa);

void __stdcall TestFunction()
{
}

3.2 The project for this DLL also includes the following .DEF file :

LIBRARY	"TestDLL"
EXPORTS
	szExportedString	DATA
	dblExportedDouble	DATA
	GUID_EXPORTED	DATA
	TestFunction

3.3 Altogether, the DLL exports 5 data items :

  • iCPPExportedInteger
  • iCExportedInteger
  • szExportedString
  • dblExportedDouble
  • GUID_EXPORTED

and 1 function : TestFunction().

3.4 Using the Dependency Walker, we will be able to see these exported symbols including the exported data :

I deliberately defined TestFunction() to show that exported data will be listed together with exported functions with entry points. But of course, we know that exported data are not functions. What will be exported are pointers to these data.

3.5 An alternative to using the Dependency Walker for viewing the exported symbols of a DLL is to use DUMPBIN.EXE :

DUMPBIN /EXPORTS TestDLL.dll

And the following will be the output :

Microsoft (R) COFF/PE Dumper Version 9.00.21022.08
Copyright (C) Microsoft Corporation.  All rights reserved.

Dump of file TestDLL.dll

File Type: DLL

  Section contains the following exports for TestDLL.dll

    00000000 characteristics
    4EBBD25C time date stamp Thu Nov 10 22:32:12 2011
        0.00 version
           1 ordinal base
           6 number of functions
           6 number of names

    ordinal hint RVA      name

          3    0 00004000 ?iCPPExportedInteger@@3HA = ?iCPPExportedInteger@@3HA (int iCPPExportedInteger)
          5    1 000030EC GUID_EXPORTED = _GUID_EXPORTED
          1    2 00001020 TestFunction = ?TestFunction@@YGXXZ (void __stdcall TestFunction(void))
          2    3 00004020 dblExportedDouble = ?dblExportedDouble@@3NA (double dblExportedDouble)
          6    4 00004004 iCExportedInteger = _iCExportedInteger
          4    5 00004008 szExportedString = ?szExportedString@@3PADA (char * szExportedString)

  Summary

        1000 .data
        1000 .rdata
        1000 .reloc
        1000 .rsrc
        2000 .text

3.6 From the output of DUMPBIN, we observe the following about the exported symbols :

  • iCPPExportedInteger is a C++ symbol and its name will be mangled as shown.
  • iCExportedInteger and GUID_EXPORTED are a C symbols (due to them being declared as extern “C”) and so their names will not be mangled because of this.
  • szExportedString is actually a C++ string and dblExportedDouble a C++ double but because they have been named in the .DEF file as they appear in code, their names are not mangled.

3.7 The next section will discuss a sample C# client code that accesses the exported data from TestDLL.dll.

4. Sample C# Code that Accesses the Exported DLL Data.

4.1 Listed below is a full C# code that demonstrates how to access the exported DLL data from inside managed code :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace CSConsoleClient01
{
    class Program
    {
        [DllImport("Kernel32.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
        private static extern IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)]string lpFileName);

        [DllImport("Kernel32.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
        private static extern IntPtr GetProcAddress(IntPtr hModule, [MarshalAs(UnmanagedType.LPStr)] string lpProcName);

        [DllImport("Kernel32.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool FreeLibrary(IntPtr hModule);

        private static void TestLoadExportedData()
        {
            // Load the DLL.
            IntPtr hDLL = LoadLibrary("TestDLL.dll");

            // Perform action only if we are able to load it.
            if (hDLL != IntPtr.Zero)
            {
                // Obtain the runtime address of the exported data with name "?iCPPExportedInteger@@3HA".
                // The name is mangled because it is a C++ symbol.
                IntPtr piCPPExportedInteger = (IntPtr)GetProcAddress(hDLL, "?iCPPExportedInteger@@3HA");
                // Convert the data at the address (as contained in piCPPExportedInteger) into an Int32.
                Int32 iCPPExportedInteger = (Int32)Marshal.PtrToStructure(piCPPExportedInteger, typeof(Int32));
                // Display the data on the console output.
                Console.WriteLine("iCPPExportedInteger : {0:D}.", iCPPExportedInteger);

                // Obtain the runtime address of the exported data with name "iCExportedInteger".
                // The name is not mangled because it has been declared as extern "C".
                IntPtr piCExportedInteger = (IntPtr)GetProcAddress(hDLL, "iCExportedInteger");
                // Convert the data at the address (as contained in piCExportedInteger) into an Int32.
                Int32 iCExportedInteger = (Int32)Marshal.PtrToStructure(piCExportedInteger, typeof(Int32));
                // Display the data on the console output.
                Console.WriteLine("iCExportedInteger : {0:D}.", iCExportedInteger);

                // Obtain the runtime address of the exported data with name "szExportedString".
                // The name is not mangled because it has been declared as it is in the .DEF file.
                IntPtr pszExportedString = (IntPtr)GetProcAddress(hDLL, "szExportedString");
                // Convert the data at the address (as contained in pszExportedString) into a managed string.
                string szExportedString = (string)Marshal.PtrToStringAnsi(pszExportedString);
                // Display the data on the console output.
                Console.WriteLine("szExportedString : {0:S}", szExportedString);

                // Obtain the runtime address of the exported data with name "dblExportedDouble".
                // The name is not mangled because it has been declared as it is in the .DEF file.
                IntPtr pdblExportedDouble = (IntPtr)GetProcAddress(hDLL, "dblExportedDouble");
                // Convert the data at the address (as contained in pdblExportedDouble) into a double.
                double dblExportedDouble = (double)Marshal.PtrToStructure(pdblExportedDouble, typeof(double));
                // Display the data on the console output.
                Console.WriteLine("dblExportedDouble : {0:F}.", (double)dblExportedDouble);

                // Obtain the runtime address of the exported data with name "GUID_EXPORTED".
                // The name is not mangled because it has been declared as it is in the .DEF file.
                IntPtr pGUID = (IntPtr)GetProcAddress(hDLL, "GUID_EXPORTED");
                // Convert the data at the address (as contained in pGUID) into a Guid.
                Guid guid_exported = (Guid)Marshal.PtrToStructure(pGUID, typeof(Guid));
                // Display the data on the console output.
                Console.WriteLine("guid_exported : {0:S}.", guid_exported.ToString());

                // Finally, free the library when it is no longer required.
                FreeLibrary(hDLL);
                hDLL = IntPtr.Zero;
            }
        }

        static void Main(string[] args)
        {
            TestLoadExportedData();
        }
    }
}

The following are significant points about the code above :

  • The three main system APIs used by the program are declared using the DllImportAttribute.
  • The main focus is on the TestLoadExportedData() function.
  • This function first loads TestDLL.dll using LoadLibrary().
  • Then if the loading was successful, the function searches for specific exported data using GetProcAddress().
  • The GetProcAddress() API is usually used to search for exported functions but it can also be used to search for exported data.
  • The return value of GetProcAddress() is a pointer to the exported data. It would still be a pointer (to a function) if it was used to search for a function. We hold each returned address in an IntPtr.
  • It is important to use the correct exported name of the data as they are listed in the DLL’s export table.
  • These are the names as listed by the Dependency Walker and DUMPBIN.EXE.
  • Once we have acquired the address of an exported data, we need to convert the data pointed to by the address into its managed counterpart.
  • The specific conversion function to use depends on the type of the data to be converted.
  • In our example, for integers, doubles and the GUID, we can use Marshal.PtrToStructure().
  • For C-style strings, we can use one of the Marshal.PtrToStringXXX() functions.
  • Finally, when we have converted all the exported data, we need to free the DLL using FreeLibrary().

4.2 The above code will produce the following console output :

iCPPExportedInteger : 100.
iCExportedInteger : 200.
szExportedString : Exported string.
dblExportedDouble : 0.12.
guid_exported : 727f4b7f-8753-4bf3-b6c7-136881a5eaaa.

5. In Conclusion.

5.1 I hope this introduction into accessing exported data from a DLL has been interesting to the reader.

5.2 Besides being able to read them, it is also possible to write values into global exported data.

5.3 This will be the subject of another write up that I intend to post.

 

Advertisements

About Lim Bio Liong

I've been in software development for nearly 20 years specializing in C , COM and C#. It's truly an exicting time we live in, with so much resources at our disposal to gain and share knowledge. I hope my blog will serve a small part in this global knowledge sharing network. For many years now I've been deeply involved with C development work. However since circa 2010, my current work has required me to use more and more on C# with a particular focus on COM interop. I've also written several articles for CodeProject. However, in recent years I've concentrated my time more on helping others in the MSDN forums. Please feel free to leave a comment whenever you have any constructive criticism over any of my blog posts.

Discussion

Trackbacks/Pingbacks

  1. Pingback: Writing to Data Exported From a DLL in Managed Code. « limbioliong - November 17, 2011

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: