【问题标题】:PInvoke method contained in a native structure本机结构中包含的 PInvoke 方法
【发布时间】:2016-03-06 00:14:43
【问题描述】:

我正在尝试在 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 中找不到名为“...whatever I try...”的入口点

我已经四处寻找一种方法来做到这一点,但似乎只找到不受 C++/CLI 管理的解决方案。我正在尝试做的事情是否可能(考虑到结构中包含的各个方法未导出)?

【问题讨论】:

  • 用C#完整定义struct,只声明要导入的native函数。
  • @Felix 没有要导入的函数
  • data_api_func_tab() 声明需要 CallingConvention.Cdecl 和 ExactSpelling = true 因为函数名没有被修饰。返回类型为 IntPtr,使用 Marshal.PtrToStructure 恢复结构。将结构体的 app_init 成员声明为委托,需要 [UnmanagedFunctionPointer] 声明为 Cdecl。
  • @Hans No. data_api_func_tab 是数据导出而不是函数导出。
  • api_init 是一个函数。

标签: c# api struct pinvoke


【解决方案1】:

这是一个相当遥远的 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 步骤显然是不必要的,编组器已经代表您执行了它。

我没有测试任何代码,只是将它输入到浏览器中。毫无疑问,有一些皱纹,但此代码更多地用作指南,而不是您可以直接使用的代码。

【讨论】:

  • 在我尝试 GetDelegateForFunctionPointer() 之前它一直在工作。我错过了一些东西。请注意定义参数和返回类型的 C++ 代码:extern "C" typedef short (* MAPIINIT)(short); 这部分不在我的结构中(只有返回类型和函数名)。尝试指定结构中包含的函数时,我得到“最佳重载方法匹配...包含一些无效参数”,因为它不是指针。
  • 我猜不出你的代码是什么。关于MAPIINITncm_api_init,它们只出现在问题的两行中,并没有被做某事的C++代码引用。
  • 更新了我的问题。我遗漏了 EXTERN / extern 定义语句。我不确定它们是否相关,但似乎它们是相关的。
  • 不,我认为它们不相关。它们与对GetDelegateForFunctionPointer 的调用无关。我回答了我面前的问题。我想你明白这里发生了什么。你精通两种语言。我建议你继续努力。
  • 感谢您的帮助。我接受了,因为你的回答几乎把我带到了那里。如果我无法弄清楚,我可能会提出一个新问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多