包含在本机结构中的PInvoke方法

本文关键字:PInvoke 方法 结构 本机 包含 | 更新日期: 2023-09-27 18:25:13

我正在尝试在C#中重新创建一些C++示例API使用代码
看起来我可能需要创建一个C++/CLI包装器,以使API函数可供托管世界访问,但如果可能的话,我希望避免这种情况。API库本身只有一个导出函数:data_api_func_tab

这就是C++API的使用情况:

//
// .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 );  
    ...
}

由于我不能在本地库上使用反射(更不用说它会很慢),PInvoke似乎是进入的唯一途径

我在C#中重新创建了适当的结构,并尝试了以下PInvoke签名,

[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);

但它们会产生异常

找不到名为"的入口点。。。无论我尝试什么…"在NATIVEAPI.dll 中

我已经四处寻找了实现这一点的方法,但似乎只找到了非托管到C++/CLI的解决方案。我试图做的事情是否可能(考虑到结构中包含的单个方法没有导出)?

包含在本机结构中的PInvoke方法

这是一个相当遥远的API。库不导出函数,而是导出包含函数指针的结构的地址。注意C++代码如何调用GetProcAddress,然后将结果解释为指向结构的指针,而不是更常见的指向函数的指针。

根据GetProcAddress的文档,我强调:

从指定的动态链接库(DLL)检索导出函数或变量的地址。

不能使用DllImport访问此库的导出,因为DllImport用于函数而非变量。

以下是您必须做的:

  1. 使用LoadLibrary加载DLL并获得作为IntPtr的模块句柄
  2. 调用GetProcAddress获取结构体的地址
  3. 使用Marshal.PtrToStructure获取包含函数指针的托管结构
  4. 对结构的每个成员使用Marshal.GetDelegateForFunctionPointer以获得一个委托,然后可以调用该委托
  5. 完成库后,通过调用FreeLibrary将其卸载。如果您愿意等待进程终止并且系统自动卸载,则可以省略此步骤

假设您可以获得LoadLibrary和朋友的p/invoke签名,代码如下所示:

// 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();

整理者有能力为您创建委托,这是合理的。在这种情况下,结构将是:

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

在这种情况下,Marshal.GetDelegateForFunctionPointer步骤显然是不必要的,因为编组员已经代表您执行了该步骤。

我没有测试过这些代码,只是把它输入到浏览器中。毫无疑问,这里有一些问题,但这段代码更多的是作为一个指南,而不是作为您可以直接使用的代码。