Click here to Skip to main content
15,903,385 members
Articles / Programming Languages / C
Article

Using Unmanaged C++ Libraries (DLLs) in .NET Applications

Rate me:
Please Sign up or sign in to vote.
4.81/5 (42 votes)
2 Jun 2006CPOL11 min read 589.7K   8K   209   60
An article on how to use unmanaged C++ classes exported from pre-built libraries (DLLs) with no source code.

Contents

1. Introduction

This article has been revised. Check here for updates.

There are many reasons why you would want to reuse unmanaged C/C++ libraries; the most important one is perhaps that you want to use existing tools, utilities, and classes written in unmanaged C/C++. They could be third-party tools or in-house libraries. When choosing an approach to reusing unmanaged libraries, you normally have three options:

  1. IJW or It Just Works. This is one of the greatest features that .NET Framework has provided to developers. You just recompile the old code on the new .NET platform. No or little changes are necessary. Don't forget though; it works in the C++ language only.
  2. COM. The COM model works on both the unmanaged and managed environments. It's straightforward to perform a COM Invoke on .NET. But, if your unmanaged classes are not COM-ready, you probably won't rewrite all the old code to support COM.
  3. P/Invoke or Platform Invoke. This mechanism allows you to import a class as functions at the attribute level. Basically, you import class methods one by one as individual functions, as you do with Win32 APIs.

If your unmanaged C++ libraries are not COM-ready, you can choose between IJW and P/Invloke. Also, you may combine the two approaches in your importing practice. As IJW requires C++ source code, if you don't have the source code, P/Invoke probably is the only option available. Using Win32 API via [DllImport] attributes is a typical example of P/Invoke in .NET development.

This article will discuss how we can use unmanaged C++ classes exported from a DLL. No source code for the unmanaged C++ libraries are required to be present. In particular, I will demonstrate how to wrap up your unmanaged classes into managed ones so that any .NET application can use them directly. I will take a practical approach and omit theoretical discussions where possible. All the samples and source code provided in this article are simple and for tutorial purposes only. In order to use the source code included in the article, you should have Visual Studio 2005 and .NET Framework 2.0 installed. However, the wrapping technique remains the same on VS 2003 and .NET Framework 1.x. The unmanaged DLL has been compiled on Visual C++ 6.0, which is not required if you don't recompile the unmanaged source.

2. Sample Unmanaged C++ Library

Go to Top

The following segment is the definition of a base class "Vehicle" and its derived class "Car":

C++
// The following ifdef block is the standard way of creating macros which make exporting 
// from a DLL simpler. All files within this DLL are compiled with the CPPWIN32DLL_EXPORTS
// symbol defined on the command line. this symbol should not be defined on any project
// that uses this DLL. This way any other project whose source files include this file see 
// CPPWIN32DLL_API functions as being imported from a DLL, whereas this DLL sees symbols
// defined with this macro as being exported.

#ifdef CPPWIN32DLL_EXPORTS
#define CPPWIN32DLL_API __declspec(dllexport) 
#else 
#define CPPWIN32DLL_API __declspec(dllimport) 
#endif 

// This class is exported from the CppWin32Dll.dll
class CPPWIN32DLL_API Vehicle 
{
public:
    Vehicle(char* idx);
    // Define the virtual destructor

    virtual ~Vehicle();
    
    char* GetId() const;
    // Define a virtual method

    virtual void Move();
    
protected:
    char* id;
};

class CPPWIN32DLL_API Car : public Vehicle
{
public:
    ~Car();
    // Override this virtual method

    void Move();
};

By all means, the two classes are very simple. However, they bear two most important characteristics:

  1. The base class contains a virtual destructor.
  2. The derived class overrides a virtual method of the base class.

To demonstrate the invoke sequence, I've inserted a printf statement in each method. For your reference, here is the complete source of "CppWin32Dll.cpp":

C++
#include "stdafx.h"
#include "CppWin32Dll.h"

BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
        case DLL_PROCESS_ATTACH:
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
            break;
    }
    return TRUE;
};

// This is the constructor of a class that has been exported.
// see CppWin32Dll.h for the class definition

Vehicle::Vehicle(char* idx) : id(idx)
{ 
    printf("Called Vehicle constructor with ID: %s\n", idx);
};

Vehicle::~Vehicle() 
{ 
    printf("Called Vehicle destructor\n");
};    
char* Vehicle::GetId() const 
{ 
    printf("Called Vehicle::GetId()\n");
    return id;
};
void Vehicle::Move() 
{ 
    printf("Called Vehicle::Move()\n");
};

Car::~Car() 
{ 
    printf("Called Car destructor\n");
};
void Car::Move() 
{ 
    printf("Called Car::Move()\n");
};

I have built the two classes into a Win32 DLL called "CppWin32Dll.dll" on Visual C++ 6.0. All our importing work will be based on this DLL and the header, "CppWin32Dll.h". We are not going to use the unmanaged source hereafter.

As with all unmanaged DLLs, we cannot use "CppWin32Dll.dll" as an assembly/reference. Although P/Invoke allows us to import functions exported by the DLL, we cannot import classes. What we can do is import all the methods in a class and wrap them in a managed class, which then can be used by .NET applications written in any .NET compatible language, C++, C#, VB, or J#.

3. Retrieve Exported Information from the DLL

Go to Top

As the first step, we are going to import class methods from the DLL. As we don't have access to the source code, we use the Microsoft dumping tool "dumpbin.exe" to retrieve the decorated name for each function from the DLL. After executing "dumpbin /exports CppWin32Dll.dll", we get:

Sample screenshot

The ordinal segment contains all the names for all the functions. Although it lists all the functions from the DLL, you should determine which functions are accessible methods based on the class definitions in the header. Mapping of the mangled names to the class members is listed in the following table:

C++ Decorated Name

Class Member

Note

??0Vehicle@@QAE@ABV0@@Z

Default constructor

Added by compiler

??0Vehicle@@QAE@PAD@Z

Vehicle::Vehicle(char *)

??1Vehicle@@UAE@XZ

Vehicle::~Vehicle()

??4Vehicle@@QAEAAV0@ABV0@@Z

Class default structure

Added by compiler

??_7Vehicle@@6B@

Virtual table (VTB)

Added by compiler

?GetId@Vehicle@@QBEPADXZ

Vehicle::GetId()

?Move@Vehicle@@UAEXXZ

Vehicle::Move()

??0Car@@QAE@ABV0@@Z

Default constructor

Added by compiler

??1Car@@UAE@XZ

Car::~Car()

??4Car@@QAEAAV0@ABV0@@Z

Class default structure

Added by compiler

??_7Car@@6B@

Virtual table (VTB)

Added by compiler

?Move@Car@@UAEXXZ

Car::Move()

Be wary that the exact details of "name mangling" are compiler-dependent, and they may vary from one version to another. Interestingly, if you add/remove/change class members to the Win32 project, you will notice that the new DLL may have different "mangled names" for the constructor or other class members. This is because the "mangled name" contains all the information about the class member and its relationship with the rest of the class. Any changes to this relationship will be reflected in its "mangled name" in the new DLL.

Anyway, it appears the unmanaged DLLs built by VC++ 6.0 on NT-based platforms (NT/2000/XP) will work with .NET applications. At the time of this writing, it is difficult to verify whether unmanaged DLLs built by older compilers on older Windows will still work. This is more like a compatibility issue.

4. Perform Platform Invoke

Go to Top

I have imported four methods: the constructor, the destructor, GetId, and Move, and put them in another unmanaged class called "VehicleUnman":

MC++
/// Create a unmanaged wrapper structure as the placeholder for unmanaged class 
/// members as exported by the DLL. This structure/class is not intended to be
/// instantiated by .NET applications directly.

public struct VehicleUnman
{
    /// Define the virtual table for the wrapper

    typedef struct 
    {
        void (*dtor)(VehicleUnman*);
        void (*Move)(VehicleUnman*);    
    } __VTB;
public:
    char* id;
    static __VTB *vtb;    

    /// Perform all required imports. Use "ThisCall" calling convention to import 
    /// functions as class methods of this object (not "StdCall"). Note that we 
    /// pass this pointer to the imports. Use the "decorated name" retrieved from
    /// the DLL as the entry point.

    [DllImport("CppWin32Dll.dll", 
        EntryPoint="??0Vehicle@@QAE@PAD@Z", 
        CallingConvention=CallingConvention::ThisCall)]
    static void ctor(VehicleUnman*, char*);
    [DllImport("CppWin32Dll.dll", 
        EntryPoint="??1Vehicle@@UAE@XZ", 
        CallingConvention=CallingConvention::ThisCall)]
    static void dtor(VehicleUnman*);
    [DllImport("CppWin32Dll.dll", 
        EntryPoint="?GetId@Vehicle@@QBEPADXZ", 
        CallingConvention=CallingConvention::ThisCall)]
    static char* GetId(VehicleUnman*);
    [DllImport("CppWin32Dll.dll", 
        EntryPoint="?Move@Vehicle@@UAEXXZ", 
        CallingConvention=CallingConvention::ThisCall)]
    static void Move(VehicleUnman*);
        
    /// Delegates of imported virtual methods for the virtual table.
    /// This basically is hacking the limitation of function pointer (FP),
    /// as FP requires function address at compile time.

    static void Vdtor(VehicleUnman* w)
    {
        dtor(w);
    }
    static void VMove(VehicleUnman* w)
    {
        Move(w);
    }
    static void Ndtor(VehicleUnman* w)
    {
        ///Do nothing

    }
};


/// Create a unmanaged wrapper structure as the placeholder for unmanaged class 
/// members as exported by the DLL. This structure/class is not intended to be
/// instantiated by .NET applications directly.

public struct CarUnman
{
    /// Define the virtual table for the wrapper

    typedef struct 
    {
        void (*dtor)(CarUnman*);
        void (*Move)(CarUnman*);    
    } __VTB;
public:
    static __VTB *vtb;    

    /// Perform all required imports. Use "ThisCall" calling convention to import 
    /// functions as class methods of this object (not "StdCall"). Note that we 
    /// pass this pointer to the imports. Use the "decorated name" retrieved from
    /// the DLL as the entry point.

    [DllImport("CppWin32Dll.dll", 
        EntryPoint="??1Car@@UAE@XZ", 
        CallingConvention=CallingConvention::ThisCall)]
    static void dtor(CarUnman*);
    [DllImport("CppWin32Dll.dll", 
        EntryPoint="?Move@Car@@UAEXXZ", 
        CallingConvention=CallingConvention::ThisCall)]
    static void Move(CarUnman*);

    /// Delegates of imported virtual methods for the virtual table.
    /// This basically is hacking the limitation of function pointer (FP),
    /// as FP requires function address at compile time.

    static void Vdtor(CarUnman* w)
    {
        dtor(w);
    }
    static void VMove(CarUnman* w)
    {
        Move(w);
    }
};

Note the following:

  1. Import the exported public methods/members only.
  2. Don't import compiler-added members. They are mostly internals, and not all of them are accessible.
  3. Every imported function takes the current pointer as an input parameter, in addition to the original input parameter(s). The DLL uses this pointer to call the function properly via the decorated name "@Vehicle" or "@Car", which is how the C++ compiler handles classes internally.
  4. I added a virtual table or VTB manually to handle virtual methods, as an emulation of C++ virtual members internal handling. The VTB contains function pointers for all virtual methods.

As you may notice, I defined two extra methods: Vdtor and VMove, each to call its corresponding import. This actually is a hack/patch of function pointers in P/Invoke. As we know, a function pointer points to (the address of) a function. Here, it would point to an import, which doesn't have an address at compile time. It gets the address only through dynamical binding at run-time. The two delegates help to delay the binding between the function pointers and the actual functions.

Note that the source file should contain the initialization of the static VTB data:

MC++
/// Unmanaged wrapper static data initialization
VehicleUnman::__VTB *VehicleUnman::vtb = new VehicleUnman::__VTB;
CarUnman::__VTB *CarUnman::vtb = new CarUnman::__VTB;

5. Wrap all the Imports in Managed Classes

Go to Top

Now, we are ready to write a new managed C++ class, which will contain an object of each unmanaged class defined above. Here is the source:

MC++
/// Managed wrapper class which will actually be used by .NET applications.

public ref class VehicleWrap
{
public: 
    /// User-defined managed wrapper constructor. It will perform a few tasks:
    /// 1) Allocating memory for the unmanaged data
    /// 2) Assign the v-table
    /// 3) Marshall the parameters to and call the imported unmanaged class constructor

    VehicleWrap(String ^str)
    {
        tv = new VehicleUnman();
        VehicleUnman::vtb->dtor = VehicleUnman::Vdtor;
        VehicleUnman::vtb->Move = VehicleUnman::VMove;
            
        char* y = (char*)(void*)Marshal::StringToHGlobalAnsi(str);
        VehicleUnman::ctor(tv, y);
    }
    /// Let the v-table handle virtual destructor

    virtual ~VehicleWrap()
    {
        VehicleUnman::vtb->dtor(tv);
    }        
    /// Let the v-table handle method overriding

    String^ GetId()
    {
        char *str = VehicleUnman::GetId(tv);
        String ^s = gcnew String(str);
        return s;
    }
    virtual void Move()
    {
        VehicleUnman::vtb->Move(tv);
    }
private: 
    VehicleUnman *tv;
};

/// Managed wrapper class which will actually be used by .NET applications.
public ref class CarWrap : public VehicleWrap
{
public: 
    /// User-defined managed wrapper constructor. It will perform two tasks:
    /// 1) Allocating memory for the unmanaged data
    /// 2) Assign the v-table

    CarWrap(String ^str) : VehicleWrap(str)
    {
        tc = new CarUnman();
        CarUnman::vtb->dtor = CarUnman::Vdtor;
        CarUnman::vtb->Move = CarUnman::VMove;
    }
    /// Let the v-table handle virtual destructor

    ~CarWrap()
    {
        CarUnman::vtb->dtor(tc);
        /// After the DLL code handled virtual destructor, manually turn off
        /// the managed virtual destrctor capability.

        VehicleUnman::vtb->dtor = VehicleUnman::Ndtor;
    }    
    /// Let the v-table handle method overriding

    virtual void Move () override 
    {
        CarUnman::vtb->Move(tc);
    }

private:
    CarUnman *tc;
};

Several places in the source code are noticeable:

  1. Don't derive the managed "VehicleWrap" from the unmanaged "VehicleUnman". Unmanaged wrappers merely provide the storage for the original class members, including data and methods, whereas managed ones handle the class relationship. More importantly, you pass the unmanaged object to the DLL, not the managed one.
  2. Use managed data types in managed classes whenever possible, particularly as input/output parameters and return types. This is more than just a good practice, rather a necessity, because other .NET developers do not have to marshal unmanaged data types at the application level.
  3. Derive "CarWrap" from "VehicleWrap" to recover the original inheritance between the two unmanaged classes. This way, we don't have to handle the inheritance manually in the managed classes.
  4. Assign the do-nothing function to VehicleUnman::vtb->dtor in the ~Car() destructor. This is a hack to mitigate the conflict between the unmanaged DLL internals and the managed class inheritance. I'll leave the detailed discussion of this issue to the next section.

Now, we put all the classes in a DLL named "CppManagedDll.dll". "VehicleWrap" and "CarWrap" are two managed classes, which are ready to be used by .NET applications. In order to test the "VehicleWrap" and the "CarWrap" classes, I created a .NET C++ CLR console application project, with this source code:

MC++
// TestProgram.cpp : main project file.

#include "stdafx.h"

using namespace System;
using namespace CppManagedDll;

int main(array<System::String ^> ^args)
{
    /// Create an instance of Car and cast it differently to test polymorphism 

    CarWrap ^car1 = gcnew CarWrap("12345");

    String ^s = car1->GetId();

    Console::WriteLine(L"GetId() returned: {0:s}", s);

    car1->Move();

    /// Delete instances to test virtual destructor

    delete car1, s;

    return 0;
}

6. Inheritance, Polymorphism, and Virtual Destructor

Go to Top

As we saw earlier, I derived "CarWrap" from "VehicleWrap" to avoid the manual implementation of the original inheritance between the "Car" and "Vehicle" classes, with the assumption that the C++ DLL breaks down all the relationship between the derived classes. This turned out not to be true. The tests revealed that it only breaks the binding between the two Move() methods of "Vehicle" and "Car", but retains the virtual destructor binding. That is, whenever ~Car() is called from outside the DLL, ~Vehicle() gets called automatically. This has some adverse impact on our managed classes, because ~Vehicle() would be called twice, one by the managed class virtual destructor and the other by the original destructor inside the DLL. To test this, you can comment/uncomment the following line in ~CarWrap():

MC++
VehicleUnman::vtb->dtor = VehicleUnman::Ndtor;

This line allows the managed class to use its own binding, and meanwhile, to disable the unexpected binding in the DLL, which is achieved through the power of VTB and function pointer!

After we run "TestProgram.exe", we get the print-out as follows:

Called Vehicle constructor with ID: 12345
Called Vehicle::GetId()
GetId() returned: 12345
Called Car::Move()
Called Car destructor
Called Vehicle destructor

To verify the polymorphism, modify the second line in the main:

MC++
VehicleWrap ^car1 = gcnew CarWrap("12345");

You will get the same printout. If you change the line to:

MC++
VehicleWrap ^car1 = gcnew VehicleWrap ("12345");

You will get:

Called Vehicle constructor with ID: 12345
Called Vehicle::GetId()
GetId() returned: 12345
Called Vehicle::Move()
Called Vehicle destructor

As we discussed earlier, if you comment out the VTB assignment in ~Car(), the "Vehicle" destructor in the DLL would be called twice:

Called Vehicle constructor with ID: 12345
Called Vehicle::GetId()
GetId() returned: 12345
Called Car::Move()
Called Car destructor
Called Vehicle destructor
Called Vehicle destructor

Surprisingly, although calling the same destructor twice is logically incorrect, it hasn't caused any crash. How could this happen? It did because the importing didn't create any object in the DLL. We will discuss this in more details in the next section.

Now, everything seems to work smoothly. We are ready to extend to multiple inheritance, another important unmanaged C++ specification. Well, not quite. This extension is not feasible, not because we cannot mimic multiple inheritance, but because managed C++ has abandoned this complicated concept completely. In order to comply with the managed C++ standard, you should avoid legacy multiple inheritance in .NET applications.

7. Imported Resource Disposal

Go to Top

To fully understand why the two calls to the exported destructor in the DLL didn't cause any memory problems, let's first analyze where unmanaged resources are allocated:

  1. Created by your unmanaged wrapper. It's your responsibility to dispose all the resource allocated within the unmanaged wrapper.
  2. Created by the imports? No. When we import, we don't create any instance inside the DLL. Instead, the instance will be created within our managed assembly, with the unmanaged wrapper (structure) as the placeholder for all the imported functions and other class data. The exported functions are allocated by the DLL on the stack. No dynamic allocation is performed during importing, as you don't "new" any objects inside the DLL. Thus, no disposing is necessary for the importing itself.
  3. Created and disposed by the DLL internally. We assume that the DLL has taken care of this properly already; in other words, the DLL is bug-free. One thing to note though. If a DLL destructor contains code to dispose any other resources, multiple calls to it may cause problems to the DLL.

8. Concluding Remarks

Go to Top

This tutorial provides an alternative approach to reusing unmanaged C++ libraries, particularly when direct importing from unmanaged DLLs becomes necessary. I have demonstrated three steps to wrap unmanaged C++ DLLs for use in .NET applications:

  1. Retrieve class member data from the DLL.
  2. Import required class methods.
  3. Wrap up all the imports in a managed class.

The tutorial also shows that the implementation of the approach is not trivial, mainly because you must recover the original relationship between unmanaged classes, such as inheritance, virtual functions, and polymorphism. Managed C++ can help, but when there are conflicts, you have to simulate some C++ compiler internals. In working with C++ internals, you will find virtual table and function pointer helpful.

9. Revision History

Go to Top

  • 21 May 2006: First revision of the article and source code.
  • 28 May 2006: Revised with the following updates:
    • Added section: "7. Imported Resource Disposal".
    • Added comments to the source code.
    • Changed unmanaged wrapper from class to structure.

    Thanks to vmihalj for the inspiring question leading to this update.

  • 2 June 2006: Revised with the following additions:
    • Added multiple inheritance discussion to section 3.
    • Added "name mangling" discussion to section 6.

    Thanks to lsanil for the inspiring question leading to this update.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect GuestLogix Inc.
Canada Canada
Jun is an experienced software architect. He wrote his first computer code on the tape machine for a "super computer". The tape machine reads holes on the black pape tape as source code. When manually fixing code, you need a punch and tranparent tape. To delete code, you block holes or cut off a segment and glue two ends together. To change code, you block old holes and punch new holes. You already know how to add new code, don't you? Anyway, that was his programming story in early 1980's.

Jun completed university with the specialty in oceanography, and graduate study in meteorology. He obtained his Ph.D. in physics. Jun has worked in a number of different areas. Since mid-90's, he has been working as a software professional in both military & commercial industries, including Visual Defence, Atlantis Systems International and Array Systems Computing.

Currently, Jun is an architect at GuestLogix, the global leader in providing onboard retail solutions for airlines and other travel industries. He is also the founder of Intribute Dynamics, a consulting firm specialized in software development. He has a personal blog site, although he is hardly able to keep it up to date.

In his spare time, Jun loves classic music, table tennis, and NBA games. During the summer, he enjoyes camping out to the north and fishing on wild lakes.

Comments and Discussions

 
GeneralRe: Problem with calling the functions Pin
Jun Du17-Aug-06 13:16
Jun Du17-Aug-06 13:16 
GeneralRe: Problem with calling the functions Pin
mariakoryakina18-Aug-06 4:47
mariakoryakina18-Aug-06 4:47 
GeneralRe: Problem with calling the functions Pin
Jun Du18-Aug-06 5:38
Jun Du18-Aug-06 5:38 
GeneralRe: Problem with calling the functions Pin
mariakoryakina18-Aug-06 5:56
mariakoryakina18-Aug-06 5:56 
GeneralRe: Problem with calling the functions Pin
Jun Du18-Aug-06 6:40
Jun Du18-Aug-06 6:40 
GeneralRe: Problem with calling the functions Pin
mariakoryakina18-Aug-06 9:19
mariakoryakina18-Aug-06 9:19 
GeneralRe: Problem with calling the functions Pin
mariakoryakina18-Aug-06 11:28
mariakoryakina18-Aug-06 11:28 
Generalneed a little help Pin
A55imilate20-Jul-06 3:43
A55imilate20-Jul-06 3:43 
I need some help understanding this as I am not a c++ developer. I was trying to use this article to convert an unmanged C++ dll (for which I dont have the source) to a managed one so I could use it in my c# application.

When I run the example I get the following. I tried to register the dll (using regsvr32.exe, but that didnt find an entry point)

An unhandled exception of type 'System.DllNotFoundException' occurred in CppManagedDll.dll

Additional information: Unable to load DLL 'CppWin32Dll.dll': The specified module could not be found. (Exception from HRESULT: 0x8007007E)

Also I ran dumpbin.exe on my dll and it looks very different to the example:
File Type: DLL

Section contains the following exports for MDBEngine.dll

00000000 characteristics
4434319D time date stamp Thu Apr 06 00:07:41 2006
0.00 version
1 ordinal base
77 number of functions
77 number of names

ordinal hint RVA name

1 0 00005050 AcceptorCreate
2 1 00005280 AcceptorDeduct
3 2 00005100 AcceptorDestroy
4 3 000053B0 AcceptorDiagnosticStatus
5 4 000052E0 AcceptorDispense
6 5 00005220 AcceptorEnable
7 6 00005350 AcceptorFeatureEnable
8 7 00005250 AcceptorFunds
9 8 000051F0 AcceptorInfo
10 9 00005320 AcceptorPayout
11 A 000052B0 AcceptorRefund
12 B 000051C0 AcceptorReset
13 C 00005380 AcceptorTubeStatus
14 D 000053E0 BillCreate
15 E 00005490 BillDestroy
16 F 000055B0 BillEnable
17 10 000055F0 BillEscrow
18 11 00005650 BillFeatureEnable
19 12 00005580 BillInfo
20 13 00005550 BillReset
21 14 00005620 BillStatus
22 15 00005B20 DebugComment
23 16 00005A50 DebugCreate
24 17 00005A90 DebugDestroy
25 18 00005AC0 DebugGetSetup
26 19 00005AF0 DebugSetTrace
27 1A 000047E0 DownloadTarget
28 1B 00004880 EngineReset
29 1C 00004860 EngineVersion
30 1D 00005890 ExecutiveControl
31 1E 000057E0 ExecutiveCreate
32 1F 00005840 ExecutiveDestroy
33 20 00005920 ExecutiveGetDisplay
34 21 000058C0 ExecutivePrice
35 22 000058F0 ExecutiveVend
36 23 000048E0 GetDeviceCount
37 24 00004950 GetDeviceID
38 25 00005950 MasterAcceptorCreate
39 26 00005A20 MasterAcceptorDeposit
40 27 000059B0 MasterAcceptorDestroy
41 28 00005A00 MasterAcceptorReset
42 29 00004B00 MasterApproved
43 2A 00004AA0 MasterBeginSession
44 2B 00004990 MasterCreate
45 2C 00004B30 MasterDenied
46 2D 000049F0 MasterDestroy
47 2E 00004B90 MasterDisplay
48 2F 00004AD0 MasterEndSession
49 30 00004A70 MasterInfo
50 31 00004BC0 MasterReceiveFile
51 32 00004A40 MasterReset
52 33 00004B60 MasterRevalue
53 34 00004BC0 MasterTransmitFile
54 35 00004DE0 ReaderCancel
55 36 00004C00 ReaderCreate
56 37 00004CB0 ReaderDestroy
57 38 00004EA0 ReaderDisplay
58 39 00004DA0 ReaderEnable
59 3A 00004FA0 ReaderFeatureEnable
60 3B 00004E10 ReaderInfo
61 3C 00004FD0 ReaderReceiveFile
62 3D 00004D70 ReaderReset
63 3E 00004F60 ReaderRevalueLimit
64 3F 00004F30 ReaderRevalueRequest
65 40 00004E40 ReaderSession
66 41 00004E70 ReaderSessionComplete
67 42 00005010 ReaderTransmitFile
68 43 00004ED0 ReaderVendRequest
69 44 00004F00 ReaderVendStatus
70 45 000047A0 ResetTarget
71 46 00004970 SetDeviceID
72 47 00005680 VMCCreate
73 48 00005760 VMCCredit
74 49 00005730 VMCDebug
75 4A 000056E0 VMCDestroy
76 4B 000057B0 VMCExactChange
77 4C 00005790 VMCVend

Summary

6000 .data
6000 .rdata
4000 .reloc
14000 .rsrc
1A000 .text



GeneralRe: need a little help Pin
Jun Du17-Aug-06 8:18
Jun Du17-Aug-06 8:18 
GeneralFantastic Pin
AlexEvans11-Jul-06 19:02
AlexEvans11-Jul-06 19:02 
GeneralRe: Fantastic [modified] Pin
Jun Du17-Aug-06 8:57
Jun Du17-Aug-06 8:57 
GeneralNice Article Pin
another_keesh16-Jun-06 7:48
another_keesh16-Jun-06 7:48 
GeneralRe: Nice Article Pin
Jun Du16-Jun-06 8:13
Jun Du16-Jun-06 8:13 
Generalquestion on different processor architecture Pin
lsanil1-Jun-06 21:07
lsanil1-Jun-06 21:07 
GeneralRe: question on different processor architecture Pin
Jun Du2-Jun-06 4:19
Jun Du2-Jun-06 4:19 
GeneralGood 5/5 Pin
Farrukh_530-May-06 22:41
Farrukh_530-May-06 22:41 
GeneralConverting this to a .NET 2003 Pin
AlexEvans29-May-06 17:27
AlexEvans29-May-06 17:27 
GeneralRe: Converting this to a .NET 2003 Pin
Jun Du30-May-06 3:17
Jun Du30-May-06 3:17 
GeneralRe: Converting this to a .NET 2003 Pin
giridharn30-Aug-06 21:32
giridharn30-Aug-06 21:32 
GeneralLink error Pin
AlexEvans29-May-06 12:36
AlexEvans29-May-06 12:36 
GeneralRe: Link error Pin
Jun Du29-May-06 13:08
Jun Du29-May-06 13:08 
GeneralRe: Link error Pin
AlexEvans29-May-06 17:25
AlexEvans29-May-06 17:25 
GeneralI have the C++ headers Pin
maihem28-May-06 15:11
maihem28-May-06 15:11 
GeneralRe: I have the C++ headers Pin
Jun Du28-May-06 16:16
Jun Du28-May-06 16:16 
GeneralRe: I have the C++ headers Pin
maihem29-May-06 2:07
maihem29-May-06 2:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.