来自c#LPCTSTR的编组不再起作用

时间:2013-11-23 18:25:41

标签: c# pinvoke marshalling

我在我的一个项目中使用P / Invoke来调用avifil32.dll方法,到目前为止一切正常。今天,在几个月之后回到那个项目并在此期间切换到Windows 8,我发现没有任何东西可以工作了。我对avifil32方法的大多数调用都会导致AccessViolationException(“尝试读取或写入受保护的内存......”)。

导致此问题的一个函数是AVIFileOpen,之前我以这种方式调用(如pinvoke.net here中所述):

[DllImport("avifil32.dll", PreserveSig=true)]
static extern int AVIFileOpen(out IntPtr ppfile, string szFile, uint mode, int pclsidHandler);

在网上进行了一些搜索后,我发现了this有用的帖子,因此我将通话更改为(注意 AVIFileOpenW ):

[DllImport("avifil32.dll", PreserveSig=true, CharSet = CharSet.Auto)]
public static extern int AVIFileOpenW(ref int ppfile, [MarshalAs(UnmanagedType.LPWStr)] String szFile,  int uMode, int pclsidHandler);

现在它有效。但是我有其他问题,例如我在调用AVIFileCreateStream

时遇到了同样的例外

那么我做错了什么?而且更有趣的是,这可能是导致现在不再起作用的原因了?我承认我不知道在此期间会发生什么。最大的变化是从Windows 7转移到Windows 8,但这可以解释这个问题吗?

修改


在Hans Passant消化之后,我已经纠正了P / Invoke电话,现在我已经不再是AccessViolationexception了。然而,在我工作之前,它仍然是一个谜。实际上现在(Win8)和之前(Win7)我都有64位操作系统。 但是,项目设置中还有另一个细节可能有助于解释。这是一个从我的主项目调用的库项目。我的主要项目针对x64(目标x86不是一个选项),而库目标是AnyCPU。我做了另一个试验,并再次使用这些设置和我AVIFileOpen的旧版本调用该程序在Win7 x64机器下工作,但不在Win8 x64机器下工作。这可能与JIT编译器对AnyCPU的不同“管理”有关,还是必须有一些我缺少的其他设置?

现在该程序在x86和x64中都可以使用。下面是我正在使用的代码的摘录:

AVIFileInit();
IntPtr aviFile = IntPtr.Zero;
IntPtr aviStream = IntPtr.Zero;
string tmp = Path.GetTempFileName();
if (AVIFileOpen(out aviFile, tmp.Substring(0, tmp.Length - 4) + ".avi", OF_WRITE | OF_CREATE, IntPtr.Zero) == 0)
{
    AVISTREAMINFO strhdr = new AVISTREAMINFO();
    strhdr.fccType = mmioStringToFOURCC("vids", 0);
    strhdr.fccHandler = mmioStringToFOURCC("CVID", 0);
    strhdr.dwScale = 1;
    strhdr.dwRate = 25;
    strhdr.dwSuggestedBufferSize = 102400;
    strhdr.dwQuality = -1;
    strhdr.rcFrame.bottom = 320;
    strhdr.rcFrame.right = 320;
    strhdr.szName = "";
    if (AVIFileCreateStream(aviFile, out aviStream, ref strhdr) == 0)
    {
        AVICOMPRESSOPTIONS_CLASS options = new AVICOMPRESSOPTIONS_CLASS();
        options.fccType = (uint)streamtypeVIDEO;
        options.lpParms = IntPtr.Zero;
        options.lpFormat = IntPtr.Zero;
        bool ok = AVISaveOptions(this.Handle, ICMF_CHOOSE_KEYFRAME | ICMF_CHOOSE_DATARATE, 1, ref aviStream, ref options);
    }
}

public static readonly int streamtypeVIDEO = mmioFOURCC('v', 'i', 'd', 's');
public const UInt32 ICMF_CHOOSE_KEYFRAME = 0x0001;
public const UInt32 ICMF_CHOOSE_DATARATE = 0x0002;
public const UInt32 ICMF_CHOOSE_PREVIEW = 0x0004;
public const int OF_WRITE = 1;
public const int OF_READWRITE = 2;
public const int OF_CREATE = 4096;

[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
    public UInt32 left;
    public UInt32 top;
    public UInt32 right;
    public UInt32 bottom;
} 

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct AVISTREAMINFO
{
    public Int32 fccType;
    public Int32 fccHandler;
    public Int32 dwFlags;
    public Int32 dwCaps;
    public Int16 wPriority;
    public Int16 wLanguage;
    public Int32 dwScale;
    public Int32 dwRate;
    public Int32 dwStart;
    public Int32 dwLength;
    public Int32 dwInitialFrames;
    public Int32 dwSuggestedBufferSize;
    public Int32 dwQuality;
    public Int32 dwSampleSize;
    public RECT rcFrame;
    public Int32 dwEditCount;
    public Int32 dwFormatChangeCount;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] public String szName;
}

[StructLayout(LayoutKind.Sequential)]
public class AVICOMPRESSOPTIONS_CLASS
{
    public UInt32 fccType;
    public UInt32 fccHandler;
    public UInt32 dwKeyFrameEvery;
    public UInt32 dwQuality;
    public UInt32 dwBytesPerSecond;
    public UInt32 dwFlags;
    public IntPtr lpFormat;
    public UInt32 cbFormat;
    public IntPtr lpParms;
    public UInt32 cbParms;
    public UInt32 dwInterleaveEvery;
}

[DllImport("avifil32.dll")]
public static extern void AVIFileInit();

[DllImport("winmm.dll", CharSet = CharSet.Auto)]
public static extern int mmioStringToFOURCC([MarshalAs(UnmanagedType.LPWStr)] String sz, int uFlags);

[DllImport("avifil32.dll", PreserveSig = true, CharSet = CharSet.Auto)]
public static extern int AVIFileOpen(out IntPtr ppfile, String szFile, int uMode, IntPtr pclsidHandler);

[DllImport("avifil32.dll", CharSet = CharSet.Auto)]
public static extern int AVIFileCreateStream(IntPtr pfile, out IntPtr ppavi, ref AVISTREAMINFO ptr_streaminfo);

[DllImport("avifil32.dll")]
public static extern bool AVISaveOptions(IntPtr hwnd, UInt32 uiFlags, Int32 nStreams, ref IntPtr ppavi, ref AVICOMPRESSOPTIONS_CLASS plpOptions);

1 个答案:

答案 0 :(得分:4)

  

最大的变化是从Windows 7转移到Windows 8

这可以解释这些问题,您可能已经获得了64位版本的Windows 8.您使用的两个声明都是错误的,并且在64位模式下运行程序时会很难诊断出来。相关项目设置是项目+属性,构建选项卡,平台目标设置。最好在这里使用“x86”(在VS2012及更高版本中选择优先级32位),这将限制您从这些不良声明中遭受的痛苦。

第一个参数是PAVIFILE,“指向接收接口指针的缓冲区的指针”。最后一个参数是CLSID,“指向类标识符的指针”。指针在32位模式下为4字节,在64位模式下为8字节。相应的C#声明必须使用 ref out 关键字,或使用IntPtr兼容。

所以第一个参数是“指向指针的指针”,使用out IntPtr是正确的。就像你一样。

最后一个参数几乎从未使用过,你传递一个空指针。在这种情况下,您要声明它IntPtr并在拨打电话时传递IntPtr.Zero。您最初传递0,4个字节,而函数预期8个字节设置为0.足以生成错误的指针值并获取AccessViolationException。

您使用的替换声明特别令人讨厌,您只传递了足够的空间来存储 int ,4个字节,但该函数在64位模式下写入8个字节。这将破坏堆栈或GC堆,非常讨厌。随后的调用,如AviFileCreateStream确实很可能失败,因为接口指针值不可能不受影响。

我修复了pinvoke.net声明。