Search code examples
c#apistructpinvoke

PInvoke method contained in a native structure


I am trying to re-create some C++ sample API usage code in C#.
It's looking like I might need to create a C++/CLI wrapper to make the API functions accessible to the managed world, but I'd like to avoid that if possible. The API library itself only has a single exported function: data_api_func_tab.

This is what the C++ API usage looks like:

//
// .h file 
// 
typedef struct _DATA_API_FUNC_TAB {
    short   (*api_init)();
    // ... lots of other methods ...

} DATA_API_FUNC_TAB

extern  "C"  typedef short  (* MAPIINIT)(short);
// ... lots of other methods ...

#undef  EXTERN
#ifdef  _MAIN
#define EXTERN
#else
#define EXTERN  extern
#endif

EXTERN  MAPIINIT ncm_api_init;
// ... lots of other methods ...

public:
    UCHAR SomeVariable;
    void SomeMethod( arguments );

//
// .cpp file
//  
/// Constructor
CWidgetDataApi::CWidgetDataApi()
{
    SomeVariable = 0;

    m_hInstHdl = ::LoadLibrary(_T(".\\NATIVEAPI.dll"));
    if( NULL != m_hInstHdl )
    {
        DATA_API_FUNC_TAB* p_data_api_func_tab =
            (DATA_API_FUNC_TAB*) ::GetProcAddress(m_hInstHdl, "data_api_func_tab");
        SomeVariable = 1;

        if( p_data_api_func_tab == NULL )
        {
            ::FreeLibrary(m_hInstHdl);
            m_hInstHdl = NULL;
            SomeVariable = 0;
        }
        else
        {
            api_init = (MAPINIT) p_data_api_func_tab->api_init;
            // ... lots of other methods ...

            short Ret = api_init(index);
        }
    }
}

/// Method
void CWidgetDataApi::SomeMethod( arguments )
{
   // ... Makes use of the various other methods ...
}

//
// Usage in another class
//
DataAPI = new CWidgetDataApi;

if( DataAPI->SomeVariable == 1 )
{ 
    DataAPI->SomeMethod( arguments );  
    ...
}

Since I can't use reflection on the native library (not to mention it would be slow), PInvoke seems to be the only way in.

I have recreated the appropriate structures in C# and tried the following PInvoke signatures,

[DllImport("NATIVEAPI.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern struct data_api_func_tab { };

[DllImport("NATIVEAPI.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern short api_init([In, Out] ref _DATA_API_FUNC_TAB data_api_func_tab);

but they produce the exception

Unable to find an entry point named '... whatever I try...' in NATIVEAPI.dll

I've searched around for a way to do this but only seem to find unmanaged to C++/CLI solutions. Is what I am attempting to do even possible (given that the individual methods contained in the structure are not exported)?


Solution

  • This is a pretty far out API. Instead of exporting functions, the library exports the address of a struct containing function pointers. Notice how the C++ code call GetProcAddress and then interprets the result as a pointer to a struct and not as is more common, as a pointer to a function.

    From the documentation of GetProcAddress, with my emphasis:

    Retrieves the address of an exported function or variable from the specified dynamic-link library (DLL).

    You cannot use DllImport to access this library's export because DllImport is for functions rather than variables.

    Here's what you must do:

    1. Use LoadLibrary to load the DLL and obtain a module handle as an IntPtr.
    2. Call GetProcAddress to obtain the address of the struct.
    3. Use Marshal.PtrToStructure to get a managed struct containing the function pointers.
    4. Use Marshal.GetDelegateForFunctionPointer for each member of the struct to obtain a delegate which you can then call.
    5. When you are done with the library unload it by calling FreeLibrary. You can omit this step if you are happy to wait until the process terminates and the system unloads automatically.

    Assuming you can obtain p/invoke signatures for LoadLibrary and friends, the code looks like this:

    // declare the structure of function pointers
    
    struct DATA_API_FUNC_TAB
    {
        IntPtr api_init;
        // more function pointers here
    }
    
    ....
    
    // load the DLL, and obtain the structure of function pointers
    
    IntPtr lib = LoadLibrary(@"full\path\to\dll");
    if (lib == IntPtr.Zero)
        throw new Win32Exception();
    IntPtr funcTabPtr = GetProcAddress(lib, "data_api_func_tab");
    if (funcTabPtr == IntPtr.Zero)
        throw new Win32Exception();
    DATA_API_FUNC_TAB funcTab = (DATA_API_FUNC_TAB)Marshal.PtrToStructure(funcTabPtr, typeof(DATA_API_FUNC_TAB));
    
    ....
    
    // declare the delegate types, note the calling convention
    
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate short api_init_delegate();
    
    ....
    
    // obtain a delegate instance
    
    api_init_delegate api_init = (api_init_delegate)Marshal.GetDelegateForFunctionPointer(funcTab.api_init, typeof(api_init_delegate));
    
    ....
    
    // finally we can call the function
    
    short retval = api_init();
    

    It's plausible that the marshaller is capable of creating the delegate for you. In which case the struct would be:

    struct DATA_API_FUNC_TAB
    {
        api_init_delegate api_init;
        // more delegates here
    }
    

    In which case the Marshal.GetDelegateForFunctionPointer step would obviously not be necessary, the marshaller having already performed it on your behalf.

    I've not tested any of this code, just typed it into the browser. No doubt there are a few wrinkles, but this code is intended more as a guide than as code that you could use directly.