【C#】调用DLL问题汇总

 

一、尝试读取或写入受保护的内存,这通常指示其他内存已损坏

 

可能原因:

 

1、传入的数组长度开的太小了;

 

2、传入参数没有加ref。

 

 

二、C#调用DLL时提示:未找到指定模块

 

1、检查DLL中是否有该函数,并有调用接口

可以使用dumpbin.exe查看封装好的dll里都有什么函数,参见:

https://blog.csdn.net/wangzhichunnihao/article/details/112570828

https://blog.csdn.net/weixin_42953003/article/details/124383170

 

2、DLL中可能有调用其他的DLL,但调用时未查找到

比如一个集合了操作示波器、万用表等设备函数接口的DLL,使用时往往需要先在电脑上安装示波器、万用表的使用环境(驱动等)。

如果电脑上安装了万用表的环境,没有安装示波器的环境,即使调用该DLL时只是调用了操作万用表的函数,也是会提示找不到模块的。

 

3、C:\Windows\System32或C:\Windows\SysWOW64中缺少DLL

像是MSVCP60D.DLL、MSVCRTD.DLL这种。一般这些DLL安装了VC6.0应该就可以。

 

4、在将用VS2008编写的WinForm项目转为VS2013项目后,调用dll报该错

尝试将用VC6 C++编写的dll项目转为VS2013项目,同时在WinForm项目内写调用接口时,加上

[DllImport("xxx.dll", SetLastError = true,CallingConvention=CallingConvention.Cdecl)]

 

参考:https://blog.csdn.net/jing_cs/article/details/78559508

 

 

三、程序在32位操作系统上运行正常,在64位操作系统上运行提示“试图加载格式不正确”

 

点击项目属性,把目标平台Any CPU 设置为X86。

 

 

四、在C++函数中返回false,在C#里调用该DLL却返回true

C++函数:

        bool test()
        {
            if (xx)
            {
                return TRUE;
            }
            return FALSE;
        }

 

WinForm调用:

            while (test() == true)
            {

            }

在C++中返回FALSE,在C#里调用却是true,导致死循环。

可能是bool类型的问题。返回类型改为int返回就正常了:

        int test()
        {
            if (xx)
            {
                return 1;
            }
            return 0;
        }

调用DLL时出现的错误总结:https://blog.csdn.net/qq_22654855/article/details/113456374

 

五、引用子文件夹下的DLL

修改App.config:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>

  <runtime>
    <!--xmlns是必需的特性。指定程序集绑定所需的 XML 命名空间。-->
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <publisherPolicy apply="yes"/>
      <!--指定运行时是否使用发布者策略-->
      <!--指定加载程序集时公共语言运行时搜索的子目录, 其中privatePath是相对于*.exe.config文件的相对路径,多个文件夹以分号分隔。-->
      <probing privatePath="DLL;DLL\OtherDLL"/>
    </assemblyBinding>
  </runtime>
</configuration>

注意:如果这些Dll在修改路径前就已经引用到 解决方案资源管理器--引用 里,那转移后,如果编译报错的话,要重新把这些Dll添加到引用。并且要把这些Dll“复制本地”属性设为False,否则再次生成的时候会在转移前的文件夹下再次出现。

但对于自己编写的一些动态库好像没办法用这个方法引用,还是要放在Bin文件夹下才能调用到。

 

参考:https://blog.csdn.net/weixin_39994714/article/details/115201485

 

六、调用DLL数据类型转换

1、char、char*类型转换

在C#中char是16位的;C++中的char是8位的。
(1)unsigned char类型可以用byte类型代替。
(2)char*可以用string传入。

2、结构体转换

https://www.cnblogs.com/HansZimmer/p/12485234.html

3、byte[]和byte*的复制和转换

https://www.cnblogs.com/castor-xu/p/14719493.html

4、DLL转换类型对照

C++:unsigned char * ---- C#:ref byte 或 [MarshalAs(UnmanagedType.LPArray)]

C++:unsigned char & ---- C#:ref bytebyte[] 或 [MarshalAs(UnmanagedType.LPArray)] Intptr

C++:HANDLE(void *) ---- C#:System.IntPtr

C++:Byte(unsigned char)---- C#:System.Byte

C++:SHORT(short) ---- C#:System.Int16

C++:WORD(unsigned short) ---- C#:System.UInt16

C++:LONG(long) ---- C#:System.Int32

C++:ULONG(unsigned long)---- C#:System.UInt32

C++:DWORD(unsigned long) ---- C#:System.UInt32

C++:结构体 **变量名 ---- C#:out 变量名 //C#中提前申明一个结构体实例化后的变量名

C++:结构体 &变量名 ---- C#:ref 结构体 变量名

参考:https://blog.csdn.net/weixin_28823431/article/details/117127717

 

5、用C#实现联合体

情景:有一个接收函数,在C++中用法是传入一个变量的地址,变量类型是一个联合体。

C++函数:int Test(unsigned char* data)

使用如下:

AFRAME * pAFrame; //定义
pAFrame = (AFRAME *)malloc(DATA_LENGTH);//DATA_LENGTH = 16;
memset(pAFrame,0,DATA_LENGTH);
int rstatus = Test((unsigned char *)pAFrame);//传入地址取得数据
//做其他处理
if(pAFrame[0].Fra1.Info.bit.d1 == 0)
{
} ...

其中,联合体定义如下:

typedef union{
    struct{
        BYTE d1:4;
        BYTE d2:2;
        BYTE d3:1;
        BYTE d4:1;
    }bit;
    BYTE byte;
}INFO;

typedef struct _Frame1 {
     INFO Info;
     WORD data1;
     BYTE data2[8];
     BYTE data3[2];
     BYTE data4[3];
}FRAME1;

typedef struct _Frame2 {
     INFO Info;
     DWORD data1;
     BYTE data2[8];
     BYTE data3[3];
}FRAME2;

typedef union{
    FRAME1    Fra1;
    FRAME2    Fra2;
}AFRAME;

 

(1)第一种方法:char * 对应C#中的Intptr,有时候C#中也可以用byte[]代替。

封装:
[DllImport("test.dll", SetLastError = true)]
public static extern int Test(byte[] data); //不需要加ref

调用:
byte[] data = new byte[16];
int res = Test(data);
//执行后需要根据联合体的定义对data进行数据解析

这是最简单的解决方式。

 

(2)如果不想解析字节数组,那么就需要在C#实现联合体,然后像C++里一样实现。

在C#实现联合体:

    [StructLayout(LayoutKind.Explicit)]
    struct INFO
    {
        [FieldOffset(0)]
        public byte value;
        [FieldOffset(1)]
        byte _d1;
        [FieldOffset(1)]
        byte _d2;
        [FieldOffset(1)]
        byte _d3;
        [FieldOffset(1)]
        byte _d4;

        public byte d1
        {
            get
            {
                _d1 = (byte)(this.value & 0x0F); //4bit
                return _d1;
            }
            set
            {
                this.value = (byte)(((value & 0x01) << 7) | ((value & 0x01) << 6) | ((value & 0x03) << 

4) | (value & 0x0F));
            }
        }
       
        public byte d2
        {
            get
            {
                _d2 = (byte)((this.value >> 4) & 0x03); //2bit
                return _d2;
            }
            set
            {
                this.value = (byte)(((value & 0x01) << 7) | ((value & 0x01) << 6) | ((value & 0x03) << 

4) | (value & 0x0F));
            }
        }
        
        public byte d3
        {
            get
            {
                _d3 = (byte)((this.value >> 6) & 0x01); //1bit
                return _d3;
            }
            set
            {
                this.value = (byte)(((value & 0x01) << 7) | ((value & 0x01) << 6) | ((value & 0x03) << 

4) | (value & 0x0F));
            }
        }
        
        public byte d4
        {
            get
            {
                _d4 = (byte)((this.value >> 7) & 0x1); //1bit
                return _d4;
            }
            set
            {
                this.value = (byte)(((value & 0x01) << 7) | ((value & 0x01) << 6) | ((value & 0x03) << 

4) | (value & 0x0F));
            }
        }
    }

    struct FRAME1
    {
        public INFO Info;    
        public short data1;        
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
        public byte[] data2;        
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
        public byte[] data3;    
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
        public byte[] data4;        
    }

    struct FRAME2
    {
        public INFO Info;
        public uint data1;        
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
        public byte[] data2;      
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
        public byte[] data3;
    }

    [StructLayout(LayoutKind.Explicit)]
    struct AFRAME
    {
        [FieldOffset(0)]
        public FRAME1 Fra1;
        [FieldOffset(0)]
        public FRAME2 Fra2;
    };

 

封装:

[DllImport("test.dll", CharSet = CharSet.Ansi)]
static extern UInt32 Test(IntPtr pReceive);

 

调用:

IntPtr pt = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(AFRAME)));
res = Test(pt);
for (UInt32 i = 0; i < 1; i++)
{
    AFRAME pAFrame = (AFRAME)Marshal.PtrToStructure((IntPtr)((UInt32)pt + i * Marshal.SizeOf(typeof

(AFRAME))), typeof(AFRAME));
    //做其他处理
    if(pAFrame[0].Fra1.Info.bit.d1 == 0)
    {
      
     }
     ...
}

 

第二种方法我没有验证到最后,因为这个地方会报错:“因为它在0偏移位置处包含一个对象字段”。

[StructLayout(LayoutKind.Explicit)]
    struct AFRAME
    {
        [FieldOffset(0)]
        public FRAME1 Fra1;
        [FieldOffset(0)]
        public FRAME2 Fra2;
    };

没找到解决办法...

主要是学到了在C#中实现位域的方法,那个是验证了能用的。其他的先Mark一下。

 

学习网址:

1)IntPtr数据类型相关操作

byte[]转IntPtr、IntPtr转byte、IntPtr转Stream:

https://blog.csdn.net/qq_41452267/article/details/109484161/

byte[]、struct、intptr等的相互转换:网址

 

2)调用带结构体指针的C Dll的方法

https://www.cnblogs.com/ye-ming/p/8004314.html

 

3)C#使用结构体实现共用体

https://www.cnblogs.com/willick/p/14274914.html

举例:

    // FieldOffset 表示偏移的位置(以字节为单位)
    // sizeof(int) = 4, sizeof(byte) = 1
    [FieldOffset(0)] public int Address;  // Address是占用4个字节的整数;
    [FieldOffset(0)] public byte Byte1;   //在0字节处对齐,其实就是指向同一个内存
    [FieldOffset(1)] public byte Byte2;
   [FieldOffset(2)] public byte Byte3;
   [FieldOffset(3)] public byte Byte4;

//当使用Address进行访问时,则直接使用四个字节
//而访问Byte1、Byte2、Byte3、Byte4则分别使用1个字节
//Byte1、Byte2、Byte3、Byte4共同组成了Address

 

附:一个实例

 

6、C#实现位域

C++中的位域成员有2种用途。一种是存放位标志的,还有一种是存放数值的。

(1)存放位标志

应该跟位域枚举是一个东西。主要为了方便位运算。在C#中通过enum实现。

(网上搜索在C#中实现位域,很多答案说用enum,如果是要实现上面那种存放数值的位域,用enum是不能实现的)

C/C++位域知识小结:

https://blog.csdn.net/dragon101788/article/details/54315703

https://www.cnblogs.com/x_wukong/p/5743369.html


枚举类型与位域枚举Enum:

https://www.cnblogs.com/springsnow/p/9428501.html#_label2_3

 

使用C++ struct位域的方法:

https://blog.csdn.net/sailor32731958/article/details/4844064

 

(2)实现C++中的数值型位域

https://blog.csdn.net/tiger_zhao/article/details/45075897(我参考的)

https://blog.csdn.net/XinChiMaker/article/details/104924707

 

5、C语言共用体(C语言union用法)详解

http://c.biancheng.net/view/2035.html

 

6、联合体(union)的使用方法及其本质

https://blog.csdn.net/huqinweI987/article/details/23597091

 

posted @ 2021-11-15 15:25  不溯流光  阅读(1921)  评论(0编辑  收藏  举报