Reputation: 3897
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)?
Upvotes: 1
Views: 309
Reputation: 612854
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:
LoadLibrary
to load the DLL and obtain a module handle as an IntPtr
. GetProcAddress
to obtain the address of the struct. Marshal.PtrToStructure
to get a managed struct containing the function pointers. Marshal.GetDelegateForFunctionPointer
for each member of the struct to obtain a delegate which you can then call. 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.
Upvotes: 1