Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 2개 있습니다.)
(시리즈 글이 8개 있습니다.)
.NET Framework: 1140. C# - ffmpeg(FFmpeg.AutoGen)를 이용해 MP3 오디오 파일 인코딩/디코딩하는 예제
; https://www.sysnet.pe.kr/2/0/12939

.NET Framework: 1144. C# - ffmpeg(FFmpeg.AutoGen) AVFormatContext를 이용해 ffprobe처럼 정보 출력
; https://www.sysnet.pe.kr/2/0/12948

.NET Framework: 1145. C# - ffmpeg(FFmpeg.AutoGen) - Codec 정보 열람 및 사용 준비
; https://www.sysnet.pe.kr/2/0/12949

.NET Framework: 1148.  C# - ffmpeg(FFmpeg.AutoGen) - decoding 과정
; https://www.sysnet.pe.kr/2/0/12956

.NET Framework: 1149. C# - ffmpeg(FFmpeg.AutoGen) - 비디오 프레임 디코딩
; https://www.sysnet.pe.kr/2/0/12958

.NET Framework: 1155. C# - ffmpeg(FFmpeg.AutoGen): Bitmap으로부터 yuv420p + rawvideo 형식의 파일로 쓰기
; https://www.sysnet.pe.kr/2/0/12966

.NET Framework: 1156. C# - ffmpeg(FFmpeg.AutoGen): Bitmap으로부터 h264 형식의 파일로 쓰기
; https://www.sysnet.pe.kr/2/0/12970

.NET Framework: 1160. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 qsv 디코딩
; https://www.sysnet.pe.kr/2/0/12977




C# - ffmpeg(FFmpeg.AutoGen) AVFormatContext를 이용해 ffprobe처럼 정보 출력

ffmpeg에서 하나의 동영상 정보를 취급하는 단위가 AVFormatContext입니다. 이것을 얻으려면 우선 avformat_open_input을 호출하면 됩니다.

string filePath = @"D:\media_sample\output2.mp4";

AVFormatContext* av_context = null;
int ret = ffmpeg.avformat_open_input(&av_context, filePath, null, null);

avformat_open_input에 av_context 변수를 null로 넣으면 할당해서 반환합니다. 혹은, 미리 avformat_alloc_context 함수를 이용해 할당해도 됩니다.

AVFormatContext* av_context = ffmpeg.avformat_alloc_context();
int ret = ffmpeg.avformat_open_input(&av_context, filePath, null, null);

일단, 이렇게 열고나면 AVFormatContext에 있는 몇몇 정보를 조회할 수 있습니다. 지난 글에서 메타데이터를 조회하는 예제를 올렸는데,

C# - ffmpeg(FFmpeg.AutoGen)를 이용해 멀티미디어 파일의 메타데이터를 보여주는 예제(metadata.c)
; https://www.sysnet.pe.kr/2/0/12936

그 코드에서는 avformat_open_input + avformat_find_stream_info 함수를 호출했었죠? 위의 테스트를 해보니 굳이 avformat_find_stream_info 함수는 호출하지 않아도 조회가 되었습니다. (어쨌든, 이렇게라도 정리를 해서 접근하니 뭔가 손에 잡히는 것 같은 기분입니다. ^^)

참고로, 이렇게 AVFormatContext 정보를 avformat_open_input으로 얻어도 해당 구조체의 모든 내용이 채워지는 것은 아닙니다. 가령, bitrate 등의 정보는 avformat_find_stream_info 함수를 추가 호출해야 합니다.

ffmpeg.avformat_find_stream_info(av_context, null);

마지막으로 AVFormatContext의 사용을 마쳤으면 avformat_close_input을 이용해 자원을 해제할 수 있습니다. 그리고 이를 종합하면 다음과 같은 식으로 예제 코드를 정리할 수 있습니다.

string filePath = @"D:\media_sample\output2.mp4";

int ret = ffmpeg.avformat_open_input(&av_context, filePath, null, null);
if (ret != 0)
{
    return;
}

// ...[avformat_open_input으로 얻을 수 있는 정보 조회]...

ffmpeg.avformat_find_stream_info(av_context, null);

// ...[avformat_find_stream_info로 얻을 수 있는 정보 조회]...

ffmpeg.avformat_close_input(&av_context);

그나저나, AVFormatContext에는 정말 많은 정보들이 있는데요,

    //
    // Summary:
    //     Format I/O context. New fields can be added to the end with minor version bumps.
    //     Removal, reordering and changes to existing fields require a major version bump.
    //     sizeof(AVFormatContext) must not be used outside libav*, use avformat_alloc_context()
    //     to create an AVFormatContext.
    public struct AVFormatContext
    {
        //
        // Summary:
        //     A class for logging and avoptions. Set by avformat_alloc_context(). Exports (de)muxer
        //     private options if they exist.
        public unsafe AVClass* av_class;

        //
        // Summary:
        //     The input container format.
        public unsafe AVInputFormat* iformat;

        //
        // Summary:
        //     The output container format.
        public unsafe AVOutputFormat* oformat;

        //
        // Summary:
        //     Format private data. This is an AVOptions-enabled struct if and only if iformat/oformat.priv_class
        //     is not NULL.
        public unsafe void* priv_data;

        //
        // Summary:
        //     I/O context.
        public unsafe AVIOContext* pb;

        //
        // Summary:
        //     Flags signalling stream properties. A combination of AVFMTCTX_*. Set by libavformat.
        public int ctx_flags;

        //
        // Summary:
        //     Number of elements in AVFormatContext.streams.
        public uint nb_streams;

        //
        // Summary:
        //     A list of all streams in the file. New streams are created with avformat_new_stream().
        public unsafe AVStream** streams;

        //
        // Summary:
        //     input or output filename
        [Obsolete("Use url instead.")]
        public byte_array1024 filename;

        //
        // Summary:
        //     input or output URL. Unlike the old filename field, this field has no length
        //     restriction.
        public unsafe byte* url;

        //
        // Summary:
        //     Position of the first frame of the component, in AV_TIME_BASE fractional seconds.
        //     NEVER set this value directly: It is deduced from the AVStream values.
        public long start_time;

        //
        // Summary:
        //     Duration of the stream, in AV_TIME_BASE fractional seconds. Only set this value
        //     if you know none of the individual stream durations and also do not set any of
        //     them. This is deduced from the AVStream values if not set.
        public long duration;

        //
        // Summary:
        //     Total stream bitrate in bit/s, 0 if not available. Never set it directly if the
        //     file_size and the duration are known as FFmpeg can compute it automatically.
        public long bit_rate;

        public uint packet_size;

        public int max_delay;

        //
        // Summary:
        //     Flags modifying the (de)muxer behaviour. A combination of AVFMT_FLAG_*. Set by
        //     the user before avformat_open_input() / avformat_write_header().
        public int flags;

        //
        // Summary:
        //     Maximum size of the data read from input for determining the input container
        //     format. Demuxing only, set by the caller before avformat_open_input().
        public long probesize;

        //
        // Summary:
        //     Maximum duration (in AV_TIME_BASE units) of the data read from input in avformat_find_stream_info().
        //     Demuxing only, set by the caller before avformat_find_stream_info(). Can be set
        //     to 0 to let avformat choose using a heuristic.
        public long max_analyze_duration;

        public unsafe byte* key;

        public int keylen;

        public uint nb_programs;

        public unsafe AVProgram** programs;

        //
        // Summary:
        //     Forced video codec_id. Demuxing: Set by user.
        public AVCodecID video_codec_id;

        //
        // Summary:
        //     Forced audio codec_id. Demuxing: Set by user.
        public AVCodecID audio_codec_id;

        //
        // Summary:
        //     Forced subtitle codec_id. Demuxing: Set by user.
        public AVCodecID subtitle_codec_id;

        //
        // Summary:
        //     Maximum amount of memory in bytes to use for the index of each stream. If the
        //     index exceeds this size, entries will be discarded as needed to maintain a smaller
        //     size. This can lead to slower or less accurate seeking (depends on demuxer).
        //     Demuxers for which a full in-memory index is mandatory will ignore this. - muxing:
        //     unused - demuxing: set by user
        public uint max_index_size;

        //
        // Summary:
        //     Maximum amount of memory in bytes to use for buffering frames obtained from realtime
        //     capture devices.
        public uint max_picture_buffer;

        //
        // Summary:
        //     Number of chapters in AVChapter array. When muxing, chapters are normally written
        //     in the file header, so nb_chapters should normally be initialized before write_header
        //     is called. Some muxers (e.g. mov and mkv) can also write chapters in the trailer.
        //     To write chapters in the trailer, nb_chapters must be zero when write_header
        //     is called and non-zero when write_trailer is called. - muxing: set by user -
        //     demuxing: set by libavformat
        public uint nb_chapters;

        public unsafe AVChapter** chapters;

        //
        // Summary:
        //     Metadata that applies to the whole file.
        public unsafe AVDictionary* metadata;

        //
        // Summary:
        //     Start time of the stream in real world time, in microseconds since the Unix epoch
        //     (00:00 1st January 1970). That is, pts=0 in the stream was captured at this real
        //     world time. - muxing: Set by the caller before avformat_write_header(). If set
        //     to either 0 or AV_NOPTS_VALUE, then the current wall-time will be used. - demuxing:
        //     Set by libavformat. AV_NOPTS_VALUE if unknown. Note that the value may become
        //     known after some number of frames have been received.
        public long start_time_realtime;

        //
        // Summary:
        //     The number of frames used for determining the framerate in avformat_find_stream_info().
        //     Demuxing only, set by the caller before avformat_find_stream_info().
        public int fps_probe_size;

        //
        // Summary:
        //     Error recognition; higher values will detect more errors but may misdetect some
        //     more or less valid parts as errors. Demuxing only, set by the caller before avformat_open_input().
        public int error_recognition;

        //
        // Summary:
        //     Custom interrupt callbacks for the I/O layer.
        public AVIOInterruptCB interrupt_callback;

        //
        // Summary:
        //     Flags to enable debugging.
        public int debug;

        //
        // Summary:
        //     Maximum buffering duration for interleaving.
        public long max_interleave_delta;

        //
        // Summary:
        //     Allow non-standard and experimental extension
        public int strict_std_compliance;

        //
        // Summary:
        //     Flags indicating events happening on the file, a combination of AVFMT_EVENT_FLAG_*.
        public int event_flags;

        //
        // Summary:
        //     Maximum number of packets to read while waiting for the first timestamp. Decoding
        //     only.
        public int max_ts_probe;

        //
        // Summary:
        //     Avoid negative timestamps during muxing. Any value of the AVFMT_AVOID_NEG_TS_*
        //     constants. Note, this only works when using av_interleaved_write_frame. (interleave_packet_per_dts
        //     is in use) - muxing: Set by user - demuxing: unused
        public int avoid_negative_ts;

        //
        // Summary:
        //     Transport stream id. This will be moved into demuxer private options. Thus no
        //     API/ABI compatibility
        public int ts_id;

        //
        // Summary:
        //     Audio preload in microseconds. Note, not all formats support this and unpredictable
        //     things may happen if it is used when not supported. - encoding: Set by user -
        //     decoding: unused
        public int audio_preload;

        //
        // Summary:
        //     Max chunk time in microseconds. Note, not all formats support this and unpredictable
        //     things may happen if it is used when not supported. - encoding: Set by user -
        //     decoding: unused
        public int max_chunk_duration;

        //
        // Summary:
        //     Max chunk size in bytes Note, not all formats support this and unpredictable
        //     things may happen if it is used when not supported. - encoding: Set by user -
        //     decoding: unused
        public int max_chunk_size;

        //
        // Summary:
        //     forces the use of wallclock timestamps as pts/dts of packets This has undefined
        //     results in the presence of B frames. - encoding: unused - decoding: Set by user
        public int use_wallclock_as_timestamps;

        //
        // Summary:
        //     avio flags, used to force AVIO_FLAG_DIRECT. - encoding: unused - decoding: Set
        //     by user
        public int avio_flags;

        //
        // Summary:
        //     The duration field can be estimated through various ways, and this field can
        //     be used to know how the duration was estimated. - encoding: unused - decoding:
        //     Read by user
        public AVDurationEstimationMethod duration_estimation_method;

        //
        // Summary:
        //     Skip initial bytes when opening stream - encoding: unused - decoding: Set by
        //     user
        public long skip_initial_bytes;

        //
        // Summary:
        //     Correct single timestamp overflows - encoding: unused - decoding: Set by user
        public uint correct_ts_overflow;

        //
        // Summary:
        //     Force seeking to any (also non key) frames. - encoding: unused - decoding: Set
        //     by user
        public int seek2any;

        //
        // Summary:
        //     Flush the I/O context after each packet. - encoding: Set by user - decoding:
        //     unused
        public int flush_packets;

        //
        // Summary:
        //     format probing score. The maximal score is AVPROBE_SCORE_MAX, its set when the
        //     demuxer probes the format. - encoding: unused - decoding: set by avformat, read
        //     by user
        public int probe_score;

        //
        // Summary:
        //     number of bytes to read maximally to identify format. - encoding: unused - decoding:
        //     set by user
        public int format_probesize;

        //
        // Summary:
        //     ',' separated list of allowed decoders. If NULL then all are allowed - encoding:
        //     unused - decoding: set by user
        public unsafe byte* codec_whitelist;

        //
        // Summary:
        //     ',' separated list of allowed demuxers. If NULL then all are allowed - encoding:
        //     unused - decoding: set by user
        public unsafe byte* format_whitelist;

        //
        // Summary:
        //     An opaque field for libavformat internal usage. Must not be accessed in any way
        //     by callers.
        public unsafe AVFormatInternal* @internal;

        //
        // Summary:
        //     IO repositioned flag. This is set by avformat when the underlaying IO context
        //     read pointer is repositioned, for example when doing byte based seeking. Demuxers
        //     can use the flag to detect such changes.
        public int io_repositioned;

        //
        // Summary:
        //     Forced video codec. This allows forcing a specific decoder, even when there are
        //     multiple with the same codec_id. Demuxing: Set by user
        public unsafe AVCodec* video_codec;

        //
        // Summary:
        //     Forced audio codec. This allows forcing a specific decoder, even when there are
        //     multiple with the same codec_id. Demuxing: Set by user
        public unsafe AVCodec* audio_codec;

        //
        // Summary:
        //     Forced subtitle codec. This allows forcing a specific decoder, even when there
        //     are multiple with the same codec_id. Demuxing: Set by user
        public unsafe AVCodec* subtitle_codec;

        //
        // Summary:
        //     Forced data codec. This allows forcing a specific decoder, even when there are
        //     multiple with the same codec_id. Demuxing: Set by user
        public unsafe AVCodec* data_codec;

        //
        // Summary:
        //     Number of bytes to be written as padding in a metadata header. Demuxing: Unused.
        //     Muxing: Set by user via av_format_set_metadata_header_padding.
        public int metadata_header_padding;

        //
        // Summary:
        //     User data. This is a place for some private data of the user.
        public unsafe void* opaque;

        //
        // Summary:
        //     Callback used by devices to communicate with application.
        public AVFormatContext_control_message_cb_func control_message_cb;

        //
        // Summary:
        //     Output timestamp offset, in microseconds. Muxing: set by user
        public long output_ts_offset;

        //
        // Summary:
        //     dump format separator. can be ", " or " " or anything else - muxing: Set by user.
        //     - demuxing: Set by user.
        public unsafe byte* dump_separator;

        //
        // Summary:
        //     Forced Data codec_id. Demuxing: Set by user.
        public AVCodecID data_codec_id;

        //
        // Summary:
        //     Called to open further IO contexts when needed for demuxing.
        [Obsolete("Use io_open and io_close.")]
        public AVFormatContext_open_cb_func open_cb;

        //
        // Summary:
        //     ',' separated list of allowed protocols. - encoding: unused - decoding: set by
        //     user
        public unsafe byte* protocol_whitelist;

        //
        // Summary:
        //     A callback for opening new IO streams.
        public AVFormatContext_io_open_func io_open;

        //
        // Summary:
        //     A callback for closing the streams opened with AVFormatContext.io_open().
        public AVFormatContext_io_close_func io_close;

        //
        // Summary:
        //     ',' separated list of disallowed protocols. - encoding: unused - decoding: set
        //     by user
        public unsafe byte* protocol_blacklist;

        //
        // Summary:
        //     The maximum number of streams. - encoding: unused - decoding: set by user
        public int max_streams;

        //
        // Summary:
        //     Skip duration calcuation in estimate_timings_from_pts. - encoding: unused - decoding:
        //     set by user
        public int skip_estimate_duration_from_pts;

        //
        // Summary:
        //     Maximum number of packets that can be probed - encoding: unused - decoding: set
        //     by user
        public int max_probe_packets;
    }
}

저도 아직 초보라 많은 것들을 알 수 없고, 겨우 다음과 같은 식으로 조회를 해봤습니다.

AVFormatContext* av_context = ffmpeg.avformat_alloc_context(); // null;

string filePath = @"D:\media_sample\output2.mp4";

int ret = ffmpeg.avformat_open_input(&av_context, filePath, null, null);
if (ret != 0)
{
    return;
}

Console.WriteLine("[metadata]");

WriteMetadata(av_context->metadata);

Console.WriteLine();
Console.WriteLine("[context-info]");
Console.WriteLine($"duration: {(decimal)av_context->duration / ffmpeg.AV_TIME_BASE}");
Console.WriteLine($"# of streams: {av_context->nb_streams}");
Console.WriteLine($"url: {Marshal.PtrToStringAnsi(new IntPtr(av_context->url))}");

ffmpeg.avformat_find_stream_info(av_context, null);
Console.WriteLine($"start time: {av_context->start_time}");
Console.WriteLine($"bitrate: {(decimal)av_context->bit_rate / 1000} kb/s");

for (int i = 0; i < av_context->nb_streams; i++)
{
    Console.WriteLine();
    AVStream* stream = av_context->streams[i];
    AVCodecParameters* codecpar = stream->codecpar;
    AVCodecContext* codec = stream->codec;
    Console.WriteLine($"Stream #0:{stream->index} {codecpar->codec_type} {codecpar->codec_id} (0x{codecpar->codec_tag.ToString("x")})");

    if (codecpar->codec_type == AVMediaType.AVMEDIA_TYPE_VIDEO)
    {
        Console.Write($"{codecpar->width}x{codecpar->height}, {stream->codec->pix_fmt}, {(AVPixelFormat)codecpar->format}, {codecpar->color_primaries}, {((float)codec->framerate.num / codec->framerate.den):.00} fps");
        Console.Write($", {stream->codec->pkt_timebase.den / stream->codec->pkt_timebase.num / 1000}k tbn");

        float tbc = (float)stream->codec->time_base.den / stream->codec->time_base.num;
        if (tbc > 1000)
        {
            Console.Write($", {tbc / 1000}k tbc");
        }
        else
        {
            Console.Write($", {tbc:.00} tbc");
        }
        Console.WriteLine();
    }
    else
    {
        Console.WriteLine($"{codecpar->sample_rate} Hz, {codecpar->channels} Channel(s), {codecpar->bit_rate / 1000} kb/s, {(AVSampleFormat)codecpar->format}");
    }

    WriteMetadata(stream->metadata);
}

위의 코드를 지난 글에서 설명한 4개의 스트림을 갖는 파일을 입력으로 실행하면 다음과 같은 출력 정보를 얻을 수 있습니다.

[metadata]
major_brand = isom
minor_version = 512
compatible_brands = isomiso2avc1mp41
encoder = Lavf58.45.100

[context-info]
duration: 187.954
# of streams: 4
url: D:\media_sample\output2.mp4
start time: 0
bitrate: 3553.945 kb/s

Stream #0:0 AVMEDIA_TYPE_VIDEO AV_CODEC_ID_H264 (0x31637661)
1920x1080, AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P, AVCOL_PRI_BT709, 29.97 fps, 16k tbn, 59.94 tbc
language = und
handler_name = ISO Media file produced by Google Inc.
vendor_id = [0][0][0][0]

Stream #0:1 AVMEDIA_TYPE_AUDIO AV_CODEC_ID_AAC (0x6134706d)
48000 Hz, 2 Channel(s), 128 kb/s, AV_SAMPLE_FMT_FLTP
language = eng
handler_name = SoundHandler
vendor_id = [0][0][0][0]

Stream #0:2 AVMEDIA_TYPE_AUDIO AV_CODEC_ID_AAC (0x6134706d)
44100 Hz, 2 Channel(s), 128 kb/s, AV_SAMPLE_FMT_FLTP
language = eng
handler_name = ISO Media file produced by Google Inc.
vendor_id = [0][0][0][0]

Stream #0:3 AVMEDIA_TYPE_VIDEO AV_CODEC_ID_VP9 (0x39307076)
640x360, AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P, AVCOL_PRI_BT709, 29.97 fps, 16k tbn, 16k tbc
language = eng
handler_name = VideoHandler
vendor_id = [0][0][0][0]

이 정보를 ffprobe로 알아낸 것과 비교해 볼까요? ^^

D:\media_sample> ffprobe output2.mp4
...[생략]...
  libpostproc    55.  9.100 / 55.  9.100
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'output2.mp4':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    encoder         : Lavf58.45.100
  Duration: 00:03:07.95, start: 0.000000, bitrate: 3553 kb/s
  Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, bt709), 1920x1080 [SAR 1:1 DAR 16:9], 3070 kb/s, 29.97 fps, 29.97 tbr, 16k tbn, 59.94 tbc (default)
    Metadata:
      handler_name    : ISO Media file produced by Google Inc.
      vendor_id       : [0][0][0][0]
  Stream #0:1(eng): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 128 kb/s (default)
    Metadata:
      handler_name    : SoundHandler
      vendor_id       : [0][0][0][0]
  Stream #0:2(eng): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 128 kb/s (default)
    Metadata:
      handler_name    : ISO Media file produced by Google Inc.
      vendor_id       : [0][0][0][0]
  Stream #0:3(eng): Video: vp9 (Profile 0) (vp09 / 0x39307076), yuv420p(tv, bt709), 640x360, 212 kb/s, SAR 1:1 DAR 16:9, 29.97 fps, 29.97 tbr, 16k tbn, 16k tbc (default)
    Metadata:
      handler_name    : VideoHandler
      vendor_id       : [0][0][0][0]

대충 유사하게 구할 수 있군요. 나머지 정보들은 시간이 지나면 천천히 해결이 되겠고. ^^




(2022-02-01: 업데이트)

ffprobe의 출력 결과를 그대로 얻을 수 있는 av_dump_format 함수가 있군요. ^^

int videoIndex = -1;
int audioIndex = -1;

{
    videoIndex = ffmpeg.av_find_best_stream(av_context, AVMediaType.AVMEDIA_TYPE_VIDEO, -1, -1, null, 0);
    audioIndex = ffmpeg.av_find_best_stream(av_context, AVMediaType.AVMEDIA_TYPE_AUDIO, -1, -1 /* 또는 videoIndex */, null, 0);

    ffmpeg.av_dump_format(av_context, videoIndex, filePath, 0);
}

(첨부 파일은 이 글의 예제 코드를 포함합니다.)




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 2/1/2022]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




1  2  3  4  5  6  7  8  9  10  11  12  [13]  14  15  ...
NoWriterDateCnt.TitleFile(s)
13297정성태3/26/20234350Windows: 235. Win32 - Code Modal과 UI Modal
13296정성태3/25/20233692Windows: 234. IsDialogMessage와 협업하는 WM_GETDLGCODE Win32 메시지 [1]파일 다운로드1
13295정성태3/24/20233957Windows: 233. Win32 - modeless 대화창을 modal처럼 동작하게 만드는 방법파일 다운로드1
13294정성태3/22/20234126.NET Framework: 2105. LargeAddressAware 옵션이 적용된 닷넷 32비트 프로세스의 가용 메모리 - 두 번째
13293정성태3/22/20234195오류 유형: 853. dumpbin - warning LNK4048: Invalid format file; ignored
13292정성태3/21/20234314Windows: 232. C/C++ - 일반 창에도 사용 가능한 IsDialogMessage파일 다운로드1
13291정성태3/20/20234719.NET Framework: 2104. C# Windows Forms - WndProc 재정의와 IMessageFilter 사용 시의 차이점
13290정성태3/19/20234223.NET Framework: 2103. C# - 윈도우에서 기본 제공하는 FindText 대화창 사용법파일 다운로드1
13289정성태3/18/20233419Windows: 231. Win32 - 대화창 템플릿의 2진 리소스를 읽어들여 자식 윈도우를 생성하는 방법파일 다운로드1
13288정성태3/17/20233518Windows: 230. Win32 - 대화창의 DLU 단위를 pixel로 변경하는 방법파일 다운로드1
13287정성태3/16/20233688Windows: 229. Win32 - 대화창 템플릿의 2진 리소스를 읽어들여 윈도우를 직접 띄우는 방법파일 다운로드1
13286정성태3/15/20234147Windows: 228. Win32 - 리소스에 포함된 대화창 Template의 2진 코드 해석 방법
13285정성태3/14/20233740Windows: 227. Win32 C/C++ - Dialog Procedure를 재정의하는 방법파일 다운로드1
13284정성태3/13/20233941Windows: 226. Win32 C/C++ - Dialog에서 값을 반환하는 방법파일 다운로드1
13283정성태3/12/20233481오류 유형: 852. 파이썬 - TypeError: coercing to Unicode: need string or buffer, NoneType found
13282정성태3/12/20233819Linux: 58. WSL - nohup 옵션이 필요한 경우
13281정성태3/12/20233720Windows: 225. 윈도우 바탕화면의 아이콘들이 넓게 퍼지는 경우 [2]
13280정성태3/9/20234470개발 환경 구성: 670. WSL 2에서 호스팅 중인 TCP 서버를 외부에서 접근하는 방법
13279정성태3/9/20234013오류 유형: 851. 파이썬 ModuleNotFoundError: No module named '_cffi_backend'
13278정성태3/8/20233972개발 환경 구성: 669. WSL 2의 (init이 아닌) systemd 지원 [1]
13277정성태3/6/20234636개발 환경 구성: 668. 코드 사인용 인증서 신청 및 적용 방법(예: Digicert)
13276정성태3/5/20234315.NET Framework: 2102. C# 11 - ref struct/ref field를 위해 새롭게 도입된 scoped 예약어
13275정성태3/3/20234669.NET Framework: 2101. C# 11의 ref 필드 설명
13274정성태3/2/20234260.NET Framework: 2100. C# - ref 필드로 ref struct 타입을 허용하지 않는 이유
13273정성태2/28/20233957.NET Framework: 2099. C# - 관리 포인터로서의 ref 예약어 의미
13272정성태2/27/20234216오류 유형: 850. SSMS - mdf 파일을 Attach 시킬 때 Operating system error 5: "5(Access is denied.)" 에러
1  2  3  4  5  6  7  8  9  10  11  12  [13]  14  15  ...