现在的位置: 首页 > 综合 > 正文

c#程序调用c++编写dll需要注意问题

2013年10月17日 ⁄ 综合 ⁄ 共 4829字 ⁄ 字号 评论关闭

     现在在写c#调用c++dll的例子,dll中某一个函数需要一个结构体地址作为参数传递。

     但是在传递结构体的时候,程序一直返回错误,估计原因在c#写的结构体和c++中的结构体之间有些不一致。

    下面以例子说明-----c#程序在调用c++dll的时候需要注意问题。

(1) c++和c#中对应的数据结构大小一致

    简单的c++dll程序如下:

// mydll.cpp : Defines the entry point for the DLL application.
//

#include "stdafx.h"

#define DLLEXPORT extern "C" __declspec(dllexport)
BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
					 )
{
    return TRUE;
}

typedef struct _wfsversion 
{
	WORD	wVersion;
	WORD	wLowVersion;
	WORD	wHighVersion;
	CHAR	szDescription[257];
        CHAR	szSystemStatus[257]; 
}VERSION, *LPVERSION;


DLLEXPORT int WINAPI testVersion(DWORD dwVersionRequested, LPVERSION lpVersion)
{
	lpVersion->wHighVersion = 0x20;
	strcpy(lpVersion->szDescription, "test struct copy");
	return 0;
}

c++dll中结构体定义为:

typedef struct _wfsversion 
{
	WORD	wVersion;
	WORD	wLowVersion;
	WORD	wHighVersion;
	CHAR	szDescription[257];
        CHAR	szSystemStatus[257]; 
}VERSION, *LPVERSION;

可以用c++关键字sizeof计算出此数据结构的字节大小:sizeof(VERSION)=520。

这520个字节的组成:3*(sizeof(WORD))+2*257*(sizeof(CHAR)) = 3*2 +2*257*1 = 520。可见c++中WORD占2个字节,CHAR占1个字节。

c#中结构体定义:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct WFSVERSION
    {
        public short wVersion;
        public short wLowVersion;
        public short wHighVersion;
        [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 257)]
        public string szDescription;
        [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 257)]
        public string szSystemStatus;
    }

这里特别说明的是,c++中结构体所占字节数一定要和c#相应结构体所占字节数相等。

c#结构体中的short(Int16)<-------->c++中的WORD(unsigned short),都是占2个字节。

c#中调用c++dll,使用代码如下:

[DllImport("mydll.dll", CharSet = CharSet.Ansi)]
public static extern int testVersion(int dwVersion, ref WFSVERSION outInfo);
[DllImport("mydll.dll",CharSet = CharSet.Ansi)]
public static extern int testPoint(IntPtr outHandle);

这里特别说明的是,一定要确保c#引用函数的参数类型和c++函数中的参数类型一致。在测试工程中,由于一个参数在c#应该为int类型写成short,导致找了一天不能调用成功!

(2)c#向c++dll中函数传递数据结构及对象指针

  <1>使用c# ref或者out

ref关键字使参数按照引用传递,如上文中的testVersion函数中的第二个参数传值。当使用ref关键字修饰的参数被函数(方法)调用,在函数(方法)中对参数的任何修改都反映在该参数中,这类似于c++中的引用或者指针传参。如果要使用ref参数,则必须显示使用ref关键字。

       out关键字同样也会通过引用传递,此关键字也必须显示调用。当希望方法返回多个值时,可以声明使用out 参数,加有out关键字的参数可以将便利用作返回类型。

ref和out的不同之处:

  • 使用ref型参数时,传入的参数必须先被初始化;out则必须在方法中对其初始化。
  • out适合需要返回多个返回值的地方,而ref则需要被调用的方法修改调用者的引用的地方。
通俗的说:ref在函数中可进可出,out的只能出。
言归正传,当函数参数需要引用时,在c#中使用ref传递是没错的。
c++dll中函数:
    DLLEXPORT int WINAPI testVersion(DWORD dwVersionRequested, LPVERSION lpVersion)

c#调用:

    [DllImport("mydll.dll", CharSet = CharSet.Ansi)]
    public static extern int testVersion(int dwVersion, ref WFSVERSION outInfo);
<2> 使用IntPtr结构
c#中,IntPtr结构用于表示指针或者句柄的平台特定类型。
在调用的API函数中,一定有类似窗口句柄的参数,可以IntPtr声明。
      
      例子如下,c++dll函数:
DLLEXPORT int WINAPI testPoint(LPVERSION &lpHandle)
{
	strcpy(lpHandle->szDescription,"test copy again");
	lpHandle->wVersion = 0x30;
	return 0;
}

   c#中可以采用IntPtr调用:

   
[DllImport("mydll.dll",CharSet = CharSet.Ansi)]
public static extern int testPoint(IntPtr outHandle);

 调用函数:

 private void button1_Click(object sender, EventArgs e)
        {
            IntPtr pnt = Marshal.AllocHGlobal(Marshal.SizeOf(WFSVERSION));
            try
            {
                int ret = testVersion(0x30303, ref tempVersion); 
                int ret1 = testPoint(pnt);
                IntPtr ptr = Marshal.ReadIntPtr(pnt,0);//,Marshal.SizeOf(IntPtr));
                WFSVERSION version = (WFSVERSION)Marshal.PtrToStructure(ptr, typeof(WFSVERSION));
            }
            finally
            {
                Marshal.FreeHGlobal(pnt);
            }
        }

(3)c#调用c++dll中函数传递指针的指针

      目前我知道可以使用两种方法实现(这个传递指针的指针的参数不是结构体)。

      <1> 使用Byte[] 传递

       c++中函数声明:     

HRESULT extern WINAPI WFSCreateAppHandle ( LPHAPP lphApp);

HRESULT extern WINAPI WFSAsyncOpen ( LPSTR lpszLogicalName, HAPP hApp, LPSTR lpszAppID, DWORD dwTraceLevel, DWORD dwTimeOut, LPHSERVICE lphService, HWND hWnd, DWORD dwSrvcVersionsRequired, LPWFSVERSION lpSrvcVersion, LPWFSVERSION lpSPIVersion, LPREQUESTID lpRequestID);

      这里关注HAPP这个类型,在c++中为void *类型。那么LPHAPP类型为void **类型。

     在我的测试程序中,c#引用:

[DllImport("msxfs.dll", CharSet = CharSet.Ansi)]
public static extern int WFSCreateAppHandle([Out] Byte[] b);
[DllImport("msxfs.dll", CharSet = CharSet.Ansi)]
public static extern int WFSAsyncOpen(string sLogicalName, int hApp, string sAppID, int lTraceLevel, int lTimeOut, ref int iService, IntPtr hWnd,
                                      int iSrvVersionRequired, ref WFSVERSION srvcVersion, ref WFSVERSION SpiVersion, ref int iRequestID);

     程序测试函数:

//byte类型表示
byte[] b = new byte[10];
int ret1 = WFSCreateAppHandle(b);
int b1 = b[0];
int ret2 = WFSAsyncOpen("IDC30", b1, "test", m_dwTraceLevel, 2000, ref m_service, this.Handle, 0x00000303, ref tempVersion, ref version, ref m_requset);

    <2> 使用IntPtr实现

    c#程序调用:

IntPtr pnt = IntPtr.Zero;
            
            try
            {
                pnt = Marshal.AllocHGlobal(Marshal.SizeOf(pnt));
                IntPtr[] newArray = new IntPtr[10]; 
                int ret = WFSStartUp(0x30303, ref tempVersion);

                int ret1 = WFSCreateAppHandle(pnt);
                IntPtr ptr = Marshal.ReadIntPtr(pnt, 0);
                WFSVERSION version = new WFSVERSION();
                int ret2 = WFSAsyncOpen("IDC30", ptr, "test", m_dwTraceLevel, 2000, ref m_service, this.Handle, 0x00000303, ref tempVersion, ref version, ref m_requset);
            }
            finally
            {
                Marshal.FreeHGlobal(pnt);
            }

     最后,谈一下结构体(指针、指针的指针)采用IntPtr实现调用。

例如c++dll中有结构体指针或者结构体指针的指针函数调用,如int XXX(STRUCT **struct);

        c#可以 [DllImport("xxx.dll",ChaSet=CharSet.Ansi)] int XXX(IntPtr struct);

       c#中函数可以类似下边调用:

       

for(int   i   =   0;   i   <   plStreamCount;   i++) 
{ 
   IntPtr ptr   =   Marshal.ReadIntPtr(struct,   i); 
   testsize[i]   =   (STREAM_STATE_ITEM)Marshal.PtrToStructure(ptr,typeof(STRUCT ));     
} 

抱歉!评论已关闭.