Search code examples
c#pointerswinapivisual-studio-2013registry

RegSetValueEx returns 998 (ERROR_NOACCESS) if called without ref


I'm trying to write a DWORD to registry using c#. Using p/invoke because of registry reddirection.

I've searched for this issue and finally could get it working but i don't understand.

[DllImport("advapi32.dll", SetLastError = true)]
        static extern uint RegSetValueEx(
             IntPtr hKey,
             [MarshalAs(UnmanagedType.LPStr)]
     string lpValueName,
             int Reserved,
             RegistryValueKind dwType,
             ref IntPtr lpData,
             int cbData);

 int checkreturn = RegOpenKeyEx(HKeyLocalMachine, @"SOFTWARE\Test", 0, (int) RegistrySecurity.KEY_WOW64_64KEY | (int) RegistrySecurity.KEY_SET_VALUE, ref keyHandle);

            const int dataStored = 0;
            IntPtr p = new IntPtr(dataStored);
            int size = Marshal.SizeOf(dataStored);
            uint checkreturn2 = RegSetValueEx(keyHandle, "valueName", 0, RegistryValueKind.DWord, ref p, size);

This works if i put out or ref on lpData parameter, if i don't it returns error 998 (ERROR_NOACCESS), why is that? The same thing happens if i change the IntPtr to int, and pass the actual value, but this time i get an first exception AccessViolation on my code.

the winapi declaration for that it's *lpData, which i assume is what passing a IntPtr is.

  _In_       const BYTE    *lpData,  

Solution

  • The api requires a pointer to the data plus the size of the data. You can't pass an int, or a char, or a bool. You need to pass a pointer to the data. If you pass something else, the API will interpret it as a pointer to the data, and random results will happen.

    With P/Invoke, a ref to something is translated to a pointer to that something.

    Now, you can

    [DllImport("advapi32.dll", SetLastError = true)]
    static extern uint RegSetValueEx(
         IntPtr hKey,
         [MarshalAs(UnmanagedType.LPStr)]
         string lpValueName,
         int Reserved,
         RegistryValueKind dwType,
         ref uint lpData,
         int cbData);
    

    and then in cbData pass sizeof(uint) and this will work, because a ref for P/Invoke is a ref.

    Only thing, I would suggest removing the

    [MarshalAs(UnmanagedType.LPStr)]
    

    because without it the P/Invoke will use the Unicode version of the method, that is more correct.